Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion cmd/sops/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,10 @@ func main() {
Name: "output-type",
Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the input file's extension to determine the output format",
},
cli.BoolFlag{
Name: "value-file",
Usage: "treat 'value' as a file to read the actual value from (avoids leaking secrets in process listings)",
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about adding a (mutually exclusive) further option that reads from stdin instead of a file? That would provide a platform independent way of reading from stdin (/dev/stdin doesn't work on Windows).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but that would take some more time for me to implement and test (noob here). Also, isn't that something that can be added on top of this PR and is not in conflict/wrong direction with this change (iow, not a blocker)?

Another option could be to make the "value" argument optional, and if missing, read from stdin. (On Linux it's trivial to pipe file contents to stdin, I don't know about Windows.)

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think it's better to make things explicit, and let the user explicitly ask for reading from stdin, instead of implicitly just doing that and confusing users which forget the value argument and then wonder why the program is "hanging".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. So that makes the PR as-is go in the right direction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixfontein: I'm not sure if you're waiting for me to add --value-stdin flag, or someone else to review, but I started looking at adding --value-stdin just now. And the first issue is that it removes the need for the "value" positional argument. Which means the help text will be confusing/wrong.

How about using the special value - to mean "read from stdin"? Many CLI tools already use that. It can be documented in the --value-file option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added "treat - as stdin", and rebased.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late reply; I think having a dedicated option is better than using -. (We had a similar discussion in #739 (comment); after reading FiloSottile/age#494 I prefer a separate option. Magic can always lead to trouble, including security problems, and treating - different from other files would be some magic.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I removed treating '-' as stdin in the latest update. Not because I agree, but because I'd rather not have that block us from fixing the current secret leakage in process listings when using "sops set".

(I don't see how --value-stdin can fit into the existing "sop set [..]" interface, so that'll have to be implemented by someone else. The problem is how to have an interface with mandatory positional arguments where an optional flag removes the need for a preivously mandatory positional argument. I think it either becomes an ugly interface, which is difficult to document, or we need separate "sops set-from-stdin", which is just a different kind of ugly, IMHO.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds fair.

cli.IntFlag{
Name: "shamir-secret-sharing-threshold",
Usage: "the number of master keys required to retrieve the data key with shamir",
Expand Down Expand Up @@ -1430,7 +1434,18 @@ func main() {
return common.NewExitError("Invalid set index format", codes.ErrorInvalidSetFormat)
}

value, err := jsonValueToTreeInsertableValue(c.Args()[2])
var data string
if c.Bool("value-file") {
filename := c.Args()[2]
content, err := os.ReadFile(filename)
if err != nil {
return toExitError(err)
}
data = string(content)
} else {
data = c.Args()[2]
}
value, err := jsonValueToTreeInsertableValue(data)
if err != nil {
return toExitError(err)
}
Expand Down
48 changes: 48 additions & 0 deletions functional-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,54 @@ bar: baz",
panic!("Output JSON does not have the expected structure");
}

#[test]
fn set_json_file_insert_with_value_file() {
let file_path = prepare_temp_file(
"test_set_json_file_insert_with_value_file.json",
r#"{"a": 2, "b": "ba"}"#.as_bytes(),
);
let value_file = prepare_temp_file("insert_value_file.json", r#"{"cc": "ccc"}"#.as_bytes());
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops")
.status
.success(),
"sops didn't exit successfully"
);
let output = Command::new(SOPS_BINARY_PATH)
.arg("set")
.arg("--value-file")
.arg(file_path.clone())
.arg(r#"["c"]"#)
.arg(value_file.clone())
.output()
.expect("Error running sops");
println!(
"stdout: {}, stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success(), "sops didn't exit successfully");
let mut s = String::new();
File::open(file_path)
.unwrap()
.read_to_string(&mut s)
.unwrap();
let data: Value = serde_json::from_str(&s).expect("Error parsing sops's JSON output");
if let Value::Mapping(data) = data {
let a = data.get(&Value::String("c".to_owned())).unwrap();
if let &Value::Mapping(ref a) = a {
assert_encrypted!(&a, Value::String("cc".to_owned()));
return;
}
}
panic!("Output JSON does not have the expected structure");
}

#[test]
fn set_yaml_file_update() {
let file_path = prepare_temp_file(
Expand Down
Loading