Browse Source

azkv tag feature

Cristina DE DIOS GONZÁLEZ 4 years ago
parent
commit
3256bc4b82

+ 14 - 2
apis/externalsecrets/v1beta1/externalsecret_types.go

@@ -166,20 +166,31 @@ type ExternalSecretDataRemoteRef struct {
 	// Key is the key used in the Provider, mandatory
 	Key string `json:"key"`
 
-	// Used to select a specific version of the Provider value, if supported
 	// +optional
-	Version string `json:"version,omitempty"`
+	// Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+	MetadataPolicy ExternalSecretMetadataPolicy `json:"metadataPolicy,omitempty"`
 
 	// +optional
 	// Used to select a specific property of the Provider value (if a map), if supported
 	Property string `json:"property,omitempty"`
 
 	// +optional
+	// Used to select a specific version of the Provider value, if supported
+	Version string `json:"version,omitempty"`
+
+	// +optional
 	// Used to define a conversion Strategy
 	// +kubebuilder:default="Default"
 	ConversionStrategy ExternalSecretConversionStrategy `json:"conversionStrategy,omitempty"`
 }
 
+type ExternalSecretMetadataPolicy string
+
+const (
+	ExternalSecretMetadataPolicyNone  ExternalSecretMetadataPolicy = "None"
+	ExternalSecretMetadataPolicyFetch ExternalSecretMetadataPolicy = "Fetch"
+)
+
 type ExternalSecretConversionStrategy string
 
 const (
@@ -209,6 +220,7 @@ type ExternalSecretFind struct {
 	// Find secrets based on tags.
 	// +optional
 	Tags map[string]string `json:"tags,omitempty"`
+
 	// +optional
 	// Used to define a conversion Strategy
 	// +kubebuilder:default="Default"

+ 10 - 0
config/crds/bases/external-secrets.io_clusterexternalsecrets.yaml

@@ -65,6 +65,11 @@ spec:
                             key:
                               description: Key is the key used in the Provider, mandatory
                               type: string
+                            metadataPolicy:
+                              description: Policy for fetching tags/labels from provider
+                                secrets, possible options are Fetch, None. Defaults
+                                to None
+                              type: string
                             property:
                               description: Used to select a specific property of the
                                 Provider value (if a map), if supported
@@ -102,6 +107,11 @@ spec:
                             key:
                               description: Key is the key used in the Provider, mandatory
                               type: string
+                            metadataPolicy:
+                              description: Policy for fetching tags/labels from provider
+                                secrets, possible options are Fetch, None. Defaults
+                                to None
+                              type: string
                             property:
                               description: Used to select a specific property of the
                                 Provider value (if a map), if supported

+ 10 - 0
config/crds/bases/external-secrets.io_externalsecrets.yaml

@@ -310,6 +310,11 @@ spec:
                         key:
                           description: Key is the key used in the Provider, mandatory
                           type: string
+                        metadataPolicy:
+                          description: Policy for fetching tags/labels from provider
+                            secrets, possible options are Fetch, None. Defaults to
+                            None
+                          type: string
                         property:
                           description: Used to select a specific property of the Provider
                             value (if a map), if supported
@@ -347,6 +352,11 @@ spec:
                         key:
                           description: Key is the key used in the Provider, mandatory
                           type: string
+                        metadataPolicy:
+                          description: Policy for fetching tags/labels from provider
+                            secrets, possible options are Fetch, None. Defaults to
+                            None
+                          type: string
                         property:
                           description: Used to select a specific property of the Provider
                             value (if a map), if supported

+ 12 - 0
deploy/crds/bundle.yaml

@@ -55,6 +55,9 @@ spec:
                               key:
                                 description: Key is the key used in the Provider, mandatory
                                 type: string
+                              metadataPolicy:
+                                description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                                type: string
                               property:
                                 description: Used to select a specific property of the Provider value (if a map), if supported
                                 type: string
@@ -87,6 +90,9 @@ spec:
                               key:
                                 description: Key is the key used in the Provider, mandatory
                                 type: string
+                              metadataPolicy:
+                                description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                                type: string
                               property:
                                 description: Used to select a specific property of the Provider value (if a map), if supported
                                 type: string
@@ -2651,6 +2657,9 @@ spec:
                           key:
                             description: Key is the key used in the Provider, mandatory
                             type: string
+                          metadataPolicy:
+                            description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                            type: string
                           property:
                             description: Used to select a specific property of the Provider value (if a map), if supported
                             type: string
@@ -2683,6 +2692,9 @@ spec:
                           key:
                             description: Key is the key used in the Provider, mandatory
                             type: string
+                          metadataPolicy:
+                            description: Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None
+                            type: string
                           property:
                             description: Used to select a specific property of the Provider value (if a map), if supported
                             type: string

+ 5 - 2
docs/provider-azure-key-vault.md

@@ -96,13 +96,16 @@ To create a kubernetes secret from the Azure Key vault secret a `Kind=ExternalSe
 
 You can manage keys/secrets/certificates saved inside the keyvault , by setting a "/" prefixed type in the secret name , the default type is a `secret`. other supported values are `cert` and `key`
 
-to select all secrets inside the key vault , you can use the `dataFrom` directive
+to select all secrets inside the key vault or all tags inside a secret, you can use the `dataFrom` directive
 
 ```yaml
 {% include 'azkv-external-secret.yaml' %}
 ```
+```yaml
+{% include 'azkv-datafrom-external-secret.yaml' %}
+```
 
 The operator will fetch the Azure Key vault secret and inject it as a `Kind=Secret`
 ```
 kubectl get secret secret-to-be-created -n <namespace> | -o jsonpath='{.data.dev-secret-test}' | base64 -d
-```
+```

+ 27 - 0
docs/snippets/azkv-datafrom-external-secret.yaml

@@ -0,0 +1,27 @@
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+  name: example
+spec:
+  refreshInterval: 1h           # rate SecretManager pulls Azure Key Vault
+  secretStoreRef:
+    kind: SecretStore
+    name: example               # name of the SecretStore (or kind specified)
+  target:
+    name: secret-to-be-created  # name of the k8s Secret to be created
+    creationPolicy: Owner
+  dataFrom:
+  - find:
+      name:
+        regexp: "^example"
+  - find:
+      tags:
+        author: seb
+        environment: dev
+  # secret value is in JSON format and we unmarshall it into multiple key/values in k8s secret
+  - extract: 
+      key: test
+  # get all tags and the tags in JSON format will be unmarshall 
+  - extract: 
+      key: test
+      metadataPolicy: Fetch

+ 14 - 2
docs/snippets/azkv-external-secret.yaml

@@ -23,6 +23,19 @@ spec:
     remoteRef:
       key: secret/dev-secret-test
 
+  # metadataPolicy to fetch all the tags in JSON format
+  - secretKey: dev-secret-test
+    remoteRef:
+      key: dev-secret-test
+      metadataPolicy: Fetch
+
+  # metadataPolicy to fetch a specific tag which name must be in property
+  - secretKey: dev-secret-test
+    remoteRef:
+      key: dev-secret-test
+      metadataPolicy: Fetch
+      property: tagname
+
   # type/name of certificate in the Azure KV
   # raw value will be returned, use templating features for data processing
   - secretKey: dev-cert-test
@@ -33,5 +46,4 @@ spec:
   # the key is returned PEM encoded
   - secretKey: dev-key-test
     remoteRef:
-      key: key/dev-key-test
-
+      key: key/dev-key-test

+ 74 - 18
pkg/provider/azure/keyvault/keyvault.go

@@ -56,6 +56,7 @@ const (
 	errUnexpectedStoreSpec   = "unexpected store spec"
 	errMissingAuthType       = "cannot initialize Azure Client: no valid authType was specified"
 	errPropNotExist          = "property %s does not exist in key %s"
+	errTagNotExist           = "tag %s does not exist"
 	errUnknownObjectType     = "unknown Azure Keyvault object Type for %s"
 	errUnmarshalJSONData     = "error unmarshalling json data: %w"
 	errDataFromCert          = "cannot get use dataFrom to get certificate secret"
@@ -233,55 +234,107 @@ func (a *Azure) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretF
 	return secretsMap, nil
 }
 
+// Retrieves a tag value if specified and all tags in JSON format if not.
+func getSecretTag(tags map[string]*string, property string) ([]byte, error) {
+	if property == "" {
+		secretTagsData := make(map[string]string)
+		for k, v := range tags {
+			secretTagsData[k] = *v
+		}
+		return json.Marshal(secretTagsData)
+	}
+	if val, exist := tags[property]; exist {
+		return []byte(*val), nil
+	}
+	return nil, fmt.Errorf(errTagNotExist, property)
+}
+
+// Retrieves a property value if specified and the secret value if not.
+func getProperty(secret, property, key string) ([]byte, error) {
+	if property == "" {
+		return []byte(secret), nil
+	}
+	res := gjson.Get(secret, property)
+	if !res.Exists() {
+		return nil, fmt.Errorf(errPropNotExist, property, key)
+	}
+	return []byte(res.String()), nil
+}
+
 // Implements store.Client.GetSecret Interface.
-// Retrieves a secret/Key/Certificate with the secret name defined in ref.Name
+// Retrieves a secret/Key/Certificate/Tag with the secret name defined in ref.Name
 // The Object Type is defined as a prefix in the ref.Name , if no prefix is defined , we assume a secret is required.
 func (a *Azure) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
-	version := ""
 	objectType, secretName := getObjType(ref)
 
-	if ref.Version != "" {
-		version = ref.Version
-	}
-
 	switch objectType {
 	case defaultObjType:
 		// returns a SecretBundle with the secret value
 		// https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#SecretBundle
-		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, version)
+		secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
-		if ref.Property == "" {
-			return []byte(*secretResp.Value), nil
-		}
-		res := gjson.Get(*secretResp.Value, ref.Property)
-		if !res.Exists() {
-			return nil, fmt.Errorf(errPropNotExist, ref.Property, ref.Key)
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(secretResp.Tags, ref.Property)
 		}
-		return []byte(res.String()), err
+		return getProperty(*secretResp.Value, ref.Property, ref.Key)
 	case objectTypeCert:
 		// returns a CertBundle. We return CER contents of x509 certificate
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#CertificateBundle
-		secretResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, version)
+		certResp, err := a.baseClient.GetCertificate(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
-		return *secretResp.Cer, nil
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(certResp.Tags, ref.Property)
+		}
+		return *certResp.Cer, nil
 	case objectTypeKey:
 		// returns a KeyBundle that contains a jwk
 		// azure kv returns only public keys
 		// see: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/services/keyvault/v7.0/keyvault#KeyBundle
-		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, version)
+		keyResp, err := a.baseClient.GetKey(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
 		if err != nil {
 			return nil, err
 		}
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return getSecretTag(keyResp.Tags, ref.Property)
+		}
 		return json.Marshal(keyResp.Key)
 	}
 
 	return nil, fmt.Errorf(errUnknownObjectType, secretName)
 }
 
