Browse Source

Included metadataPolicy: Fetch in AWS ParameterStore (#2069)

* metadata fetch now working in parameterstore

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* Little refactory and some tests added

Tags from secretmanager and from parameterstore are not the same structure, thus, the function TagsToJSONString has now two versions (SecretTagsToSJONString & ParametersTagsToJSONString)

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* New test cases

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* Refactored to lift some code smells

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* Constant for error message added (code smell)

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* L&F

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* Lint issue

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>

* fix: fmt

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

---------

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>
Signed-off-by: Sebastián Gómez <1637983+sebagomez@users.noreply.github.com>
Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Co-authored-by: Moritz Johner <beller.moritz@googlemail.com>
Sebastián Gómez 3 years ago
parent
commit
da9689ccfd

+ 38 - 4
pkg/provider/aws/parameterstore/parameterstore.go

@@ -324,10 +324,13 @@ func (pm *ParameterStore) fetchAndSet(ctx context.Context, data map[string][]byt
 
 // GetSecret returns a single secret from the provider.
 func (pm *ParameterStore) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
-		Name:           &ref.Key,
-		WithDecryption: aws.Bool(true),
-	})
+	var out *ssm.GetParameterOutput
+	var err error
+	if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+		out, err = pm.getParameterTags(ctx, ref)
+	} else {
+		out, err = pm.getParameterValue(ctx, ref)
+	}
 	metrics.ObserveAPICall(metrics.ProviderAWSPS, metrics.CallAWSPSGetParameter, err)
 	nsf := esv1beta1.NoSecretError{}
 	var nf *ssm.ParameterNotFound
@@ -358,6 +361,37 @@ func (pm *ParameterStore) GetSecret(ctx context.Context, ref esv1beta1.ExternalS
 	return []byte(val.String()), nil
 }
 
+func (pm *ParameterStore) getParameterTags(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
+	param := ssm.GetParameterOutput{
+		Parameter: &ssm.Parameter{
+			Name: &ref.Key,
+		},
+	}
+	tags, err := pm.getTagsByName(ctx, &param)
+	if err != nil {
+		return nil, err
+	}
+	json, err := util.ParameterTagsToJSONString(tags)
+	if err != nil {
+		return nil, err
+	}
+	out := &ssm.GetParameterOutput{
+		Parameter: &ssm.Parameter{
+			Value: &json,
+		},
+	}
+	return out, nil
+}
+
+func (pm *ParameterStore) getParameterValue(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*ssm.GetParameterOutput, error) {
+	out, err := pm.client.GetParameterWithContext(ctx, &ssm.GetParameterInput{
+		Name:           &ref.Key,
+		WithDecryption: aws.Bool(true),
+	})
+
+	return out, err
+}
+
 // GetSecretMap returns multiple k/v pairs from the provider.
 func (pm *ParameterStore) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	data, err := pm.GetSecret(ctx, ref)

+ 61 - 2
pkg/provider/aws/parameterstore/parameterstore_test.go

