From daabd1470059adbda9dfd0895c52085d5cbefc36 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 10 Feb 2024 12:03:28 +0100 Subject: [PATCH] Do not use DotEnv store for exec-env. This avoids quoting problems, fixes #784, and also better handles various problems that can arise, like '=' in keys and non-string keys and values. Signed-off-by: Felix Fontein --- cmd/sops/decrypt.go | 13 +++++++++++-- cmd/sops/main.go | 27 +++++++++++++++++++++++++-- cmd/sops/subcommand/exec/exec.go | 11 ++++++++++- functional-tests/src/lib.rs | 2 +- stores/dotenv/store.go | 4 ++-- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/cmd/sops/decrypt.go b/cmd/sops/decrypt.go index 09d92106a..db038787b 100644 --- a/cmd/sops/decrypt.go +++ b/cmd/sops/decrypt.go @@ -25,8 +25,8 @@ type decryptOpts struct { DecryptionOrder []string } -func decrypt(opts decryptOpts) (decryptedFile []byte, err error) { - tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ +func decryptTree(opts decryptOpts) (tree *sops.Tree, err error) { + tree, err = common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ Cipher: opts.Cipher, InputStore: opts.InputStore, InputPath: opts.InputPath, @@ -48,6 +48,15 @@ func decrypt(opts decryptOpts) (decryptedFile []byte, err error) { return nil, err } + return tree, nil +} + +func decrypt(opts decryptOpts) (decryptedFile []byte, err error) { + tree, err := decryptTree(opts) + if err != nil { + return nil, err + } + if len(opts.Extract) > 0 { return extract(tree, opts.Extract, opts.OutputStore) } diff --git a/cmd/sops/main.go b/cmd/sops/main.go index d50db90eb..cc8078424 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -192,17 +192,40 @@ func main() { log.Warn("exec-env's --background option is deprecated and will be removed in a future version of sops") } - output, err := decrypt(opts) + tree, err := decryptTree(opts) if err != nil { return toExitError(err) } + var env []string + for _, item := range tree.Branches[0] { + if dotenv.IsComplexValue(item.Value) { + return cli.NewExitError(fmt.Errorf("cannot use complex value in environment: %s", item.Value), codes.ErrorGeneric) + } + if _, ok := item.Key.(sops.Comment); ok { + continue + } + key, ok := item.Key.(string) + if !ok { + return cli.NewExitError(fmt.Errorf("cannot use non-string keys in environment, got %T", item.Key), codes.ErrorGeneric) + } + if strings.Contains(key, "=") { + return cli.NewExitError(fmt.Errorf("cannot use keys with '=' in environment: %s", key), codes.ErrorGeneric) + } + value, ok := item.Value.(string) + if !ok { + return cli.NewExitError(fmt.Errorf("cannot use non-string values in environment, got %T", item.Value), codes.ErrorGeneric) + } + env = append(env, fmt.Sprintf("%s=%s", key, value)) + } + if err := exec.ExecWithEnv(exec.ExecOpts{ Command: command, - Plaintext: output, + Plaintext: []byte{}, Background: c.Bool("background"), Pristine: c.Bool("pristine"), User: c.String("user"), + Env: env, }); err != nil { return toExitError(err) } diff --git a/cmd/sops/subcommand/exec/exec.go b/cmd/sops/subcommand/exec/exec.go index 550790986..be74a31a4 100644 --- a/cmd/sops/subcommand/exec/exec.go +++ b/cmd/sops/subcommand/exec/exec.go @@ -30,6 +30,7 @@ type ExecOpts struct { Fifo bool User string Filename string + Env []string } func GetFile(dir, filename string) *os.File { @@ -88,9 +89,15 @@ func ExecWithFile(opts ExecOpts) error { filename = handle.Name() } + var env []string + if !opts.Pristine { + env = os.Environ() + } + env = append(env, opts.Env...) + placeholdered := strings.Replace(opts.Command, "{}", filename, -1) cmd := BuildCommand(placeholdered) - cmd.Env = os.Environ() + cmd.Env = env if opts.Background { return cmd.Start() @@ -125,6 +132,8 @@ func ExecWithEnv(opts ExecOpts) error { env = append(env, string(line)) } + env = append(env, opts.Env...) + cmd := BuildCommand(opts.Command) cmd.Env = env diff --git a/functional-tests/src/lib.rs b/functional-tests/src/lib.rs index c2cf72666..b9691a1d3 100644 --- a/functional-tests/src/lib.rs +++ b/functional-tests/src/lib.rs @@ -902,7 +902,7 @@ echo -E "${bar}" String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); - assert_eq!(String::from_utf8_lossy(&output.stdout), "baz\\nbam\n"); + assert_eq!(String::from_utf8_lossy(&output.stdout), "baz\nbam\n"); } #[test] diff --git a/stores/dotenv/store.go b/stores/dotenv/store.go index 09f118aa8..1e533341e 100644 --- a/stores/dotenv/store.go +++ b/stores/dotenv/store.go @@ -134,7 +134,7 @@ func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) { func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { buffer := bytes.Buffer{} for _, item := range in[0] { - if isComplexValue(item.Value) { + if IsComplexValue(item.Value) { return nil, fmt.Errorf("cannot use complex value in dotenv file: %s", item.Value) } var line string @@ -166,7 +166,7 @@ func (store *Store) EmitExample() []byte { return bytes } -func isComplexValue(v interface{}) bool { +func IsComplexValue(v interface{}) bool { switch v.(type) { case []interface{}: return true