+// returns a SecretBundle with the tags values.
+func (a *Azure) getSecretTags(ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+	_, secretName := getObjType(ref)
+	secretResp, err := a.baseClient.GetSecret(context.Background(), *a.provider.VaultURL, secretName, ref.Version)
+
+	if err != nil {
+		return nil, err
+	}
+
+	secretTagsData := make(map[string][]byte)
+
+	for tagname, tagval := range secretResp.Tags {
+		name := secretName + "_" + tagname
+		kv := make(map[string]string)
+		err = json.Unmarshal([]byte(*tagval), &kv)
+		// if the tagvalue is not in JSON format then we added to secretTagsData we added as it is
+		if err != nil {
+			secretTagsData[name] = []byte(*tagval)
+		} else {
+			for k, v := range kv {
+				keyName := name + "_" + k
+				secretTagsData[keyName] = []byte(v)
+			}
+		}
+	}
+	return secretTagsData, nil
+}
+
 // Implements store.Client.GetSecretMap Interface.
 // New version of GetSecretMap.
 func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
@@ -294,6 +347,10 @@ func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDa
 			return nil, err
 		}
 
+		if ref.MetadataPolicy == esv1beta1.ExternalSecretMetadataPolicyFetch {
+			return a.getSecretTags(ref)
+		}
+
 		kv := make(map[string]string)
 		err = json.Unmarshal(data, &kv)
 		if err != nil {
@@ -311,7 +368,6 @@ func (a *Azure) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDa
 	case objectTypeKey:
 		return nil, fmt.Errorf(errDataFromKey)
 	}
