Browse Source

feat: Allow to specify tags when pushing to Azure Key Vault (#4507)

* Set tags in azure key vault

Signed-off-by: twobiers <22715034+twobiers@users.noreply.github.com>

* Introduce a helper method to reduce cognitive complexity

Signed-off-by: twobiers <22715034+twobiers@users.noreply.github.com>

---------

Signed-off-by: twobiers <22715034+twobiers@users.noreply.github.com>
Tobi 1 year ago
parent
commit
eb74af30e0

+ 4 - 2
docs/snippets/azkv-pushsecret-secret.yaml

@@ -23,9 +23,11 @@ spec:
     - match:
         secretKey: source-key # Source Kubernetes secret key containing the secret
         remoteRef:
-          remoteKey: my-azkv-secret-name 
+          remoteKey: my-azkv-secret-name
       metadata:
         apiVersion: kubernetes.external-secrets.io/v1alpha1
         kind: PushSecretMetadata
         spec:
-          expirationDate: "2024-12-31T23:59:59Z" # Expiration date for the secret in Azure Key Vault
+          expirationDate: "2024-12-31T23:59:59Z" # Expiration date for the secret in Azure Key Vault
+          tags: # Tags to be added to the secret in Azure Key Vault
+            Content-Type: application/json

+ 49 - 19
pkg/provider/azure/keyvault/keyvault.go

@@ -124,7 +124,8 @@ type Azure struct {
 }
 
 type PushSecretMetadataSpec struct {
-	ExpirationDate string `json:"expirationDate,omitempty"`
+	ExpirationDate string            `json:"expirationDate,omitempty"`
+	Tags           map[string]string `json:"tags,omitempty"`
 }
 
 func init() {
@@ -419,7 +420,7 @@ func canCreate(tags map[string]*string, err error) (bool, error) {
 	return true, nil
 }
 
-func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value []byte, expires *date.UnixTime) error {
+func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value []byte, expires *date.UnixTime, tags map[string]string) error {
 	secret, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, "")
 	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVGetSecret, err)
 	ok, err := canCreate(secret.Tags, err)
@@ -449,6 +450,10 @@ func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value
 		},
 	}
 
+	for k, v := range tags {
+		secretParams.Tags[k] = &v
+	}
+
 	if expires != nil {
 		secretParams.SecretAttributes.Expires = expires
 	}
@@ -461,7 +466,7 @@ func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value
 	return nil
 }
 
-func (a *Azure) setKeyVaultCertificate(ctx context.Context, secretName string, value []byte) error {
+func (a *Azure) setKeyVaultCertificate(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
 	val := b64.StdEncoding.EncodeToString(value)
 	localCert, err := getCertificateFromValue(value)
 	if err != nil {
@@ -486,6 +491,11 @@ func (a *Azure) setKeyVaultCertificate(ctx context.Context, secretName string, v
 			managedBy: pointer.To(managerLabel),
 		},
 	}
+
+	for k, v := range tags {
+		params.Tags[k] = &v
+	}
+
 	_, err = a.baseClient.ImportCertificate(ctx, *a.provider.VaultURL, secretName, params)
 	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportCertificate, err)
 	if err != nil {
@@ -505,7 +515,7 @@ func equalKeys(newKey, oldKey keyvault.JSONWebKey) bool {
 
 	return newKey.Kty == oldKey.Kty && (rsaCheck || symmetricCheck)
 }
-func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []byte) error {
+func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []byte, tags map[string]string) error {
 	key, err := getKeyFromValue(value)
 	if err != nil {
 		return fmt.Errorf("could not load private key %v: %w", secretName, err)
@@ -542,6 +552,10 @@ func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []b
 			managedBy: pointer.To(managerLabel),
 		},
 	}
+
+	for k, v := range tags {
+		params.Tags[k] = &v
+	}
 	_, err = a.baseClient.ImportKey(ctx, *a.provider.VaultURL, secretName, params)
 	metrics.ObserveAPICall(constants.ProviderAzureKV, constants.CallAzureKVImportKey, err)
 	if err != nil {
@@ -550,25 +564,34 @@ func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []b
 	return nil
 }
 
+func getSecretKey(secret *corev1.Secret, data esv1beta1.PushSecretData) ([]byte, error) {
+	if data.GetSecretKey() != "" {
+		return secret.Data[data.GetSecretKey()], nil
+	}
+
+	// Must convert secret values to string, otherwise data will be sent as base64 to Vault
+	secretStringVal := make(map[string]string)
+	for k, v := range secret.Data {
+		secretStringVal[k] = string(v)
+	}
+	value, err := utils.JSONMarshal(secretStringVal)
+	if err != nil {
+		return nil, fmt.Errorf("failed to serialize secret content as JSON: %w", err)
+	}
+	return value, nil
+}
+
 // PushSecret stores secrets into a Key vault instance.
 func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1beta1.PushSecretData) error {
 	var (
 		value   []byte
 		err     error
 		expires *date.UnixTime
+		tags    map[string]string
 	)
-	if data.GetSecretKey() == "" {
-		// Must convert secret values to string, otherwise data will be sent as base64 to Vault
-		secretStringVal := make(map[string]string)
-		for k, v := range secret.Data {
-			secretStringVal[k] = string(v)
-		}
-		value, err = utils.JSONMarshal(secretStringVal)
-		if err != nil {
-			return fmt.Errorf("failed to serialize secret content as JSON: %w", err)
-		}
-	} else {
-		value = secret.Data[data.GetSecretKey()]
+
+	if value, err = getSecretKey(secret, data); err != nil {
+		return err
 	}
 
 	metadata, err := metadata.ParseMetadataParameters[PushSecretMetadataSpec](data.GetMetadata())
@@ -585,14 +608,21 @@ func (a *Azure) PushSecret(ctx context.Context, secret *corev1.Secret, data esv1
 		expires = &unixTime
 	}
 
+	if metadata != nil && metadata.Spec.Tags != nil {
+		if _, exists := metadata.Spec.Tags[managedBy]; exists {
+			return fmt.Errorf("error parsing tags in metadata: Cannot specify a '%s' tag", managedBy)
+		}
+		tags = metadata.Spec.Tags
+	}
+
 	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: data.GetRemoteKey()})
 	switch objectType {
 	case defaultObjType:
-		return a.setKeyVaultSecret(ctx, secretName, value, expires)
+		return a.setKeyVaultSecret(ctx, secretName, value, expires, tags)
 	case objectTypeCert:
-		return a.setKeyVaultCertificate(ctx, secretName, value)
+		return a.setKeyVaultCertificate(ctx, secretName, value, tags)
 	case objectTypeKey:
-		return a.setKeyVaultKey(ctx, secretName, value)
+		return a.setKeyVaultKey(ctx, secretName, value, tags)
 	default:
 		return fmt.Errorf("secret type %v not supported", objectType)
 	}

File diff suppressed because it is too large
+ 67 - 0
pkg/provider/azure/keyvault/keyvault_test.go