Skip to content
This repository was archived by the owner on Jun 15, 2019. It is now read-only.

Commit 183f9c2

Browse files
authored
awsenv (#4)
* awsenv * tests * update makefile and fix imports
1 parent 1000253 commit 183f9c2

6 files changed

Lines changed: 320 additions & 0 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
test:
22
cd aws && go vet ./... && go test -v ./... -race -cover
3+
cd awsEnv && go vet ./... && go test -v ./... -race -cover

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,38 @@ go run main.go
5252
- _Note_: decryption of the value is automatically requested.
5353
- `ssmb64://` – Get base64 encoded binary value from [parameter store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
5454
- _Note_: decryption of the value is automatically requested.
55+
56+
## AWS via Env
57+
58+
Pre-process config values by reading secrets from AWS.
59+
60+
```go
61+
// main.go
62+
package main
63+
64+
import (
65+
"context"
66+
"fmt"
67+
"os"
68+
69+
"github.com/hookactions/fig/awsEnv"
70+
)
71+
72+
func main() {
73+
fig, err := awsEnv.New()
74+
if err != nil {
75+
panic(err)
76+
}
77+
78+
fmt.Println(fig.GetEnv(context.Background(), "MY_VAR"))
79+
}
80+
```
81+
82+
```bash
83+
MY_VAR=sm://foo go run main.go
84+
```
85+
86+
### Supported prefixes
87+
- `sm://` – Get string value from [secrets manager](https://aws.amazon.com/secrets-manager/)
88+
- `ssm://` – Get string value from [parameter store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html)
89+
- _Note_: decryption of the value is automatically requested.

awsEnv/env.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package awsEnv
2+
3+
import (
4+
"context"
5+
"os"
6+
"regexp"
7+
8+
"github.com/aws/aws-sdk-go-v2/aws"
9+
"github.com/aws/aws-sdk-go-v2/aws/external"
10+
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
11+
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/secretsmanageriface"
12+
"github.com/aws/aws-sdk-go-v2/service/ssm"
13+
"github.com/aws/aws-sdk-go-v2/service/ssm/ssmiface"
14+
"github.com/pkg/errors"
15+
)
16+
17+
var (
18+
secretsManagerStringRe = regexp.MustCompile("^sm://")
19+
parameterStoreStringRe = regexp.MustCompile("^ssm://")
20+
)
21+
22+
func checkPrefixAndStrip(re *regexp.Regexp, s string) (string, bool) {
23+
if re.MatchString(s) {
24+
return re.ReplaceAllString(s, ""), true
25+
}
26+
return s, false
27+
}
28+
29+
type Fig struct {
30+
DecryptParameterStoreValues bool
31+
32+
secretsManager secretsmanageriface.ClientAPI
33+
parameterStore ssmiface.ClientAPI
34+
}
35+
36+
func New() (*Fig, error) {
37+
awsConfig, err := external.LoadDefaultAWSConfig()
38+
if err != nil {
39+
return nil, errors.Wrap(err, "fig/awsEnv: error loading default aws config")
40+
}
41+
42+
fig := &Fig{
43+
DecryptParameterStoreValues: true,
44+
45+
secretsManager: secretsmanager.New(awsConfig),
46+
parameterStore: ssm.New(awsConfig),
47+
}
48+
49+
return fig, nil
50+
}
51+
52+
func (f *Fig) GetEnv(ctx context.Context, key string) string {
53+
value := os.Getenv(key)
54+
return f.processConfigItem(ctx, key, value)
55+
}
56+
57+
func (f *Fig) processConfigItem(ctx context.Context, key string, value string) string {
58+
if v, ok := checkPrefixAndStrip(secretsManagerStringRe, value); ok {
59+
return f.LoadStringValueFromSecretsManager(ctx, v)
60+
} else if v, ok := checkPrefixAndStrip(parameterStoreStringRe, v); ok {
61+
return f.LoadStringValueFromParameterStore(ctx, v, f.DecryptParameterStoreValues)
62+
}
63+
return value
64+
}
65+
66+
func (f *Fig) LoadStringValueFromSecretsManager(ctx context.Context, name string) string {
67+
resp, err := f.requestSecret(ctx, name)
68+
if err != nil {
69+
panic("fig/aws/LoadStringValueFromSecretsManager: error loading secret, " + err.Error())
70+
}
71+
72+
return *resp.SecretString
73+
}
74+
75+
func (f *Fig) requestSecret(ctx context.Context, name string) (*secretsmanager.GetSecretValueResponse, error) {
76+
input := &secretsmanager.GetSecretValueInput{SecretId: aws.String(name)}
77+
return f.secretsManager.GetSecretValueRequest(input).Send(ctx)
78+
}
79+
80+
func (f *Fig) LoadStringValueFromParameterStore(ctx context.Context, name string, decrypt bool) string {
81+
resp, err := f.requestParameter(ctx, name, decrypt)
82+
if err != nil {
83+
panic("fig/aws/LoadStringValueFromParameterStore: error loading value, " + err.Error())
84+
}
85+
86+
return *resp.Parameter.Value
87+
}
88+
89+
func (f *Fig) requestParameter(ctx context.Context, name string, decrypt bool) (*ssm.GetParameterResponse, error) {
90+
input := &ssm.GetParameterInput{Name: aws.String(name), WithDecryption: aws.Bool(decrypt)}
91+
return f.parameterStore.GetParameterRequest(input).Send(ctx)
92+
}

awsEnv/env_test.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package awsEnv
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"net/http"
7+
"os"
8+
"testing"
9+
10+
"github.com/aws/aws-sdk-go-v2/aws"
11+
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
12+
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/secretsmanageriface"
13+
"github.com/aws/aws-sdk-go-v2/service/ssm"
14+
"github.com/aws/aws-sdk-go-v2/service/ssm/ssmiface"
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
type mockSecretManagerClient struct {
20+
secretsmanageriface.ClientAPI
21+
22+
checkInput func(*secretsmanager.GetSecretValueInput)
23+
stringValue *string
24+
binaryValue []byte
25+
}
26+
27+
func (m *mockSecretManagerClient) GetSecretValueRequest(in *secretsmanager.GetSecretValueInput) secretsmanager.GetSecretValueRequest {
28+
if m.checkInput != nil {
29+
m.checkInput(in)
30+
}
31+
32+
req := &aws.Request{
33+
Data: &secretsmanager.GetSecretValueOutput{
34+
SecretString: m.stringValue,
35+
SecretBinary: m.binaryValue,
36+
},
37+
HTTPRequest: new(http.Request),
38+
}
39+
return secretsmanager.GetSecretValueRequest{Request: req, Input: in, Copy: m.GetSecretValueRequest}
40+
}
41+
42+
type mockParameterStoreClient struct {
43+
ssmiface.ClientAPI
44+
45+
checkInput func(*ssm.GetParameterInput)
46+
stringValue *string
47+
binaryValue []byte
48+
}
49+
50+
func (m *mockParameterStoreClient) GetParameterRequest(in *ssm.GetParameterInput) ssm.GetParameterRequest {
51+
if m.checkInput != nil {
52+
m.checkInput(in)
53+
}
54+
55+
var value *string
56+
57+
if m.stringValue != nil {
58+
value = m.stringValue
59+
} else if m.binaryValue != nil {
60+
value = aws.String(base64.StdEncoding.EncodeToString(m.binaryValue))
61+
}
62+
63+
req := &aws.Request{
64+
Data: &ssm.GetParameterOutput{
65+
Parameter: &ssm.Parameter{
66+
Value: value,
67+
},
68+
},
69+
HTTPRequest: new(http.Request),
70+
}
71+
return ssm.GetParameterRequest{Request: req, Input: in, Copy: m.GetParameterRequest}
72+
}
73+
74+
func TestFig_GetEnv(t *testing.T) {
75+
t.Run("NonPrefixedValues", func(t *testing.T) {
76+
fig := &Fig{}
77+
ctx := context.Background()
78+
79+
require.NoError(t, os.Setenv("FOO_1", "bar"))
80+
require.NoError(t, os.Setenv("FOO_BAR_BAZ", "test"))
81+
82+
defer os.Unsetenv("FOO_1")
83+
defer os.Unsetenv("FOO_BAR_BAZ")
84+
85+
assert.Equal(t, "bar", fig.GetEnv(ctx, "FOO_1"))
86+
assert.Equal(t, "test", fig.GetEnv(ctx, "FOO_BAR_BAZ"))
87+
})
88+
89+
t.Run("SecretsManager", func(t *testing.T) {
90+
manager := &mockSecretManagerClient{}
91+
92+
fig := &Fig{
93+
DecryptParameterStoreValues: true,
94+
secretsManager: manager,
95+
}
96+
ctx := context.Background()
97+
98+
t.Run("String", func(t *testing.T) {
99+
t.Run("Simple", func(t *testing.T) {
100+
require.NoError(t, os.Setenv("foo", "sm://foo_bar"))
101+
defer os.Unsetenv("foo")
102+
103+
manager.checkInput = func(input *secretsmanager.GetSecretValueInput) {
104+
assert.Equal(t, "foo_bar", *input.SecretId)
105+
}
106+
manager.stringValue = aws.String("baz")
107+
108+
assert.Equal(t, "baz", fig.GetEnv(ctx, "foo"))
109+
})
110+
111+
// "complex" in the sense that this would break using strings.TrimPrefix(...)
112+
t.Run("Complex", func(t *testing.T) {
113+
require.NoError(t, os.Setenv("foo", "sm://small_foo_bar"))
114+
defer os.Unsetenv("foo")
115+
116+
manager.checkInput = func(input *secretsmanager.GetSecretValueInput) {
117+
assert.Equal(t, "small_foo_bar", *input.SecretId)
118+
}
119+
manager.stringValue = aws.String("baz")
120+
121+
assert.Equal(t, "baz", fig.GetEnv(ctx, "foo"))
122+
})
123+
})
124+
})
125+
126+
t.Run("ParameterStore", func(t *testing.T) {
127+
storeClient := &mockParameterStoreClient{}
128+
129+
fig := &Fig{
130+
DecryptParameterStoreValues: true,
131+
parameterStore: storeClient,
132+
}
133+
ctx := context.Background()
134+
135+
t.Run("String", func(t *testing.T) {
136+
t.Run("Simple", func(t *testing.T) {
137+
require.NoError(t, os.Setenv("foo", "ssm://foo_bar"))
138+
defer os.Unsetenv("foo")
139+
140+
storeClient.checkInput = func(input *ssm.GetParameterInput) {
141+
assert.Equal(t, "foo_bar", *input.Name)
142+
assert.True(t, *input.WithDecryption)
143+
}
144+
storeClient.stringValue = aws.String("baz")
145+
146+
assert.Equal(t, "baz", fig.GetEnv(ctx, "foo"))
147+
})
148+
149+
// "complex" in the sense that this would break using strings.TrimPrefix(...)
150+
t.Run("Complex", func(t *testing.T) {
151+
require.NoError(t, os.Setenv("foo", "ssm://ssmall_foo_bar"))
152+
defer os.Unsetenv("foo")
153+
154+
storeClient.checkInput = func(input *ssm.GetParameterInput) {
155+
assert.Equal(t, "ssmall_foo_bar", *input.Name)
156+
assert.True(t, *input.WithDecryption)
157+
}
158+
storeClient.stringValue = aws.String("baz")
159+
160+
assert.Equal(t, "baz", fig.GetEnv(ctx, "foo"))
161+
})
162+
})
163+
})
164+
}

awsEnv/go.mod

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module github.com/hookactions/fig/env
2+
3+
go 1.12
4+
5+
require (
6+
github.com/aws/aws-sdk-go-v2 v0.9.0
7+
github.com/pkg/errors v0.8.1
8+
github.com/stretchr/testify v1.2.2
9+
)

awsEnv/go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
github.com/aws/aws-sdk-go-v2 v0.9.0 h1:dWtJKGRFv3UZkMBQaIzMsF0/y4ge3iQPWTzeC4r/vl4=
2+
github.com/aws/aws-sdk-go-v2 v0.9.0/go.mod h1:sa1GePZ/LfBGI4dSq30f6uR4Tthll8axxtEPvlpXZ8U=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
6+
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
7+
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
8+
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
9+
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
10+
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
11+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
12+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
13+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14+
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
15+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
16+
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
17+
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
18+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
19+
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

0 commit comments

Comments
 (0)