-
 	return nil, fmt.Errorf(errUnknownObjectType, secretName)
 }
 

+ 197 - 0
pkg/provider/azure/keyvault/keyvault_test.go

@@ -89,15 +89,34 @@ const (
 	jwkPubEC             = `{"kid":"https://example.vault.azure.net/keys/ec-p-521/e3d0e9c179b54988860c69c6ae172c65","kty":"EC","key_ops":["sign","verify"],"crv":"P-521","x":"AedOAtb7H7Oz1C_cPKI_R4CN_eai5nteY6KFW07FOoaqgQfVCSkQDK22fCOiMT_28c8LZYJRsiIFz_IIbQUW7bXj","y":"AOnchHnmBphIWXvanmMAmcCDkaED6ycW8GsAl9fQ43BMVZTqcTkJYn6vGnhn7MObizmkNSmgZYTwG-vZkIg03HHs"}`
 	jsonTestString       = `{"Name": "External", "LastName": "Secret", "Address": { "Street": "Myroad st.", "CP": "J4K4T4" } }`
 	jsonSingleTestString = `{"Name": "External", "LastName": "Secret" }`
+	jsonTagTestString    = `{"tagname":"tagvalue","tagname2":"tagvalue2"}`
 	keyName              = "key/keyname"
 	certName             = "cert/certname"
 	secretString         = "changedvalue"
 	unexpectedError      = "[%d] unexpected error: %s, expected: '%s'"
 	unexpectedSecretData = "[%d] unexpected secret data: expected %#v, got %#v"
+	errorNoTag           = "tag something does not exist"
+	something            = "something"
+	tagname              = "tagname"
+	tagname2             = "tagname2"
+	tagvalue             = "tagvalue"
+	tagvalue2            = "tagvalue2"
 	secretName           = "example-1"
+	testsecret           = "test-secret"
 	fakeURL              = "noop"
 )
 
