This repository has been archived by the owner on Aug 22, 2019. It is now read-only.
forked from JeremyLoy/config
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add AWS support * tests and such
- Loading branch information
Showing
11 changed files
with
368 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Golang CircleCI 2.0 configuration file | ||
# | ||
# Check https://circleci.com/docs/2.0/language-go/ for more details | ||
version: 2 | ||
jobs: | ||
build: | ||
docker: | ||
# specify the version | ||
- image: circleci/golang:1.12 | ||
working_directory: /go/src/github.com/hookactions/config | ||
environment: # environment variables for the build itself | ||
GO111MODULE: 'on' | ||
steps: | ||
- checkout | ||
- run: make test |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
test: | ||
go get golang.org/x/tools/cmd/goimports | ||
go get -u golang.org/x/lint/golint | ||
|
||
go vet ./... | ||
|
||
golint -set_exit_status | ||
test -z "$(goimports -l .)" | ||
|
||
go test -v ./... -race -cover | ||
|
||
go mod tidy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package config | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/aws/external" | ||
"github.com/aws/aws-sdk-go-v2/service/secretsmanager" | ||
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/secretsmanageriface" | ||
"github.com/aws/aws-sdk-go-v2/service/ssm" | ||
"github.com/aws/aws-sdk-go-v2/service/ssm/ssmiface" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
var ( | ||
secretsManagerStringRe = regexp.MustCompile("^sm://") | ||
parameterStoreStringRe = regexp.MustCompile("^ssm://") | ||
) | ||
|
||
func checkPrefixAndStrip(re *regexp.Regexp, s string) (string, bool) { | ||
if re.MatchString(s) { | ||
return re.ReplaceAllString(s, ""), true | ||
} | ||
return s, false | ||
} | ||
|
||
// NewAWSSecretManagerValuePreProcessor creates a new AWSSecretManagerValuePreProcessor with the given context and whether to decrypt parameter store values or not. | ||
// This will load the aws config from external.LoadDefaultAWSConfig() | ||
func NewAWSSecretManagerValuePreProcessor(ctx context.Context, decryptParameterStoreValues bool) (*AWSSecretManagerValuePreProcessor, error) { | ||
awsConfig, err := external.LoadDefaultAWSConfig() | ||
if err != nil { | ||
return nil, errors.Wrap(err, "config/aws: error loading default aws config") | ||
} | ||
|
||
return &AWSSecretManagerValuePreProcessor{ | ||
decryptParameterStoreValues: decryptParameterStoreValues, | ||
|
||
secretsManager: secretsmanager.New(awsConfig), | ||
parameterStore: ssm.New(awsConfig), | ||
ctx: ctx, | ||
}, nil | ||
} | ||
|
||
// AWSSecretManagerValuePreProcessor is a ValuePreProcessor for AWS. | ||
// Supports Secrets Manager and Parameter Store. | ||
type AWSSecretManagerValuePreProcessor struct { | ||
decryptParameterStoreValues bool | ||
|
||
secretsManager secretsmanageriface.ClientAPI | ||
parameterStore ssmiface.ClientAPI | ||
ctx context.Context | ||
} | ||
|
||
// PreProcessValue pre-processes a config key/value pair. | ||
func (p *AWSSecretManagerValuePreProcessor) PreProcessValue(key, value string) string { | ||
return p.processConfigItem(p.ctx, key, value) | ||
} | ||
|
||
func (p *AWSSecretManagerValuePreProcessor) processConfigItem(ctx context.Context, key string, value string) string { | ||
if v, ok := checkPrefixAndStrip(secretsManagerStringRe, value); ok { | ||
return p.loadStringValueFromSecretsManager(ctx, v) | ||
} else if v, ok := checkPrefixAndStrip(parameterStoreStringRe, v); ok { | ||
return p.loadStringValueFromParameterStore(ctx, v, p.decryptParameterStoreValues) | ||
} | ||
return value | ||
} | ||
|
||
func (p *AWSSecretManagerValuePreProcessor) loadStringValueFromSecretsManager(ctx context.Context, name string) string { | ||
resp, err := p.requestSecret(ctx, name) | ||
if err != nil { | ||
panic("config/aws/loadStringValueFromSecretsManager: error loading secret, " + err.Error()) | ||
} | ||
|
||
return *resp.SecretString | ||
} | ||
|
||
func (p *AWSSecretManagerValuePreProcessor) requestSecret(ctx context.Context, name string) (*secretsmanager.GetSecretValueResponse, error) { | ||
input := &secretsmanager.GetSecretValueInput{SecretId: aws.String(name)} | ||
return p.secretsManager.GetSecretValueRequest(input).Send(ctx) | ||
} | ||
|
||
func (p *AWSSecretManagerValuePreProcessor) loadStringValueFromParameterStore(ctx context.Context, name string, decrypt bool) string { | ||
resp, err := p.requestParameter(ctx, name, decrypt) | ||
if err != nil { | ||
panic("config/aws/loadStringValueFromParameterStore: error loading value, " + err.Error()) | ||
} | ||
|
||
return *resp.Parameter.Value | ||
} | ||
|
||
func (p *AWSSecretManagerValuePreProcessor) requestParameter(ctx context.Context, name string, decrypt bool) (*ssm.GetParameterResponse, error) { | ||
input := &ssm.GetParameterInput{Name: aws.String(name), WithDecryption: aws.Bool(decrypt)} | ||
return p.parameterStore.GetParameterRequest(input).Send(ctx) | ||
} | ||
|
||
// compile time assertion | ||
var _ ValuePreProcessor = (*AWSSecretManagerValuePreProcessor)(nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package config | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/service/secretsmanager" | ||
"github.com/aws/aws-sdk-go-v2/service/secretsmanager/secretsmanageriface" | ||
"github.com/aws/aws-sdk-go-v2/service/ssm" | ||
"github.com/aws/aws-sdk-go-v2/service/ssm/ssmiface" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type mockSecretManagerClient struct { | ||
secretsmanageriface.ClientAPI | ||
|
||
checkInput func(*secretsmanager.GetSecretValueInput) | ||
stringValue *string | ||
binaryValue []byte | ||
} | ||
|
||
func (m *mockSecretManagerClient) GetSecretValueRequest(in *secretsmanager.GetSecretValueInput) secretsmanager.GetSecretValueRequest { | ||
if m.checkInput != nil { | ||
m.checkInput(in) | ||
} | ||
|
||
req := &aws.Request{ | ||
Data: &secretsmanager.GetSecretValueOutput{ | ||
SecretString: m.stringValue, | ||
SecretBinary: m.binaryValue, | ||
}, | ||
HTTPRequest: new(http.Request), | ||
} | ||
return secretsmanager.GetSecretValueRequest{Request: req, Input: in, Copy: m.GetSecretValueRequest} | ||
} | ||
|
||
type mockParameterStoreClient struct { | ||
ssmiface.ClientAPI | ||
|
||
checkInput func(*ssm.GetParameterInput) | ||
stringValue *string | ||
binaryValue []byte | ||
} | ||
|
||
func (m *mockParameterStoreClient) GetParameterRequest(in *ssm.GetParameterInput) ssm.GetParameterRequest { | ||
if m.checkInput != nil { | ||
m.checkInput(in) | ||
} | ||
|
||
var value *string | ||
|
||
if m.stringValue != nil { | ||
value = m.stringValue | ||
} else if m.binaryValue != nil { | ||
value = aws.String(base64.StdEncoding.EncodeToString(m.binaryValue)) | ||
} | ||
|
||
req := &aws.Request{ | ||
Data: &ssm.GetParameterOutput{ | ||
Parameter: &ssm.Parameter{ | ||
Value: value, | ||
}, | ||
}, | ||
HTTPRequest: new(http.Request), | ||
} | ||
return ssm.GetParameterRequest{Request: req, Input: in, Copy: m.GetParameterRequest} | ||
} | ||
|
||
func TestAWSSecretManagerValuePreProcessor_PreProcessValue(t *testing.T) { | ||
ctx := context.Background() | ||
|
||
t.Run("NonPrefixedValues", func(t *testing.T) { | ||
p := AWSSecretManagerValuePreProcessor{} | ||
|
||
assert.Equal(t, "bar", p.PreProcessValue("FOO_1", "bar")) | ||
assert.Equal(t, "test", p.PreProcessValue("FOO_BAR_BAZ", "test")) | ||
}) | ||
|
||
t.Run("SecretsManager", func(t *testing.T) { | ||
manager := &mockSecretManagerClient{} | ||
|
||
p := &AWSSecretManagerValuePreProcessor{ | ||
decryptParameterStoreValues: true, | ||
secretsManager: manager, | ||
ctx: ctx, | ||
} | ||
|
||
t.Run("Simple", func(t *testing.T) { | ||
manager.checkInput = func(input *secretsmanager.GetSecretValueInput) { | ||
assert.Equal(t, "foo_bar", *input.SecretId) | ||
} | ||
manager.stringValue = aws.String("baz") | ||
|
||
assert.Equal(t, "baz", p.PreProcessValue("FOO", "sm://foo_bar")) | ||
}) | ||
|
||
// "complex" in the sense that this would break using strings.TrimPrefix(...) | ||
t.Run("Complex", func(t *testing.T) { | ||
manager.checkInput = func(input *secretsmanager.GetSecretValueInput) { | ||
assert.Equal(t, "small_foo_bar", *input.SecretId) | ||
} | ||
manager.stringValue = aws.String("baz") | ||
|
||
assert.Equal(t, "baz", p.PreProcessValue("FOO", "sm://small_foo_bar")) | ||
}) | ||
}) | ||
|
||
t.Run("ParameterStore", func(t *testing.T) { | ||
storeClient := &mockParameterStoreClient{} | ||
|
||
p := &AWSSecretManagerValuePreProcessor{ | ||
decryptParameterStoreValues: true, | ||
parameterStore: storeClient, | ||
ctx: ctx, | ||
} | ||
|
||
t.Run("Simple", func(t *testing.T) { | ||
storeClient.checkInput = func(input *ssm.GetParameterInput) { | ||
assert.Equal(t, "foo_bar", *input.Name) | ||
assert.True(t, *input.WithDecryption) | ||
} | ||
storeClient.stringValue = aws.String("baz") | ||
|
||
assert.Equal(t, "baz", p.PreProcessValue("FOO", "ssm://foo_bar")) | ||
}) | ||
|
||
// "complex" in the sense that this would break using strings.TrimPrefix(...) | ||
t.Run("Complex", func(t *testing.T) { | ||
storeClient.checkInput = func(input *ssm.GetParameterInput) { | ||
assert.Equal(t, "ssmall_foo_bar", *input.Name) | ||
assert.True(t, *input.WithDecryption) | ||
} | ||
storeClient.stringValue = aws.String("baz") | ||
|
||
assert.Equal(t, "baz", p.PreProcessValue("FOO", "ssm://ssmall_foo_bar")) | ||
}) | ||
}) | ||
} |
Oops, something went wrong.