Skip to content

Commit 1914fd3

Browse files
authored
Params by path (#12)
* save * Working-ish sketch * Remove debug logging, add some tests * log.Fatal -> log.Fatalf * Trim trailing slash, refactor/regroup GetValue stuff * Update tests * Address #14 by removing log.Fatalf
1 parent 30f8043 commit 1914fd3

File tree

8 files changed

+161
-59
lines changed

8 files changed

+161
-59
lines changed

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,22 @@ data:
129129
```
130130

131131

132+
```
133+
apiVersion: v1
134+
kind: Secret
135+
metadata:
136+
name: my-secret
137+
annotations:
138+
"alpha.ssm.cmattoon.com/k8s-secret-name": app-secrets
139+
"alpha.ssm.cmattoon.com/aws-param-name": /path/to/env
140+
"alpha.ssm.cmattoon.com/aws-param-type": Directory
141+
"alpha.ssm.cmattoon.com/aws-param-key": "alias/aws/ssm"
142+
data:
143+
file1: value1
144+
file2: value2
145+
```
146+
147+
132148
Build
133149
-----
134150

File renamed without changes.

multi-secret.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: multi-secret
5+
annotations:
6+
alpha.ssm.cmattoon.com/aws-param-type: Directory
7+
alpha.ssm.cmattoon.com/aws-param-name: /path/to/test
8+
alpha.ssm.cmattoon.com/aws-param-key: alias/aws/ssm

pkg/controller/controller.go

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func (c *Controller) HandleSecrets(cli kubernetes.Interface) error {
7272
_, err = obj.UpdateObject(cli)
7373
if err != nil {
7474
log.Warnf("Failed to update object %s/%s", obj.Namespace, obj.Name)
75+
log.Warn(err.Error())
7576
continue
7677
}
7778
log.Infof("Successfully updated %s/%s", obj.Namespace, obj.Name)

pkg/provider/aws.go

+25-3
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
package provider
1717

1818
import (
19-
log "github.com/sirupsen/logrus"
19+
"path"
2020

2121
"github.com/aws/aws-sdk-go/aws"
2222
"github.com/aws/aws-sdk-go/aws/credentials"
2323
"github.com/aws/aws-sdk-go/aws/session"
2424
"github.com/aws/aws-sdk-go/service/ssm"
2525
"github.com/cmattoon/aws-ssm/pkg/config"
26+
log "github.com/sirupsen/logrus"
2627
)
2728

2829
type AWSProvider struct {
@@ -47,16 +48,37 @@ func NewAWSProvider(cfg *config.Config) (Provider, error) {
4748
}
4849

4950
func (p AWSProvider) GetParameterValue(name string, decrypt bool) (string, error) {
50-
log.Debugf("GetParameterValue(%v, %v)", name, decrypt)
5151
param, err := p.Service.GetParameter(&ssm.GetParameterInput{
5252
Name: aws.String(name),
5353
WithDecryption: aws.Bool(decrypt),
5454
})
5555

5656
if err != nil {
57-
log.Debug("Failed to get value. Returning ''")
57+
log.Errorf("Failed to GetParameterValue: %s", err)
5858
return "", err
5959
}
6060

6161
return *param.Parameter.Value, nil
6262
}
63+
64+
func (p AWSProvider) GetParameterDataByPath(ppath string, decrypt bool) (map[string]string, error) {
65+
// ppath is something like /path/to/env
66+
params, err := p.Service.GetParametersByPath(&ssm.GetParametersByPathInput{
67+
Path: aws.String(ppath),
68+
Recursive: aws.Bool(true),
69+
WithDecryption: aws.Bool(decrypt),
70+
})
71+
72+
if err != nil {
73+
log.Errorf("Failed to GetParameterDataByPath: %s", err)
74+
return nil, err
75+
}
76+
77+
results := make(map[string]string)
78+
// '/path/to/env/foo' -> 'foo': *pa.Value
79+
for _, pa := range params.Parameters {
80+
_, basename := path.Split(*pa.Name)
81+
results[basename] = *pa.Value
82+
}
83+
return results, nil
84+
}

pkg/provider/provider.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,40 @@
1616
package provider
1717

1818
import (
19+
"errors"
1920
//log "github.com/sirupsen/logrus"
2021
"github.com/cmattoon/aws-ssm/pkg/config"
2122
)
2223

2324
type Provider interface {
2425
GetParameterValue(string, bool) (string, error)
26+
GetParameterDataByPath(string, bool) (map[string]string, error)
2527
}
2628

2729
func NewProvider(cfg *config.Config) (Provider, error) {
2830
p, err := NewAWSProvider(cfg)
2931
return p, err
3032
}
3133

34+
// Mock an error with {"(error)", "error message"}
3235
type MockProvider struct {
33-
Value string
34-
DecryptedValue string
36+
Value string
37+
DecryptedValue string
38+
DirectoryContents map[string]string
3539
}
3640

3741
func (mp MockProvider) GetParameterValue(s string, b bool) (string, error) {
42+
if mp.Value == "(error)" {
43+
return "", errors.New(mp.DecryptedValue)
44+
}
45+
3846
if b {
3947
// Decrypt flag
4048
return mp.DecryptedValue, nil
4149
}
4250
return mp.Value, nil
4351
}
52+
53+
func (mp MockProvider) GetParameterDataByPath(s string, b bool) (map[string]string, error) {
54+
return mp.DirectoryContents, nil
55+
}

pkg/secret/secret.go

+44-22
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,51 @@ func NewSecret(sec v1.Secret, p provider.Provider, secret_name string, secret_na
5959
Data: map[string]string{},
6060
}
6161

62-
log.Infof("Getting value for '%s/%s'", s.Namespace, s.Name)
62+
log.Debugf("Getting value for '%s/%s'", s.Namespace, s.Name)
6363

6464
decrypt := false
6565
if s.ParamKey != "" {
6666
decrypt = true
6767
}
6868

69-
value, err := p.GetParameterValue(s.ParamName, decrypt)
70-
71-
if err != nil {
72-
log.Infof("Couldn't get value for %s/%s: %s",
73-
s.Namespace, s.Name, err)
74-
return nil, err
75-
} else {
69+
if s.ParamType == "String" || s.ParamType == "SecureString" {
70+
value, err := p.GetParameterValue(s.ParamName, decrypt)
71+
if err != nil {
72+
return nil, err
73+
}
7674
s.ParamValue = value
75+
} else if s.ParamType == "StringList" {
76+
value, err := p.GetParameterValue(s.ParamName, decrypt)
77+
if err != nil {
78+
return nil, err
79+
}
80+
s.ParamValue = value
81+
// StringList: Also set each key
82+
values := s.ParseStringList()
83+
for k, v := range values {
84+
s.Set(k, v)
85+
}
86+
} else if s.ParamType == "Directory" {
87+
// Directory: Set each sub-key
88+
all_params, err := p.GetParameterDataByPath(s.ParamName, decrypt)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
for k, v := range all_params {
94+
s.Set(safeKeyName(k), v)
95+
}
96+
s.ParamValue = "true" // Reads "Directory": "true"
97+
return s, nil
7798
}
7899

100+
// Always set the "$ParamType" key:
101+
// String: Value
102+
// SecureString: Value
103+
// StringList: Value
104+
// Directory: <ssm-path>
105+
s.Set(s.ParamType, s.ParamValue)
106+
79107
return s, nil
80108
}
81109

@@ -147,6 +175,7 @@ func (s *Secret) ParseStringList() (values map[string]string) {
147175
}
148176

149177
func (s *Secret) Set(key string, val string) (err error) {
178+
log.Debugf("Setting key=%s", key)
150179
if s.Secret.StringData == nil {
151180
s.Secret.StringData = make(map[string]string)
152181
}
@@ -161,20 +190,13 @@ func (s *Secret) Set(key string, val string) (err error) {
161190

162191
func (s *Secret) UpdateObject(cli kubernetes.Interface) (result *v1.Secret, err error) {
163192
log.Info("Updating Kubernetes Secret...")
193+
return cli.CoreV1().Secrets(s.Namespace).Update(&s.Secret)
194+
}
164195

165-
// Always set the "$ParamType" key:
166-
// String: Value
167-
// SecureString: Value
168-
// StringList: Value
169-
s.Set(s.ParamType, s.ParamValue)
170-
171-
// If it's a StringList, attempt to set each k/v pair separately
172-
if s.ParamType == "StringList" {
173-
values := s.ParseStringList()
174-
for k, v := range values {
175-
s.Set(k, v)
176-
}
196+
func safeKeyName(key string) string {
197+
key = strings.TrimRight(key, "/")
198+
if strings.HasPrefix(key, "/") {
199+
key = strings.Replace(key, "/", "", 1)
177200
}
178-
179-
return cli.CoreV1().Secrets(s.Namespace).Update(&s.Secret)
201+
return strings.Replace(key, "/", "_", -1)
180202
}

pkg/secret/secret_test.go

+53-32
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121

2222
"github.com/cmattoon/aws-ssm/pkg/provider"
2323
"github.com/stretchr/testify/assert"
24-
//"github.com/stretchr/testify/require"
24+
"github.com/stretchr/testify/require"
2525
"k8s.io/api/core/v1"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
)
@@ -137,10 +137,8 @@ func TestSet(t *testing.T) {
137137
ParamValue: "key1=val1,key2=val2,key3=val3,key4=val4=true",
138138
Data: map[string]string{},
139139
}
140-
s.Set("foo", "bar")
141-
if s.Secret.StringData["foo"] != "bar" {
142-
t.Fail()
143-
}
140+
require.NoError(t, s.Set("foo", "bar"))
141+
assert.Equal(t, s.Secret.StringData["foo"], "bar")
144142
}
145143

146144
func TestSetRefusesToOverwriteKey(t *testing.T) {
@@ -151,48 +149,58 @@ func TestSetRefusesToOverwriteKey(t *testing.T) {
151149
ParamType: "StringList",
152150
ParamKey: "foo-param",
153151
ParamValue: "key1=val1,key2=val2,key3=val3,key4=val4=true",
154-
Data: map[string]string{},
155-
}
156-
err := s.Set("foo", "bar")
157-
if err != nil {
158-
t.Fail()
159-
}
160-
err = s.Set("foo", "baz")
161-
if err != nil {
162-
t.Fail()
163-
}
164-
if s.Secret.StringData["foo"] != "baz" {
165-
t.Fail()
152+
Data: map[string]string{
153+
"foo": "bar",
154+
},
166155
}
156+
require.Error(t, s.Set("foo", "baz"))
157+
158+
assert.Equal(t, "bar", s.Data["foo"])
159+
// "foo=bar" was specified initially, not via Set
160+
// so strictly speaking, it shouldn't be set here yet
161+
// (because s.Set() should fail)
162+
assert.Equal(t, "", s.Secret.StringData["foo"])
167163
}
168164

169165
func TestSetsValue(t *testing.T) {
170-
p := provider.MockProvider{"FooBar123", "PlaintextIsAnError"}
166+
p := provider.MockProvider{"FooBar123", "PlaintextIsAnError", make(map[string]string)}
171167
s := v1.Secret{}
172168
testSecret, err := NewSecret(s, p, "foo-secret", "namespace", "foo-param", "String", "")
173-
if err != nil {
174-
t.Fail()
175-
}
176-
if testSecret.ParamValue != "FooBar123" {
177-
t.Fail()
178-
}
169+
170+
assert.Equal(t, err, nil)
171+
assert.Equal(t, testSecret.ParamValue, "FooBar123")
179172
}
180173

181174
// When the encryption key is defined, the decrypted value should be returned
182175
func TestNewSecretDecryptsIfKeyIsSet(t *testing.T) {
183-
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123"}
176+
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
184177
s := v1.Secret{}
185178
testSecret, err := NewSecret(s, p, "foo-secret", "namespace", "foo-param", "String", "my/test/key")
186-
if err != nil {
187-
t.Fail()
179+
assert.Equal(t, nil, err)
180+
assert.Equal(t, testSecret.ParamValue, p.DecryptedValue)
181+
}
182+
183+
func TestNewSecretHandlesStringList(t *testing.T) {
184+
p := provider.MockProvider{"$@#*$(@)*$", "key1=val1,key2=val2,key3=val3", make(map[string]string)}
185+
s := v1.Secret{}
186+
ts, err := NewSecret(s, p, "foo-secret", "namespace", "foo-param", "StringList", "my/test/key")
187+
assert.True(t, err == nil)
188+
exp := map[string]string{
189+
"key1": "val1",
190+
"key2": "val2",
191+
"key3": "val3",
188192
}
189-
if testSecret.ParamValue != p.DecryptedValue {
190-
t.Fail()
193+
194+
for k, v := range exp {
195+
v3, ok := ts.Secret.StringData[k]
196+
assert.Equal(t, ok, true)
197+
assert.Equal(t, v, v3)
191198
}
199+
192200
}
193201

194202
func TestFromKubernetesSecretReturnsErrorIfIrrelevant(t *testing.T) {
195-
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123"}
203+
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
196204
s := v1.Secret{} // No annotations, so no params
197205

198206
_, err := FromKubernetesSecret(p, s)
@@ -204,7 +212,7 @@ func TestFromKubernetesSecretReturnsErrorIfIrrelevant(t *testing.T) {
204212
// If the parameter is of Type=SecureString, and no key is supplied,
205213
// attempt to use the default key.
206214
func TestFromKubernetesSecretUsesDefaultEncryptionKey(t *testing.T) {
207-
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123"}
215+
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
208216

209217
s := v1.Secret{
210218
ObjectMeta: metav1.ObjectMeta{
@@ -223,7 +231,7 @@ func TestFromKubernetesSecretUsesDefaultEncryptionKey(t *testing.T) {
223231
}
224232

225233
func TestFromKubernetesSecretUsesSpecifiedEncryptionKey(t *testing.T) {
226-
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123"}
234+
p := provider.MockProvider{"$@#*$(@)*$", "FooBar123", make(map[string]string)}
227235

228236
s := v1.Secret{
229237
ObjectMeta: metav1.ObjectMeta{
@@ -241,3 +249,16 @@ func TestFromKubernetesSecretUsesSpecifiedEncryptionKey(t *testing.T) {
241249
t.Fail()
242250
}
243251
}
252+
253+
func TestSafeKeyName(t *testing.T) {
254+
keys := map[string]string{
255+
"/foo/bar": "foo_bar",
256+
"/foo/bar/": "foo_bar",
257+
"//foo/bar": "_foo_bar",
258+
"//foo/bar/": "_foo_bar",
259+
"/foo/bar/baz": "foo_bar_baz",
260+
}
261+
for path, exp := range keys {
262+
assert.Equal(t, safeKeyName(path), exp)
263+
}
264+
}

0 commit comments

Comments
 (0)