+func getTagMap() map[string]*string {
+	tag1 := "tagname"
+	tag2 := "tagname2"
+	value1 := "tagvalue"
+	value2 := "tagvalue2"
+	tagMap := make(map[string]*string)
+	tagMap[tag1] = &value1
+	tagMap[tag2] = &value2
+	return tagMap
+}
+
 func newKVJWK(b []byte) *keyvault.JSONWebKey {
 	var key keyvault.JSONWebKey
 	err := json.Unmarshal(b, &key)
@@ -112,6 +131,7 @@ func newKVJWK(b []byte) *keyvault.JSONWebKey {
 func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 	secretString := "changedvalue"
 	secretCertificate := "certificate_value"
+	tagMap := getTagMap()
 
 	// good case
 	setSecretString := func(smtc *secretManagerTestCase) {
@@ -188,6 +208,136 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
 	}
 
+	setSecretWithTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagMap,
+		}
+		smtc.expectedSecret = tagvalue
+	}
+
+	badSecretWithTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setSecretWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagMap,
+		}
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setSecretWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{}
+		smtc.expectedSecret = "{}"
+	}
+
+	setCertWithTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString, Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = tagvalue
+		smtc.ref.Key = smtc.secretName
+	}
+
+	badCertWithTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setCertWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString, Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setCertWithNoTags := func(smtc *secretManagerTestCase) {
+		byteArrString := []byte(secretCertificate)
+		smtc.secretName = certName
+		smtc.ref.Key = smtc.secretName
+		smtc.certOutput = keyvault.CertificateBundle{
+			Cer: &byteArrString,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = "{}"
+	}
+
+	setKeyWithTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = tagvalue
+		smtc.ref.Key = smtc.secretName
+	}
+
+	badKeyWithTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.ref.Property = something
+		smtc.expectedSecret = ""
+		smtc.expectError = errorNoTag
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
+	setKeyWithNoSpecificTag := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)), Tags: tagMap,
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = jsonTagTestString
+	}
+
+	setKeyWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.secretName = keyName
+		smtc.ref.Key = smtc.secretName
+		smtc.keyOutput = keyvault.KeyBundle{
+			Key: newKVJWK([]byte(jwkPubRSA)),
+		}
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.expectedSecret = "{}"
+	}
+
+	badPropertyTag := func(smtc *secretManagerTestCase) {
+		smtc.ref.Property = tagname
+		smtc.expectedSecret = ""
+		smtc.expectError = "property tagname does not exist in key test-secret"
+		smtc.apiErr = errors.New(smtc.expectError)
+	}
+
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCase(),
 		makeValidSecretManagerTestCaseCustom(setSecretString),
