Skip to content

Commit

Permalink
Merge pull request #5 from derektamsen/feat/add-hashicorp-vault-dynam…
Browse files Browse the repository at this point in the history
…ic-database-secrets-support

Add support for hashicorp vault dynamic database secrets
  • Loading branch information
jondot authored Apr 6, 2021
2 parents b7fbc13 + 5b9f767 commit 773b32d
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 25 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ We use the following general structure to specify sync mapping for all providers
# you can use either `env_sync` or `env` or both
env_sync:
path: ... # path to mapping
remap:
PROVIDER_VAR1: VAR3 # Maps PROVIDER_VAR1 to local env var VAR3
env:
VAR1:
path: ... # path to value or mapping
Expand All @@ -190,6 +192,19 @@ env:
path: ...
```
### Remapping Provider Variables
Providers which support syncing a list of keys and values can be remapped to different environment variable keys. Typically, when teller syncs paths from `env_sync`, the key returned from the provider is directly mapped to the environment variable key. In some cases it might be necessary to have the provider key mapped to a different variable without changing the provider settings. This can be useful when using `env_sync` for [Hashicorp Vault Dynamic Database credentials](https://www.vaultproject.io/docs/secrets/databases):

```yaml
env_sync:
path: database/roles/my-role
remap:
username: PGUSER
password: PGPASSWORD
```

After remapping, the local environment variable `PGUSER` will contain the provider value for `username` and `PGPASSWORD` will contain the provider value for `password`.

## Hashicorp Vault

Expand Down
11 changes: 6 additions & 5 deletions pkg/core/types.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package core

type KeyPath struct {
Env string `yaml:"env,omitempty"`
Path string `yaml:"path"`
Field string `yaml:"field,omitempty"`
Decrypt bool `yaml:"decrypt,omitempty"`
Optional bool `yaml:"optional,omitempty"`
Env string `yaml:"env,omitempty"`
Path string `yaml:"path"`
Field string `yaml:"field,omitempty"`
Remap map[string]string `yaml:"remap,omitempty"`
Decrypt bool `yaml:"decrypt,omitempty"`
Optional bool `yaml:"optional,omitempty"`
}
type WizardAnswers struct {
Project string
Expand Down
19 changes: 16 additions & 3 deletions pkg/providers/hashicorp_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ func (h *HashicorpVault) GetMapping(p core.KeyPath) ([]core.EnvEntry, error) {
return nil, err
}

k := secret.Data["data"].(map[string]interface{})
// vault returns a secret kv struct as either data{} or data.data{} depending on engine
var k map[string]interface{}
if val, ok := secret.Data["data"]; ok {
k = val.(map[string]interface{})
} else {
k = secret.Data
}

entries := []core.EnvEntry{}
for k, v := range k {
Expand All @@ -57,7 +63,14 @@ func (h *HashicorpVault) Get(p core.KeyPath) (*core.EnvEntry, error) {
return nil, err
}

data := secret.Data["data"].(map[string]interface{})
// vault returns a secret kv struct as either data{} or data.data{} depending on engine
var data map[string]interface{}
if val, ok := secret.Data["data"]; ok {
data = val.(map[string]interface{})
} else {
data = secret.Data
}

k := data[p.Env]
if p.Field != "" {
k = data[p.Field]
Expand All @@ -81,7 +94,7 @@ func (h *HashicorpVault) getSecret(kp core.KeyPath) (*api.Secret, error) {
return nil, err
}

if secret == nil || secret.Data["data"] == nil {
if secret == nil || len(secret.Data) == 0 {
return nil, fmt.Errorf("data not found at '%s'", kp.Path)
}

Expand Down
40 changes: 30 additions & 10 deletions pkg/providers/hashicorp_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,40 @@ func TestHashicorpVault(t *testing.T) {
client := mock_providers.NewMockHashicorpClient(ctrl)
path := "settings/prod/billing-svc"
pathmap := "settings/prod/billing-svc/all"
out := api.Secret{
Data: map[string]interface{}{
"data": map[string]interface{}{
"MG_KEY": "shazam",
"SMTP_PASS": "mailman",

tests := map[string]struct {
out api.Secret
}{
"data.data": {
out: api.Secret{
Data: map[string]interface{}{
"data": map[string]interface{}{
"MG_KEY": "shazam",
"SMTP_PASS": "mailman",
},
},
},
},
"data": {
out: api.Secret{
Data: map[string]interface{}{
"MG_KEY": "shazam",
"SMTP_PASS": "mailman",
},
},
},
}
client.EXPECT().Read(gomock.Eq(path)).Return(&out, nil).AnyTimes()
client.EXPECT().Read(gomock.Eq(pathmap)).Return(&out, nil).AnyTimes()
s := HashicorpVault{
client: client,

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
client.EXPECT().Read(gomock.Eq(path)).Return(&tc.out, nil).AnyTimes()
client.EXPECT().Read(gomock.Eq(pathmap)).Return(&tc.out, nil).AnyTimes()
s := HashicorpVault{
client: client,
}
AssertProvider(t, &s, true)
})
}
AssertProvider(t, &s, true)
}

func TestHashicorpVaultFailures(t *testing.T) {
Expand Down
8 changes: 8 additions & 0 deletions pkg/teller.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ func (tl *Teller) Collect() error {
if err != nil {
return err
}

// optionally remap environment variables synced from the provider
for k, v := range es {
if val, ok := conf.EnvMapping.Remap[v.Key]; ok {
es[k].Key = val
}
}

entries = append(entries, es...)
}
if conf.Env != nil {
Expand Down
23 changes: 16 additions & 7 deletions pkg/teller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ func (im *InMemProvider) Name() string {
func NewInMemProvider(alwaysError bool) (Providers, error) {
return &InMemProvider{
inmem: map[string]string{
"prod/billing/FOO": "foo_shazam",
"prod/billing/MG_KEY": "mg_shazam",
"prod/billing/FOO": "foo_shazam",
"prod/billing/MG_KEY": "mg_shazam",
"prod/billing/BEFORE_REMAP": "test_env_remap",
},
alwaysError: alwaysError,
}, nil
Expand Down Expand Up @@ -144,23 +145,31 @@ func TestTellerCollectWithSync(t *testing.T) {
"inmem": {
EnvMapping: &core.KeyPath{
Path: "{{stage}}/billing",
Remap: map[string]string{
"prod/billing/BEFORE_REMAP": "prod/billing/REMAPED",
},
},
},
},
},
}
err := tl.Collect()
assert.Nil(t, err)
assert.Equal(t, len(tl.Entries), 2)
assert.Equal(t, tl.Entries[0].Key, "prod/billing/MG_KEY")
assert.Equal(t, tl.Entries[0].Value, "mg_shazam")
assert.Equal(t, len(tl.Entries), 3)
assert.Equal(t, tl.Entries[0].Key, "prod/billing/REMAPED")
assert.Equal(t, tl.Entries[0].Value, "test_env_remap")
assert.Equal(t, tl.Entries[0].ResolvedPath, "prod/billing")
assert.Equal(t, tl.Entries[0].Provider, "inmem")

assert.Equal(t, tl.Entries[1].Key, "prod/billing/FOO")
assert.Equal(t, tl.Entries[1].Value, "foo_shazam")
assert.Equal(t, tl.Entries[1].Key, "prod/billing/MG_KEY")
assert.Equal(t, tl.Entries[1].Value, "mg_shazam")
assert.Equal(t, tl.Entries[1].ResolvedPath, "prod/billing")
assert.Equal(t, tl.Entries[1].Provider, "inmem")

assert.Equal(t, tl.Entries[2].Key, "prod/billing/FOO")
assert.Equal(t, tl.Entries[2].Value, "foo_shazam")
assert.Equal(t, tl.Entries[2].ResolvedPath, "prod/billing")
assert.Equal(t, tl.Entries[2].Provider, "inmem")
}
func TestTellerCollectWithErrors(t *testing.T) {
var b bytes.Buffer
Expand Down

0 comments on commit 773b32d

Please sign in to comment.