Skip to content

Commit

Permalink
new/lombric: added a way to read secrets from files (#77)
Browse files Browse the repository at this point in the history
* new/lombric: added a way to read secret from files

* lint + better trim

* fixed: comments

* style

* added option to delete file after reading it

* fixed: lint
  • Loading branch information
primalmotion authored Oct 8, 2020
1 parent 25bba10 commit c9be039
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 11 deletions.
34 changes: 33 additions & 1 deletion lombric/lombric.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
package lombric

import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"os"
"reflect"
"strconv"
Expand Down Expand Up @@ -96,12 +99,41 @@ func Initialize(conf Configurable) {
fail()
}

// Replace secret from content of files if needed.
if _, ok := conf.(EnvPrexixer); ok {

for _, key := range secretFlags {

value := viper.GetString(key)

if strings.HasPrefix(value, "file://") {

u, err := url.Parse(value)
if err != nil {
panic(fmt.Sprintf("invalid url for secret: %s", err))
}

data, err := ioutil.ReadFile(u.Path)
if err != nil {
panic(fmt.Sprintf("unable to read secret file for key '%s': %s", key, err))
}
viper.Set(key, string(bytes.TrimSpace(data)))

if u.Query().Get("delete") != "" {
if err := os.Remove(u.Path); err != nil {
panic(fmt.Sprintf("unable to delete secret file: %s", err))
}
}
}
}
}

if err := viper.Unmarshal(conf); err != nil {
panic("Unable to unmarshal configuration: " + err.Error())
}

// Clean up all secrets
if p, ok := conf.(EnvPrexixer); ok {
// Clean up all secrets
for _, key := range secretFlags {
env := strings.Replace(strings.ToUpper(p.Prefix()+"_"+key), "-", "_", -1)
if err := os.Unsetenv(env); err != nil {
Expand Down
58 changes: 48 additions & 10 deletions lombric/lombric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
package lombric

import (
"fmt"
"io/ioutil"
"net"
"os"
"testing"
Expand All @@ -26,30 +28,33 @@ func init() {
}

type testConf struct {
ABool bool `mapstructure:"a-bool" desc:"This is a boolean" required:"true" default:"true"`
ABool bool `mapstructure:"a-bool" desc:"This is a boolean" default:"true"`
ARequiredBool bool `mapstructure:"a-required-bool" desc:"This is a boolean" required:"true"`
ABoolNoDef bool `mapstructure:"a-bool-nodef" desc:"This is a no def boolean" `
ABoolSlice []bool `mapstructure:"a-bool-slice" desc:"This is a bool slice" required:"true" default:"true,false,true"`
ADuration time.Duration `mapstructure:"a-duration" desc:"This is a duration" required:"true" default:"10s"`
ABoolSlice []bool `mapstructure:"a-bool-slice" desc:"This is a bool slice" default:"true,false,true"`
ADuration time.Duration `mapstructure:"a-duration" desc:"This is a duration" default:"10s"`
ADurationNoDef time.Duration `mapstructure:"a-duration-nodef" desc:"This is a no def duration" `
AInteger int `mapstructure:"a-integer" desc:"This is a number" required:"true" default:"42"`
AInteger int `mapstructure:"a-integer" desc:"This is a number" default:"42"`
AIntegerNoDef int `mapstructure:"a-integer-nodef" desc:"This is a no def number" `
AIntSlice []int `mapstructure:"a-int-slice" desc:"This is a int slice" required:"true" default:"1,2,3"`
AIntSlice []int `mapstructure:"a-int-slice" desc:"This is a int slice" default:"1,2,3"`
AnEnum string `mapstructure:"a-enum" desc:"This is an enum" allowed:"a,b,c" default:"a"`
AnIPSlice []net.IP `mapstructure:"a-ip-slice" desc:"This is an ip slice" default:"127.0.0.1,192.168.100.1"`
AnotherStringSliceNoDef []string `mapstructure:"a-string-slice-from-var" desc:"This is a no def string" `
ASecret string `mapstructure:"a-secret-from-var" desc:"This is a secret" secret:"true"`
AString string `mapstructure:"a-string" desc:"This is a string" required:"true" default:"hello"`
ASecretFromFile string `mapstructure:"a-secret-from-file" desc:"This is a secret from file" secret:"true"`
ASecretFromFileDelete string `mapstructure:"a-secret-from-file-del" desc:"This is a secret from file" secret:"true"`
AString string `mapstructure:"a-string" desc:"This is a string" default:"hello"`
AStringNoDef string `mapstructure:"a-string-nodef" desc:"This is a no def string" `
AStringSlice []string `mapstructure:"a-string-slice" desc:"This is a string slice" required:"true" default:"a,b,c"`
AStringSlice []string `mapstructure:"a-string-slice" desc:"This is a string slice" default:"a,b,c"`
AStringSliceNoDef []string `mapstructure:"a-string-slice-nodef" desc:"This is a no def string slice"`

embedTestConf `mapstructure:",squash" override:"embedded-string-a=outter1,embedded-ignored-string=-"`
}

type embedTestConf struct {
EmbeddedStringA string `mapstructure:"embedded-string-a" desc:"This is a string" required:"true" default:"inner1"`
EmbeddedStringB string `mapstructure:"embedded-string-b" desc:"This is a string" required:"true" default:"inner2"`
EmbeddedIgnoredStringB string `mapstructure:"embedded-ignored-string" desc:"This is a string" required:"true" default:"inner3"`
EmbeddedStringA string `mapstructure:"embedded-string-a" desc:"This is a string" default:"inner1"`
EmbeddedStringB string `mapstructure:"embedded-string-b" desc:"This is a string" default:"inner2"`
EmbeddedIgnoredStringB string `mapstructure:"embedded-ignored-string" desc:"This is a string" default:"inner3"`
}

// Prefix return the configuration prefix.
Expand All @@ -60,15 +65,39 @@ func TestLombric_Initialize(t *testing.T) {

Convey("Given have a conf", t, func() {

sfile1, err := ioutil.TempFile(os.TempDir(), "secret")
if err != nil {
panic(err)
}
defer sfile1.Close() // nolint
if _, err := sfile1.WriteString("this-is-super=s3cr3t\n\n"); err != nil {
panic(err)
}
spath1 := fmt.Sprintf("file://%s", sfile1.Name())

sfile2, err := ioutil.TempFile(os.TempDir(), "secret2")
if err != nil {
panic(err)
}
defer sfile2.Close() // nolint
if _, err := sfile2.WriteString("wow\n\n"); err != nil {
panic(err)
}
spath2 := fmt.Sprintf("file://%s?delete=true", sfile2.Name())

conf := &testConf{}
os.Setenv("LOMBRIC_A_STRING_SLICE_FROM_VAR", "x y z") // nolint: errcheck
os.Setenv("LOMBRIC_A_REQUIRED_BOOL", "true") // nolint: errcheck
os.Setenv("LOMBRIC_A_SECRET_FROM_VAR", "secret") // nolint: errcheck
os.Setenv("LOMBRIC_A_SECRET_FROM_FILE", spath1) // nolint: errcheck
os.Setenv("LOMBRIC_A_SECRET_FROM_FILE_DEL", spath2) // nolint: errcheck

Initialize(conf)

Convey("Then the flags should be correctly set", func() {

So(conf.ABool, ShouldEqual, true)
So(conf.ARequiredBool, ShouldEqual, true)
So(conf.ABoolNoDef, ShouldEqual, false)
So(conf.ABoolSlice, ShouldResemble, []bool{true, false, true})
So(conf.ADuration, ShouldEqual, 10*time.Second)
Expand All @@ -79,6 +108,9 @@ func TestLombric_Initialize(t *testing.T) {
So(conf.AnIPSlice, ShouldResemble, []net.IP{net.IPv4(127, 0, 0, 1), net.IPv4(192, 168, 100, 1)})
So(conf.AnotherStringSliceNoDef, ShouldResemble, []string{"x", "y", "z"})
So(conf.ASecret, ShouldEqual, "secret")
So(conf.ASecretFromFile, ShouldEqual, "this-is-super=s3cr3t")
So(conf.ASecretFromFileDelete, ShouldEqual, "wow")
So(viper.GetString("a-secret-from-file"), ShouldEqual, "this-is-super=s3cr3t")
So(conf.AString, ShouldEqual, "hello")
So(conf.AStringNoDef, ShouldEqual, "")
So(conf.AStringSlice, ShouldResemble, []string{"a", "b", "c"})
Expand All @@ -88,6 +120,12 @@ func TestLombric_Initialize(t *testing.T) {
So(conf.EmbeddedStringB, ShouldEqual, "inner2")
So(os.Getenv("LOMBRIC_A_SECRET_FROM_VAR"), ShouldEqual, "")
So(viper.AllKeys(), ShouldNotContain, "embedded-ignored-string")

_, err := os.Stat(sfile1.Name())
So(os.IsNotExist(err), ShouldBeFalse)

_, err = os.Stat(sfile2.Name())
So(os.IsNotExist(err), ShouldBeTrue)
})
})
}
Expand Down

0 comments on commit c9be039

Please sign in to comment.