Browse Source

:sparkles: Add MetadataPolicy=Fetch for AWS Secret Manager (#2025)

* Get all the properties

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

* Add secrets to the cache

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

* First set of tests

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

* Last set of tests added

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

* Fixed lint issues

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

* Improved Tags to string mechanism

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

* Fix lint complain

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

---------

Signed-off-by: Sebastián Gómez <sebastiangomezcorrea@gmail.com>
Co-authored-by: Moritz Johner <moolen@users.noreply.github.com>
Sebastián Gómez 3 years ago
parent
commit
1cfca77b9b

+ 65 - 19
pkg/provider/aws/secretsmanager/secretsmanager.go

@@ -77,43 +77,89 @@ func New(sess *session.Session, cfg *aws.Config, referentAuth bool) (*SecretsMan
 	}, nil
 }
 
-func (sm *SecretsManager) fetch(_ context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
+func (sm *SecretsManager) fetch(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (*awssm.GetSecretValueOutput, error) {
 	ver := "AWSCURRENT"
+	valueFrom := "SECRET"
 	if ref.Version != "" {
 		ver = ref.Version
 	}
-	log.Info("fetching secret value", "key", ref.Key, "version", ver)
+	if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+		valueFrom = "TAG"
+	}
+
+	log.Info("fetching secret value", "key", ref.Key, "version", ver, "value", valueFrom)
 
-	cacheKey := fmt.Sprintf("%s#%s", ref.Key, ver)
+	cacheKey := fmt.Sprintf("%s#%s#%s", ref.Key, ver, valueFrom)
 	if secretOut, found := sm.cache[cacheKey]; found {
 		log.Info("found secret in cache", "key", ref.Key, "version", ver)
 		return secretOut, nil
 	}
 
-	var getSecretValueInput *awssm.GetSecretValueInput
-	if strings.HasPrefix(ver, "uuid/") {
-		versionID := strings.TrimPrefix(ver, "uuid/")
-		getSecretValueInput = &awssm.GetSecretValueInput{
-			SecretId:  &ref.Key,
-			VersionId: &versionID,
+	var secretOut *awssm.GetSecretValueOutput
+	var err error
+
+	if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+		describeSecretInput := &awssm.DescribeSecretInput{
+			SecretId: &ref.Key,
+		}
+
+		descOutput, err := sm.client.DescribeSecretWithContext(ctx, describeSecretInput)
+		if err != nil {
+			return nil, err
+		}
+		log.Info("found metadata secret", "key", ref.Key, "output", descOutput)
+
+		jsonTags, err := TagsToJSONString(descOutput.Tags)
+		if err != nil {
+			return nil, err
+		}
+		secretOut = &awssm.GetSecretValueOutput{
+			ARN:          descOutput.ARN,
+			CreatedDate:  descOutput.CreatedDate,
+			Name:         descOutput.Name,
+			SecretString: &jsonTags,
+			VersionId:    &ver,
 		}
 	} else {
-		getSecretValueInput = &awssm.GetSecretValueInput{
-			SecretId:     &ref.Key,
-			VersionStage: &ver,
+		var getSecretValueInput *awssm.GetSecretValueInput
+		if strings.HasPrefix(ver, "uuid/") {
+			versionID := strings.TrimPrefix(ver, "uuid/")
+			getSecretValueInput = &awssm.GetSecretValueInput{
+				SecretId:  &ref.Key,
+				VersionId: &versionID,
+			}
+		} else {
+			getSecretValueInput = &awssm.GetSecretValueInput{
+				SecretId:     &ref.Key,
+				VersionStage: &ver,
+			}
+		}
+		secretOut, err = sm.client.GetSecretValue(getSecretValueInput)
+		var nf *awssm.ResourceNotFoundException
+		if errors.As(err, &nf) {
+			return nil, esv1beta1.NoSecretErr
+		}
+		if err != nil {
+			return nil, err
 		}
 	}
-	secretOut, err := sm.client.GetSecretValue(getSecretValueInput)
-	var nf *awssm.ResourceNotFoundException
-	if errors.As(err, &nf) {
-		return nil, esv1beta1.NoSecretErr
+	sm.cache[cacheKey] = secretOut
+
+	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 nil, err
+		return "", err
 	}
-	sm.cache[cacheKey] = secretOut
 
-	return secretOut, nil
+	return string(byteArr), nil
 }
 
 func (sm *SecretsManager) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {

+ 62 - 0
pkg/provider/aws/secretsmanager/secretsmanager_test.go

@@ -46,6 +46,12 @@ type secretsManagerTestCase struct {
 }
 
 const unexpectedErrorString = "[%d] unexpected error: %s, expected: '%s'"
+const (
+	tagname1  = "tagname1"
+	tagvalue1 = "tagvalue1"
+	tagname2  = "tagname2"
+	tagvalue2 = "tagvalue2"
+)
 
 func makeValidSecretsManagerTestCase() *secretsManagerTestCase {
 	smtc := secretsManagerTestCase{
@@ -175,6 +181,41 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		smtc.expectedSecret = "myvalue"
 	}
 
+	fetchMetadata := func(smtc *secretsManagerTestCase) {
+		smtc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		describeSecretOutput := &awssm.DescribeSecretOutput{
+			Tags: getTagSlice(),
+		}
+		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
+		jsonTags, _ := TagsToJSONString(getTagSlice())
+		smtc.apiOutput.SecretString = &jsonTags
+		smtc.expectedSecret = jsonTags
+	}
+
+	fetchMetadataProperty := func(smtc *secretsManagerTestCase) {
+		smtc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		describeSecretOutput := &awssm.DescribeSecretOutput{
+			Tags: getTagSlice(),
+		}
+		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
+		smtc.remoteRef.Property = tagname2
+		jsonTags, _ := TagsToJSONString(getTagSlice())
+		smtc.apiOutput.SecretString = &jsonTags
+		smtc.expectedSecret = tagvalue2
+	}
+
+	failMetadataWrongProperty := func(smtc *secretsManagerTestCase) {
+		smtc.remoteRef.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		describeSecretOutput := &awssm.DescribeSecretOutput{
+			Tags: getTagSlice(),
+		}
+		smtc.fakeClient.DescribeSecretWithContextFn = fakesm.NewDescribeSecretWithContextFn(describeSecretOutput, nil)
+		smtc.remoteRef.Property = "fail"
+		jsonTags, _ := TagsToJSONString(getTagSlice())
+		smtc.apiOutput.SecretString = &jsonTags
+		smtc.expectError = "key fail does not exist in secret /baz"
+	}
+
 	successCases := []*secretsManagerTestCase{
 		makeValidSecretsManagerTestCase(),
 		makeValidSecretsManagerTestCaseCustom(setSecretString),
@@ -188,6 +229,9 @@ func TestSecretsManagerGetSecret(t *testing.T) {
 		makeValidSecretsManagerTestCaseCustom(setCustomVersionStage),
 		makeValidSecretsManagerTestCaseCustom(setCustomVersionID),
 		makeValidSecretsManagerTestCaseCustom(setAPIErr),
+		makeValidSecretsManagerTestCaseCustom(fetchMetadata),
+		makeValidSecretsManagerTestCaseCustom(fetchMetadataProperty),
+		makeValidSecretsManagerTestCaseCustom(failMetadataWrongProperty),
 	}
 
 	for k, v := range successCases {
@@ -700,3 +744,21 @@ func makeValidSecretStore() *esv1beta1.SecretStore {
 		},
 	}
 }
+
+func getTagSlice() []*awssm.Tag {
+	tagKey1 := tagname1
+	tagValue1 := tagvalue1
+	tagKey2 := tagname2
+	tagValue2 := tagvalue2
+
+	return []*awssm.Tag{
+		{
+			Key:   &tagKey1,
+			Value: &tagValue1,
+		},
+		{
+			Key:   &tagKey2,
+			Value: &tagValue2,
+		},
+	}
+}