@@ -28,6 +28,12 @@ import (
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	fakeps "github.com/external-secrets/external-secrets/pkg/provider/aws/parameterstore/fake"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
+)
+
+const (
+	errInvalidProperty = "key INVALPROP does not exist in secret"
+	invalidProp        = "INVALPROP"
 )
 
 type parameterstoreTestCase struct {
@@ -481,8 +487,8 @@ func TestGetSecret(t *testing.T) {
 	// bad case: extract property failure due to invalid json
 	setPropertyFail := func(pstc *parameterstoreTestCase) {
 		pstc.apiOutput.Parameter.Value = aws.String(`------`)
-		pstc.remoteRef.Property = "INVALPROP"
-		pstc.expectError = "key INVALPROP does not exist in secret"
+		pstc.remoteRef.Property = invalidProp
+		pstc.expectError = errInvalidProperty
 	}
 
 	// bad case: parameter.Value may be nil but binary is set
@@ -498,6 +504,38 @@ func TestGetSecret(t *testing.T) {
 		pstc.expectError = "oh no"
 	}
 
+	// good case: metadata returned
+	setMetadataString := func(pstc *parameterstoreTestCase) {
+		pstc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		output := ssm.ListTagsForResourceOutput{
+			TagList: getTagSlice(),
+		}
+		pstc.fakeClient.ListTagsForResourceWithContextFn = fakeps.NewListTagsForResourceWithContextFn(&output, nil)
+		pstc.expectedSecret, _ = util.ParameterTagsToJSONString(getTagSlice())
+	}
+
+	// good case: metadata property returned
+	setMetadataProperty := func(pstc *parameterstoreTestCase) {
+		pstc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		output := ssm.ListTagsForResourceOutput{
+			TagList: getTagSlice(),
+		}
+		pstc.fakeClient.ListTagsForResourceWithContextFn = fakeps.NewListTagsForResourceWithContextFn(&output, nil)
+		pstc.remoteRef.Property = "tagname2"
+		pstc.expectedSecret = "tagvalue2"
+	}
+
+	// bad case: metadata property not found
+	setMetadataMissingProperty := func(pstc *parameterstoreTestCase) {
+		pstc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		output := ssm.ListTagsForResourceOutput{
+			TagList: getTagSlice(),
+		}
+		pstc.fakeClient.ListTagsForResourceWithContextFn = fakeps.NewListTagsForResourceWithContextFn(&output, nil)
+		pstc.remoteRef.Property = invalidProp
+		pstc.expectError = errInvalidProperty
+	}
+
 	successCases := []*parameterstoreTestCase{
 		makeValidParameterStoreTestCaseCustom(setSecretString),
 		makeValidParameterStoreTestCaseCustom(setExtractProperty),
@@ -507,6 +545,9 @@ func TestGetSecret(t *testing.T) {
 		makeValidParameterStoreTestCaseCustom(setAPIError),
 		makeValidParameterStoreTestCaseCustom(setExtractPropertyWithDot),
 		makeValidParameterStoreTestCaseCustom(setParameterValueNotFound),
+		makeValidParameterStoreTestCaseCustom(setMetadataString),
+		makeValidParameterStoreTestCaseCustom(setMetadataProperty),
+		makeValidParameterStoreTestCaseCustom(setMetadataMissingProperty),
 	}
 
 	ps := ParameterStore{}
@@ -595,3 +636,21 @@ func ErrorContains(out error, want string) bool {
 	}
 	return strings.Contains(out.Error(), want)
 }
+
+func getTagSlice() []*ssm.Tag {
+	tagKey1 := "tagname1"
+	tagValue1 := "tagvalue1"
+	tagKey2 := "tagname2"
+	tagValue2 := "tagvalue2"
+
+	return []*ssm.Tag{
+		{
+			Key:   &tagKey1,
+			Value: &tagValue1,
+		},
+		{
+			Key:   &tagKey2,
+			Value: &tagValue2,
+		},
+	}
+}

+ 1 - 15
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -110,7 +110,7 @@ func (sm *SecretsManager) fetch(ctx context.Context, ref esv1beta1.ExternalSecre
 		}
 		log.Info("found metadata secret", "key", ref.Key, "output", descOutput)
 
-		jsonTags, err := TagsToJSONString(descOutput.Tags)
+		jsonTags, err := util.SecretTagsToJSONString(descOutput.Tags)
 		if err != nil {
 			return nil, err
 		}
@@ -150,20 +150,6 @@ func (sm *SecretsManager) fetch(ctx context.Context, ref esv1beta1.ExternalSecre
 	return secretOut, nil
 }
 
-func TagsToJSONString(tags []*awssm.Tag) (string, error) {
-	tagMap := make(map[string]string, len(tags))
-	for _, tag := range tags {
-		tagMap[*tag.Key] = *tag.Value
-	}
-
-	byteArr, err := json.Marshal(tagMap)
-	if err != nil {
-		return "", err
-	}
-
-	return string(byteArr), nil
-}
-
 func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
 	secretName := remoteRef.GetRemoteKey()
 	secretValue := awssm.GetSecretValueInput{

+ 4 - 3
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -29,6 +29,7 @@ import (
 
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	fakesm "github.com/external-secrets/external-secrets/pkg/provider/aws/secretsmanager/fake"
+	"github.com/external-secrets/external-secrets/pkg/provider/aws/util"
 )
 
 type secretsManagerTestCase struct {
@@ -187,7 +188,7 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 			Tags: getTagSlice(),
 		}
 		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
-		jsonTags, _ := TagsToJSONString(getTagSlice())
+		jsonTags, _ := util.SecretTagsToJSONString(getTagSlice())
 		smtc.apiOutput.SecretString = &jsonTags
 		smtc.expectedSecret = jsonTags
 	}
@@ -199,7 +200,7 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		}
 		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
 		smtc.remoteRef.Property = tagname2
-		jsonTags, _ := TagsToJSONString(getTagSlice())
+		jsonTags, _ := util.SecretTagsToJSONString(getTagSlice())
 		smtc.apiOutput.SecretString = &jsonTags
 		smtc.expectedSecret = tagvalue2
 	}
@@ -211,7 +212,7 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		}
 		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
 		smtc.remoteRef.Property = "fail"
-		jsonTags, _ := TagsToJSONString(getTagSlice())
+		jsonTags, _ := util.SecretTagsToJSONString(getTagSlice())
 		smtc.apiOutput.SecretString = &jsonTags
 		smtc.expectError = "key fail does not exist in secret /baz"
 	}

+ 32 - 0
pkg/provider/aws/util/provider.go

@@ -14,8 +14,12 @@ limitations under the License.
 package util
 
 import (
+	"encoding/json"
 	"fmt"
 
+	awssm "github.com/aws/aws-sdk-go/service/secretsmanager"
+	"github.com/aws/aws-sdk-go/service/ssm"
+
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 )
 
@@ -59,3 +63,31 @@ func IsReferentSpec(prov esv1beta1.AWSAuth) bool {
 
 	return false
 }
+
+func SecretTagsToJSONString(tags []*awssm.Tag) (string, error) {
+	tagMap := make(map[string]string, len(tags))
+	for _, tag := range tags {
+		tagMap[*tag.Key] = *tag.Value
+	}
+
+	byteArr, err := json.Marshal(tagMap)
+	if err != nil {
+		return "", err
+	}
+
+	return string(byteArr), nil
+}
+
+func ParameterTagsToJSONString(tags []*ssm.Tag) (string, error) {
+	tagMap := make(map[string]string, len(tags))
+	for _, tag := range tags {
+		tagMap[*tag.Key] = *tag.Value
+	}
+
+	byteArr, err := json.Marshal(tagMap)
+	if err != nil {
+		return "", err
+	}
+
+	return string(byteArr), nil
+}