diff --git a/template/funcs.go b/template/funcs.go index 13cd2e344..9926c1bac 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -117,5 +117,25 @@ func GetAWSSecret(name, key string) (string, error) { Key: key, } - return client.GetSecret(spec) + return client.GetSecret(spec, false) +} + +func GetRawAWSSecret(name string) (string, error) { + if len(name) == 0 { + return "", errors.New("At least one secret name must be provided") + } + + // client uses AWS SDK CredentialChain method. So,credentials can + // be loaded from credential file, environment variables, or IAM + // roles. + client := awssmapi.New( + &awssmapi.AWSConfig{}, + ) + + spec := &awssmapi.SecretSpec{ + Name: name, + Key: "", + } + + return client.GetSecret(spec, true) } diff --git a/template/interpolate/aws/secretsmanager/secretsmanager.go b/template/interpolate/aws/secretsmanager/secretsmanager.go index 9fd0d18f3..1f7e3a379 100644 --- a/template/interpolate/aws/secretsmanager/secretsmanager.go +++ b/template/interpolate/aws/secretsmanager/secretsmanager.go @@ -52,7 +52,7 @@ func (c *Client) newSession(config *AWSConfig) *session.Session { // GetSecret return an AWS Secret Manager secret // in plain text from a given secret name -func (c *Client) GetSecret(spec *SecretSpec) (string, error) { +func (c *Client) GetSecret(spec *SecretSpec, raw bool) (string, error) { params := &secretsmanager.GetSecretValueInput{ SecretId: aws.String(spec.Name), VersionStage: aws.String("AWSCURRENT"), @@ -71,7 +71,7 @@ func (c *Client) GetSecret(spec *SecretSpec) (string, error) { Name: *resp.Name, SecretString: *resp.SecretString, } - value, err := getSecretValue(&secret, spec) + value, err := getSecretValue(&secret, spec, raw) if err != nil { return "", err } @@ -79,12 +79,12 @@ func (c *Client) GetSecret(spec *SecretSpec) (string, error) { return value, nil } -func getSecretValue(s *SecretString, spec *SecretSpec) (string, error) { +func getSecretValue(s *SecretString, spec *SecretSpec, raw bool) (string, error) { var secretValue map[string]interface{} blob := []byte(s.SecretString) - //For those plaintext secrets just return the value - if !json.Valid(blob) { + //For those plaintext secrets just return the value or if raw is requested + if !json.Valid(blob) || raw { return s.SecretString, nil } diff --git a/template/interpolate/aws/secretsmanager/secretsmanager_test.go b/template/interpolate/aws/secretsmanager/secretsmanager_test.go index a2d872a5a..9fd88dd04 100644 --- a/template/interpolate/aws/secretsmanager/secretsmanager_test.go +++ b/template/interpolate/aws/secretsmanager/secretsmanager_test.go @@ -25,6 +25,7 @@ func TestGetSecret(t *testing.T) { testCases := []struct { description string arg *SecretSpec + raw bool mock secretsmanager.GetSecretValueOutput want string ok bool @@ -32,6 +33,7 @@ func TestGetSecret(t *testing.T) { { description: "input has valid secret name, secret has single key", arg: &SecretSpec{Name: "test/secret"}, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"key": "test"}`), @@ -45,6 +47,7 @@ func TestGetSecret(t *testing.T) { Name: "test/secret", Key: "key", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"key": "test"}`), @@ -58,6 +61,7 @@ func TestGetSecret(t *testing.T) { Name: "test/secret", Key: "second_key", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"first_key": "first_val", "second_key": "second_val"}`), @@ -70,6 +74,7 @@ func TestGetSecret(t *testing.T) { arg: &SecretSpec{ Name: "test/secret", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"first_key": "first_val", "second_key": "second_val"}`), @@ -82,6 +87,7 @@ func TestGetSecret(t *testing.T) { Name: "test/secret", Key: "nonexistent", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"key": "test"}`), @@ -94,6 +100,7 @@ func TestGetSecret(t *testing.T) { Name: "test/secret", Key: "nonexistent", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test/secret"), SecretString: aws.String(`{"first_key": "first_val", "second_key": "second_val"}`), @@ -106,6 +113,7 @@ func TestGetSecret(t *testing.T) { Name: "test/secret", Key: "nonexistent", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{}, ok: false, }, @@ -114,6 +122,7 @@ func TestGetSecret(t *testing.T) { arg: &SecretSpec{ Name: "test", }, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test"), SecretString: aws.String("ThisIsThePassword"), @@ -124,6 +133,7 @@ func TestGetSecret(t *testing.T) { { description: "input as secret stored with 'String: int' value", arg: &SecretSpec{Name: "test"}, + raw: false, mock: secretsmanager.GetSecretValueOutput{ Name: aws.String("test"), SecretString: aws.String(`{"port": 5432}`), @@ -131,13 +141,34 @@ func TestGetSecret(t *testing.T) { want: "5432", ok: true, }, + { + description: "input as secret stored as json object, returned as json", + arg: &SecretSpec{Name: "test"}, + raw: true, + mock: secretsmanager.GetSecretValueOutput{ + Name: aws.String("test"), + SecretString: aws.String(`{"foo":{"bar":"baz"}}`), + }, + want: `{"foo":{"bar":"baz"}}`, + ok: true, + }, + { + description: "input as secret stored as json with object, fails without raw", + arg: &SecretSpec{Name: "test"}, + raw: false, + mock: secretsmanager.GetSecretValueOutput{ + Name: aws.String("test"), + SecretString: aws.String(`{"foo":{"bar":"baz"}}`), + }, + ok: false, + }, } for _, test := range testCases { c := &Client{ api: mockedSecret{Resp: test.mock}, } - got, err := c.GetSecret(test.arg) + got, err := c.GetSecret(test.arg, test.raw) if test.ok { if got != test.want { t.Fatalf("want %v, got %v, error %v, using arg %v", test.want, got, err, test.arg)