Skip to content

Commit c98d336

Browse files
committed
Add "--value-file" option to "sops set [...]"
This allows running "sops set [...]" without leaking secrets in process listings. To read secrets from stdin, use "/dev/stdin" as the file path. Fixes #729 Signed-off-by: Bjørn Forsman <bjorn.forsman@gmail.com>
1 parent d8f044c commit c98d336

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

cmd/sops/main.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1385,6 +1385,10 @@ func main() {
13851385
Name: "output-type",
13861386
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",
13871387
},
1388+
cli.BoolFlag{
1389+
Name: "value-file",
1390+
Usage: "treat 'value' as a file to read the actual value from (avoids leaking secrets in process listings)",
1391+
},
13881392
cli.IntFlag{
13891393
Name: "shamir-secret-sharing-threshold",
13901394
Usage: "the number of master keys required to retrieve the data key with shamir",
@@ -1430,7 +1434,18 @@ func main() {
14301434
return common.NewExitError("Invalid set index format", codes.ErrorInvalidSetFormat)
14311435
}
14321436

1433-
value, err := jsonValueToTreeInsertableValue(c.Args()[2])
1437+
var data string
1438+
if c.Bool("value-file") {
1439+
filename := c.Args()[2]
1440+
content, err := os.ReadFile(filename)
1441+
if err != nil {
1442+
return toExitError(err)
1443+
}
1444+
data = string(content)
1445+
} else {
1446+
data = c.Args()[2]
1447+
}
1448+
value, err := jsonValueToTreeInsertableValue(data)
14341449
if err != nil {
14351450
return toExitError(err)
14361451
}

functional-tests/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,54 @@ bar: baz",
490490
panic!("Output JSON does not have the expected structure");
491491
}
492492

493+
#[test]
494+
fn set_json_file_insert_with_value_file() {
495+
let file_path = prepare_temp_file(
496+
"test_set_json_file_insert_with_value_file.json",
497+
r#"{"a": 2, "b": "ba"}"#.as_bytes(),
498+
);
499+
let value_file = prepare_temp_file("insert_value_file.json", r#"{"cc": "ccc"}"#.as_bytes());
500+
assert!(
501+
Command::new(SOPS_BINARY_PATH)
502+
.arg("encrypt")
503+
.arg("-i")
504+
.arg(file_path.clone())
505+
.output()
506+
.expect("Error running sops")
507+
.status
508+
.success(),
509+
"sops didn't exit successfully"
510+
);
511+
let output = Command::new(SOPS_BINARY_PATH)
512+
.arg("set")
513+
.arg("--value-file")
514+
.arg(file_path.clone())
515+
.arg(r#"["c"]"#)
516+
.arg(value_file.clone())
517+
.output()
518+
.expect("Error running sops");
519+
println!(
520+
"stdout: {}, stderr: {}",
521+
String::from_utf8_lossy(&output.stdout),
522+
String::from_utf8_lossy(&output.stderr)
523+
);
524+
assert!(output.status.success(), "sops didn't exit successfully");
525+
let mut s = String::new();
526+
File::open(file_path)
527+
.unwrap()
528+
.read_to_string(&mut s)
529+
.unwrap();
530+
let data: Value = serde_json::from_str(&s).expect("Error parsing sops's JSON output");
531+
if let Value::Mapping(data) = data {
532+
let a = data.get(&Value::String("c".to_owned())).unwrap();
533+
if let &Value::Mapping(ref a) = a {
534+
assert_encrypted!(&a, Value::String("cc".to_owned()));
535+
return;
536+
}
537+
}
538+
panic!("Output JSON does not have the expected structure");
539+
}
540+
493541
#[test]
494542
fn set_yaml_file_update() {
495543
let file_path = prepare_temp_file(

0 commit comments

Comments
 (0)