@@ -198,6 +348,19 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(setPubECKey),
 		makeValidSecretManagerTestCaseCustom(setCertificate),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
+		makeValidSecretManagerTestCaseCustom(setSecretWithTag),
+		makeValidSecretManagerTestCaseCustom(badSecretWithTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoTags),
+		makeValidSecretManagerTestCaseCustom(setCertWithTag),
+		makeValidSecretManagerTestCaseCustom(badCertWithTag),
+		makeValidSecretManagerTestCaseCustom(setCertWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setCertWithNoTags),
+		makeValidSecretManagerTestCaseCustom(setKeyWithTag),
+		makeValidSecretManagerTestCaseCustom(badKeyWithTag),
+		makeValidSecretManagerTestCaseCustom(setKeyWithNoSpecificTag),
+		makeValidSecretManagerTestCaseCustom(setKeyWithNoTags),
+		makeValidSecretManagerTestCaseCustom(badPropertyTag),
 	}
 
 	sm := Azure{
@@ -218,6 +381,7 @@ func TestAzureKeyVaultSecretManagerGetSecret(t *testing.T) {
 func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 	secretString := "changedvalue"
 	secretCertificate := "certificate_value"
+	tagMap := getTagMap()
 
 	badSecretString := func(smtc *secretManagerTestCase) {
 		smtc.expectedSecret = secretString
@@ -286,6 +450,36 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 		smtc.ref.Key = fmt.Sprintf("dummy/%s", smtc.secretName)
 	}
 
+	setSecretTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Tags: tagMap,
+		}
+		smtc.expectedData[testsecret+"_"+tagname] = []byte(tagvalue)
+		smtc.expectedData[testsecret+"_"+tagname2] = []byte(tagvalue2)
+	}
+
+	setSecretWithJSONTag := func(smtc *secretManagerTestCase) {
+		tagJSONMap := make(map[string]*string)
+		tagJSONData := `{"keyname":"keyvalue","x":"y"}`
+		tagJSONMap["json"] = &tagJSONData
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		smtc.secretOutput = keyvault.SecretBundle{
+			Value: &secretString, Tags: tagJSONMap,
+		}
+		smtc.expectedData[testsecret+"_json_keyname"] = []byte("keyvalue")
+		smtc.expectedData[testsecret+"_json_x"] = []byte("y")
+	}
+
+	setSecretWithNoTags := func(smtc *secretManagerTestCase) {
+		smtc.ref.MetadataPolicy = esv1beta1.ExternalSecretMetadataPolicyFetch
+		tagMapTestEmpty := make(map[string]*string)
+		smtc.secretOutput = keyvault.SecretBundle{
+			Tags: tagMapTestEmpty,
+		}
+		smtc.expectedSecret = ""
+	}
+
 	successCases := []*secretManagerTestCase{
 		makeValidSecretManagerTestCaseCustom(badSecretString),
 		makeValidSecretManagerTestCaseCustom(setSecretJSON),
@@ -294,6 +488,9 @@ func TestAzureKeyVaultSecretManagerGetSecretMap(t *testing.T) {
 		makeValidSecretManagerTestCaseCustom(badPubRSAKey),
 		makeValidSecretManagerTestCaseCustom(badCertificate),
 		makeValidSecretManagerTestCaseCustom(badSecretType),
+		makeValidSecretManagerTestCaseCustom(setSecretTags),
+		makeValidSecretManagerTestCaseCustom(setSecretWithJSONTag),
+		makeValidSecretManagerTestCaseCustom(setSecretWithNoTags),
 	}
 
 	sm := Azure{