From 85846c8d022eb68b41311076c45f1546374d3e3a Mon Sep 17 00:00:00 2001 From: Clemens Wolff Date: Tue, 12 Dec 2023 12:08:20 +0100 Subject: [PATCH] Add option to export a portion of secrets --- cmd/ejson2env/main.go | 6 +++++- man/ejson2env.1.ronn | 4 ++++ secrets.go | 28 ++++++++++++++++++++++++++-- secrets_test.go | 17 +++++++++++++---- testdata/test-expected-usage.ejson | 4 +++- 5 files changed, 51 insertions(+), 8 deletions(-) diff --git a/cmd/ejson2env/main.go b/cmd/ejson2env/main.go index 1f0b540..36d89ca 100644 --- a/cmd/ejson2env/main.go +++ b/cmd/ejson2env/main.go @@ -38,6 +38,10 @@ func main() { Name: "quiet, q", Usage: "Suppress export statement", }, + cli.StringSliceFlag{ + Name: "include, i", + Usage: "Export only a subset of environment variables", + }, } app.Action = func(c *cli.Context) { @@ -69,7 +73,7 @@ func main() { fail(fmt.Errorf("no secrets.ejson filename passed")) } - if err := ejson2env.ReadAndExportEnv(filename, keydir, userSuppliedPrivateKey, exportFunc); nil != err { + if err := ejson2env.ReadAndExportEnv(filename, keydir, userSuppliedPrivateKey, exportFunc, c.StringSlice("include")); nil != err { fail(err) } } diff --git a/man/ejson2env.1.ronn b/man/ejson2env.1.ronn index d35b9b5..76e31cf 100644 --- a/man/ejson2env.1.ronn +++ b/man/ejson2env.1.ronn @@ -23,6 +23,10 @@ workflow example. If set, assumes that the key that decrypts the passed ejson file was passed in on standard input. + * `--include`=: + If set, include in the output only the specified key(s). Can be provided + more than once. + ## WORKFLOW ### 1: Add environment variables to an ejson file diff --git a/secrets.go b/secrets.go index d600aab..dd985fd 100644 --- a/secrets.go +++ b/secrets.go @@ -40,15 +40,39 @@ func IsEnvError(err error) bool { return (errNoEnv == err || errEnvNotMap == err) } +// FilterEnv removes any key from the secrets that's not in include. +// If include is empty, return the secrets unchanged. +func FilterEnv(originalEnv map[string]string, include []string) (map[string]string, error) { + if len(include) == 0 { + return originalEnv, nil + } + + filteredEnv := make(map[string]string, len(include)) + for _, key := range include { + if value, exists := originalEnv[key]; exists { + filteredEnv[key] = value + } else { + return map[string]string{}, fmt.Errorf("key not found in ejson file: %s", key) + } + } + + return filteredEnv, nil +} + // ReadAndExportEnv wraps the read, extract, and export steps. Returns // an error if any step fails. -func ReadAndExportEnv(filename, keyDir, privateKey string, exportFunc ExportFunction) error { +func ReadAndExportEnv(filename, keyDir, privateKey string, exportFunc ExportFunction, include []string) error { envValues, err := ReadAndExtractEnv(filename, keyDir, privateKey) if nil != err && !IsEnvError(err) { return fmt.Errorf("could not load environment from file: %s", err) } - exportFunc(output, envValues) + filteredEnv, err := FilterEnv(envValues, include) + if nil != err { + return err + } + + exportFunc(output, filteredEnv) return nil } diff --git a/secrets_test.go b/secrets_test.go index 8ad925b..219caee 100644 --- a/secrets_test.go +++ b/secrets_test.go @@ -31,22 +31,31 @@ func TestReadAndExportEnv(t *testing.T) { tests := []struct { name string exportFunc ExportFunction + include []string expectedOutput string }{ { name: "ExportEnv", exportFunc: ExportEnv, - expectedOutput: "export test_key='test value'\n", + include: make([]string, 0), + expectedOutput: "export test_key='test value'\nexport test_key_2='test value 2'\nexport test_key_3='test value 3'\n", }, { name: "ExportQuiet", exportFunc: ExportQuiet, - expectedOutput: "test_key='test value'\n", + include: make([]string, 0), + expectedOutput: "test_key='test value'\ntest_key_2='test value 2'\ntest_key_3='test value 3'\n", + }, + { + name: "ExportInclude", + exportFunc: ExportEnv, + include: []string{"test_key", "test_key_3"}, + expectedOutput: "export test_key='test value'\nexport test_key_3='test value 3'\n", }, } for _, test := range tests { - err := ReadAndExportEnv("testdata/test-expected-usage.ejson", "./key", TestKeyValue, test.exportFunc) + err := ReadAndExportEnv("testdata/test-expected-usage.ejson", "./key", TestKeyValue, test.exportFunc, test.include) if nil != err { t.Errorf("testing %s failed: %s", test.name, err) continue @@ -72,7 +81,7 @@ func TestReadAndExportEnvWithBadEjson(t *testing.T) { output = os.Stdout }() - err = ReadAndExportEnv("bad.ejson", "./key", TestKeyValue, ExportEnv) + err = ReadAndExportEnv("bad.ejson", "./key", TestKeyValue, ExportEnv, make([]string, 0)) if nil == err { t.Fatal("failed to fail when loading a broken ejson file") } diff --git a/testdata/test-expected-usage.ejson b/testdata/test-expected-usage.ejson index 942895d..aa07d00 100644 --- a/testdata/test-expected-usage.ejson +++ b/testdata/test-expected-usage.ejson @@ -1,6 +1,8 @@ { "_public_key": "795e671066eef17025c816b6d7c4f5c658b191cfaa31baca69963d761606415c", "environment": { - "test_key": "EJ[1:dgKp4cmFm42z8pfH/FJD8KRpaum5ZB6zACbuYvNFrGA=:wwS8w2C2Ihrg62M6E8H/G1Tt5gXocugo:Rv1qmluaq4HZHfr1lVtvAXxHrHsZSYio2wI=]" + "test_key": "EJ[1:dgKp4cmFm42z8pfH/FJD8KRpaum5ZB6zACbuYvNFrGA=:wwS8w2C2Ihrg62M6E8H/G1Tt5gXocugo:Rv1qmluaq4HZHfr1lVtvAXxHrHsZSYio2wI=]", + "test_key_2": "EJ[1:J2tLLU5iE4QqgzM1hOpK44jv8DhC8+1VYJGEh+2AJkE=:tRz/uG5NHkzRYmJ8+SUQMZxoMSO/gNR9:lnUJz70aqVT6Y3oQtybXHKDC88srWAhPbiqTuA==]", + "test_key_3": "EJ[1:CP6m/QOGqF4mXVzMrvuHtw9eFUq3OEJobv6AbjDgJWM=:l8rkRosxfPmzTOCdPPsC5arkwVzKj2PM:NGL6F62pADDuOfFOh6hfA2ObSYRVo1D4uMgrYQ==]" } }