diff --git a/cmd/sops/main.go b/cmd/sops/main.go index bdbd5eab1..f40b181ee 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -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)", + }, cli.IntFlag{ Name: "shamir-secret-sharing-threshold", Usage: "the number of master keys required to retrieve the data key with shamir", @@ -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) } diff --git a/functional-tests/src/lib.rs b/functional-tests/src/lib.rs index ad3034116..6ed0289cb 100644 --- a/functional-tests/src/lib.rs +++ b/functional-tests/src/lib.rs @@ -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(