Browse Source

:sparkles: Adds Keyvault PushSecret (#1883)

* Adds Keyvault PushSecret

Signed-off-by: Gustavo Carvalho <gusfcarvalho@gmail.com>
Gustavo Fernandes de Carvalho 3 years ago
parent
commit
833658699d

+ 1 - 1
docs/introduction/stability-support.md

@@ -50,7 +50,7 @@ The following table show the support for features across different providers.
 | AWS Parameter Store       |      x       |      x       |                      |            x            |        x         |             |
 | Hashicorp Vault           |      x       |      x       |                      |                         |        x         |             |
 | GCP Secret Manager        |      x       |      x       |                      |            x            |        x         |             |
-| Azure Keyvault            |      x       |      x       |          x           |            x            |        x         |             |
+| Azure Keyvault            |      x       |      x       |          x           |            x            |        x         |     x        |
 | Kubernetes                |      x       |      x       |                      |            x            |        x         |             |
 | IBM Cloud Secrets Manager |              |              |                      |                         |        x         |             |
 | Yandex Lockbox            |              |              |                      |                         |        x         |             |

+ 30 - 0
docs/provider/azure-key-vault.md

@@ -137,3 +137,33 @@ To get a PKCS#12 certificate from Azure Key Vault and inject it as a `Kind=Secre
 ```yaml
 {% include 'azkv-pkcs12-cert-external-secret.yaml' %}
 ```
+
+### Creating a PushSecret
+You can push secrets to Keyvault into the different `secret`, `key` and `certificate` APIs.
+
+#### Pushing to a Secret
+Pushing to a Secret requires no previous setup. with the secret available in kubernetes, you can simply refer it to a PushSecret object to have it created on Azure Keyvault:
+```yaml
+{% include 'azkv-pushsecret-secret.yaml' %}
+```
+!!! note
+      In order to create a PushSecret targeting keys, `CreateSecret` and `DeleteSecret` actions must be granted to the Service Principal/Identity configured on the SecretStore.
+
+#### Pushing to a Key
+The first step is to generate a valid Private Key. Supported Formats include `PRIVATE KEY`, `RSA PRIVATE KEY` AND `EC PRIVATE KEY` (EC/PKCS1/PKCS8 types). After uploading your key to a Kubernetes Secret, the next step is to create a PushSecret manifest with the following configuration:
+
+```yaml
+{% include 'azkv-pushsecret-key.yaml' %}
+```
+
+!!! note
+      In order to create a PushSecret targeting keys, `ImportKey` and `DeleteKey` actions must be granted to the Service Principal/Identity configured on the SecretStore.
+#### Pushing to a Certificate
+The first step is to generate a valid P12 certificate. Currently, only PKCS1/PKCS8 types are supported. Currently only passwordless P12 certificates are supported.
+
+After uploading your P12 certificate to a Kubernetes Secret, the next step is to create a PushSecret manifest with the following configuration
+```yaml
+{% include 'azkv-pushsecret-certificate.yaml' %}
+```
+!!! note
+       In order to create a PushSecret targeting keys, `ImportCertificate` and `DeleteCertificate` actions must be granted to the Service Principal/Identity configured on the SecretStore.

File diff suppressed because it is too large
+ 36 - 0
docs/snippets/azkv-pushsecret-certificate.yaml


File diff suppressed because it is too large
+ 26 - 0
docs/snippets/azkv-pushsecret-key.yaml


+ 26 - 0
docs/snippets/azkv-pushsecret-secret.yaml

@@ -0,0 +1,26 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: source-secret
+stringData:
+  source-key: "my-secret"
+---
+apiVersion: external-secrets.io/v1alpha1
+kind: PushSecret
+metadata:
+  name: pushsecret-example
+  namespace: default
+spec:
+  refreshInterval: 10s # Refresh interval for which push secret will reconcile
+  deletionPolicy: Delete
+  secretStoreRefs: # A list of secret stores to push secrets to
+    - name: azure-store
+      kind: SecretStore
+  selector:
+    secret:
+      name: source-secret # Source Kubernetes secret to be pushed
+  data:
+    - match:
+        secretKey: source-key # Source Kubernetes secret key containing the secret
+        remoteRef:
+          remoteKey: my-azkv-secret-name 

+ 2 - 2
docs/snippets/azkv-secret-store.yaml

@@ -7,9 +7,9 @@ spec:
     # provider type: azure keyvault
     azurekv:
       # azure tenant ID, see: https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-how-to-find-tenant
-      tenantId: "d3bc2180-xxxx-xxxx-xxxx-154105743342"
+      tenantId: "2ed1d494-6c5a-4c5d-aa24-479446fb844d"
       # URL of your vault instance, see: https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates
-      vaultUrl: "https://my-keyvault-name.vault.azure.net"
+      vaultUrl: "https://kvtestpushsecret.vault.azure.net"
       authSecretRef:
         # points to the secret that contains
         # the azure service principal credentials

+ 78 - 0
pkg/provider/azure/keyvault/fake/fake.go

@@ -24,6 +24,12 @@ type AzureMockClient struct {
 	getSecret          func(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	getSecretsComplete func(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	getCertificate     func(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
+	setSecret          func(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error)
+	importCertificate  func(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error)
+	importKey          func(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyImportParameters) (result keyvault.KeyBundle, err error)
+	deleteCertificate  func(ctx context.Context, vaultBaseURL string, certificateName string) (result keyvault.DeletedCertificateBundle, err error)
+	deleteKey          func(ctx context.Context, vaultBaseURL string, keyName string) (result keyvault.DeletedKeyBundle, err error)
+	deleteSecret       func(ctx context.Context, vaultBaseURL string, secretName string) (result keyvault.DeletedSecretBundle, err error)
 }
 
 func (mc *AzureMockClient) GetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) (result keyvault.SecretBundle, err error) {
@@ -42,6 +48,30 @@ func (mc *AzureMockClient) GetSecretsComplete(ctx context.Context, vaultBaseURL
 	return mc.getSecretsComplete(ctx, vaultBaseURL, maxresults)
 }
 
+func (mc *AzureMockClient) SetSecret(ctx context.Context, vaultBaseURL, secretName string, parameters keyvault.SecretSetParameters) (keyvault.SecretBundle, error) {
+	return mc.setSecret(ctx, vaultBaseURL, secretName, parameters)
+}
+
+func (mc *AzureMockClient) ImportCertificate(ctx context.Context, vaultBaseURL, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error) {
+	return mc.importCertificate(ctx, vaultBaseURL, certificateName, parameters)
+}
+
+func (mc *AzureMockClient) ImportKey(ctx context.Context, vaultBaseURL, keyName string, parameters keyvault.KeyImportParameters) (result keyvault.KeyBundle, err error) {
+	return mc.importKey(ctx, vaultBaseURL, keyName, parameters)
+}
+
+func (mc *AzureMockClient) DeleteKey(ctx context.Context, vaultBaseURL, keyName string) (keyvault.DeletedKeyBundle, error) {
+	return mc.deleteKey(ctx, vaultBaseURL, keyName)
+}
+
+func (mc *AzureMockClient) DeleteSecret(ctx context.Context, vaultBaseURL, secretName string) (keyvault.DeletedSecretBundle, error) {
+	return mc.deleteSecret(ctx, vaultBaseURL, secretName)
+}
+
+func (mc *AzureMockClient) DeleteCertificate(ctx context.Context, vaultBaseURL, certificateName string) (keyvault.DeletedCertificateBundle, error) {
+	return mc.deleteCertificate(ctx, vaultBaseURL, certificateName)
+}
+
 func (mc *AzureMockClient) WithValue(serviceURL, secretName, secretVersion string, apiOutput keyvault.SecretBundle, err error) {
 	if mc != nil {
 		mc.getSecret = func(ctx context.Context, serviceURL, secretName, secretVersion string) (result keyvault.SecretBundle, retErr error) {
@@ -66,6 +96,54 @@ func (mc *AzureMockClient) WithCertificate(serviceURL, secretName, secretVersion
 	}
 }
 
+func (mc *AzureMockClient) WithImportCertificate(apiOutput keyvault.CertificateBundle, err error) {
+	if mc != nil {
+		mc.importCertificate = func(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (keyvault.CertificateBundle, error) {
+			return apiOutput, err
+		}
+	}
+}
+
+func (mc *AzureMockClient) WithImportKey(output keyvault.KeyBundle, err error) {
+	if mc != nil {
+		mc.importKey = func(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyImportParameters) (keyvault.KeyBundle, error) {
+			return output, err
+		}
+	}
+}
+
+func (mc *AzureMockClient) WithSetSecret(output keyvault.SecretBundle, err error) {
+	if mc != nil {
+		mc.setSecret = func(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (keyvault.SecretBundle, error) {
+			return output, err
+		}
+	}
+}
+
+func (mc *AzureMockClient) WithDeleteSecret(output keyvault.DeletedSecretBundle, err error) {
+	if mc != nil {
+		mc.deleteSecret = func(ctx context.Context, vaultBaseURL string, secretName string) (keyvault.DeletedSecretBundle, error) {
+			return output, err
+		}
+	}
+}
+
+func (mc *AzureMockClient) WithDeleteCertificate(output keyvault.DeletedCertificateBundle, err error) {
+	if mc != nil {
+		mc.deleteCertificate = func(ctx context.Context, vaultBaseURL string, certificateName string) (keyvault.DeletedCertificateBundle, error) {
+			return output, err
+		}
+	}
+}
+
+func (mc *AzureMockClient) WithDeleteKey(output keyvault.DeletedKeyBundle, err error) {
+	if mc != nil {
+		mc.deleteKey = func(ctx context.Context, vaultBaseURL string, keyName string) (keyvault.DeletedKeyBundle, error) {
+			return output, err
+		}
+	}
+}
+
 func (mc *AzureMockClient) WithList(serviceURL string, apiOutput keyvault.SecretListResultIterator, err error) {
 	if mc != nil {
 		mc.getSecretsComplete = func(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error) {

+ 265 - 3
pkg/provider/azure/keyvault/keyvault.go

@@ -16,7 +16,10 @@ package keyvault
 
 import (
 	"context"
+	"crypto/x509"
+	b64 "encoding/base64"
 	"encoding/json"
+	"encoding/pem"
 	"errors"
 	"fmt"
 	"os"
@@ -30,13 +33,17 @@ import (
 	"github.com/Azure/go-autorest/autorest/azure"
 	kvauth "github.com/Azure/go-autorest/autorest/azure/auth"
 	"github.com/AzureAD/microsoft-authentication-library-for-go/apps/confidential"
+	"github.com/lestrrat-go/jwx/jwk"
 	"github.com/tidwall/gjson"
+	"golang.org/x/crypto/pkcs12"
+	"golang.org/x/crypto/sha3"
 	authv1 "k8s.io/api/authentication/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes"
 	kcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+	"k8s.io/utils/pointer"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
 
@@ -52,6 +59,7 @@ const (
 	AzureDefaultAudience = "api://AzureADTokenExchange"
 	AnnotationClientID   = "azure.workload.identity/client-id"
 	AnnotationTenantID   = "azure.workload.identity/tenant-id"
+	managerLabel         = "external-secrets"
 
 	errUnexpectedStoreSpec   = "unexpected store spec"
 	errMissingAuthType       = "cannot initialize Azure Client: no valid authType was specified"
@@ -90,6 +98,12 @@ type SecretClient interface {
 	GetSecret(ctx context.Context, vaultBaseURL string, secretName string, secretVersion string) (result keyvault.SecretBundle, err error)
 	GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error)
 	GetCertificate(ctx context.Context, vaultBaseURL string, certificateName string, certificateVersion string) (result keyvault.CertificateBundle, err error)
+	SetSecret(ctx context.Context, vaultBaseURL string, secretName string, parameters keyvault.SecretSetParameters) (result keyvault.SecretBundle, err error)
+	ImportKey(ctx context.Context, vaultBaseURL string, keyName string, parameters keyvault.KeyImportParameters) (result keyvault.KeyBundle, err error)
+	ImportCertificate(ctx context.Context, vaultBaseURL string, certificateName string, parameters keyvault.CertificateImportParameters) (result keyvault.CertificateBundle, err error)
+	DeleteCertificate(ctx context.Context, vaultBaseURL string, certificateName string) (result keyvault.DeletedCertificateBundle, err error)
+	DeleteKey(ctx context.Context, vaultBaseURL string, keyName string) (result keyvault.DeletedKeyBundle, err error)
+	DeleteSecret(ctx context.Context, vaultBaseURL string, secretName string) (result keyvault.DeletedSecretBundle, err error)
 }
 
 type Azure struct {
@@ -201,13 +215,261 @@ func (a *Azure) ValidateStore(store esv1beta1.GenericStore) error {
 	return nil
 }
 
+func canDelete(tags map[string]*string, err error) (bool, error) {
+	aerr := &autorest.DetailedError{}
+	conv := errors.As(err, aerr)
+	if err != nil && !conv {
+		return false, fmt.Errorf("could not parse error: %w", err)
+	}
+	if conv && aerr.StatusCode != 404 { // Secret is already deleted, nothing to do.
+		return false, fmt.Errorf("unexpected api error: %w", err)
+	}
+	if aerr.StatusCode == 404 {
+		return false, nil
+	}
+	manager, ok := tags["managed-by"]
+	if !ok || manager == nil || *manager != managerLabel {
+		return false, fmt.Errorf("not managed by external-secrets")
+	}
+	return true, nil
+}
+
+func (a *Azure) deleteKeyVaultKey(ctx context.Context, keyName string) error {
+	value, err := a.baseClient.GetKey(ctx, *a.provider.VaultURL, keyName, "")
+	ok, err := canDelete(value.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting key %v: %w", keyName, err)
+	}
+	if ok {
+		_, err = a.baseClient.DeleteKey(ctx, *a.provider.VaultURL, keyName)
+		if err != nil {
+			return fmt.Errorf("error deleting key %v: %w", keyName, err)
+		}
+	}
+	return nil
+}
+
+func (a *Azure) deleteKeyVaultSecret(ctx context.Context, secretName string) error {
+	value, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, "")
+	ok, err := canDelete(value.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting secret %v: %w", secretName, err)
+	}
+	if ok {
+		_, err = a.baseClient.DeleteSecret(ctx, *a.provider.VaultURL, secretName)
+		if err != nil {
+			return fmt.Errorf("error deleting secret %v: %w", secretName, err)
+		}
+	}
+	return nil
+}
+
+func (a *Azure) deleteKeyVaultCertificate(ctx context.Context, certName string) error {
+	value, err := a.baseClient.GetCertificate(ctx, *a.provider.VaultURL, certName, "")
+	ok, err := canDelete(value.Tags, err)
+	if err != nil {
+		return fmt.Errorf("error getting certificate %v: %w", certName, err)
+	}
+	if ok {
+		_, err = a.baseClient.DeleteCertificate(ctx, *a.provider.VaultURL, certName)
+		if err != nil {
+			return fmt.Errorf("error deleting certificate %v: %w", certName, err)
+		}
+	}
+	return nil
+}
+
 func (a *Azure) DeleteSecret(ctx context.Context, remoteRef esv1beta1.PushRemoteRef) error {
-	return fmt.Errorf("not implemented")
+	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
+	switch objectType {
+	case defaultObjType:
+		return a.deleteKeyVaultSecret(ctx, secretName)
+	case objectTypeCert:
+		return a.deleteKeyVaultCertificate(ctx, secretName)
+	case objectTypeKey:
+		return a.deleteKeyVaultKey(ctx, secretName)
+	default:
+		return fmt.Errorf("secret type '%v' is not supported", objectType)
+	}
+}
+
+func getCertificateFromValue(value []byte) (*x509.Certificate, error) {
+	_, localCert, err := pkcs12.Decode(value, "")
+	if err != nil {
+		pemBlock, _ := pem.Decode(value)
+		if pemBlock == nil {
+			return x509.ParseCertificate(value)
+		}
+		return x509.ParseCertificate(pemBlock.Bytes)
+	}
+	return localCert, err
+}
+
+func getKeyFromValue(value []byte) (interface{}, error) {
+	val := value
+	pemBlock, _ := pem.Decode(value)
+	// if a private key regular expression doesn't match, we should consider this key to be symmetric
+	if pemBlock == nil {
+		return val, nil
+	}
+	val = pemBlock.Bytes
+	switch pemBlock.Type {
+	case "PRIVATE KEY":
+		return x509.ParsePKCS8PrivateKey(val)
+	case "RSA PRIVATE KEY":
+		return x509.ParsePKCS1PrivateKey(val)
+	case "EC PRIVATE KEY":
+		return x509.ParseECPrivateKey(val)
+	default:
+		return nil, fmt.Errorf("key type %v is not supported", pemBlock.Type)
+	}
 }
 
-// Not Implemented PushSecret.
+func canCreate(tags map[string]*string, err error) (bool, error) {
+	aerr := &autorest.DetailedError{}
+	conv := errors.As(err, aerr)
+	if err != nil && !conv {
+		return false, fmt.Errorf("could not parse error: %w", err)
+	}
+	if conv && aerr.StatusCode != 404 {
+		return false, fmt.Errorf("unexpected api error: %w", err)
+	}
+	if err == nil {
+		manager, ok := tags["managed-by"]
+		if !ok || manager == nil || *manager != managerLabel {
+			return false, fmt.Errorf("not managed by external-secrets")
+		}
+	}
+	return true, nil
+}
+
+func (a *Azure) setKeyVaultSecret(ctx context.Context, secretName string, value []byte) error {
+	secret, err := a.baseClient.GetSecret(ctx, *a.provider.VaultURL, secretName, "")
+	ok, err := canCreate(secret.Tags, err)
+	if err != nil {
+		return fmt.Errorf("cannot get secret %v: %w", secretName, err)
+	}
+	if !ok {
+		return nil
+	}
+	val := string(value)
+	if secret.Value != nil && val == *secret.Value {
+		return nil
+	}
+	secretParams := keyvault.SecretSetParameters{
+		Value: &val,
+		Tags: map[string]*string{
+			"managed-by": pointer.String(managerLabel),
+		},
+		SecretAttributes: &keyvault.SecretAttributes{
+			Enabled: pointer.Bool(true),
+		},
+	}
+	_, err = a.baseClient.SetSecret(ctx, *a.provider.VaultURL, secretName, secretParams)
+	if err != nil {
+		return fmt.Errorf("could not set secret %v: %w", secretName, err)
+	}
+	return nil
+}
+
+func (a *Azure) setKeyVaultCertificate(ctx context.Context, secretName string, value []byte) error {
+	val := b64.StdEncoding.EncodeToString(value)
+	localCert, err := getCertificateFromValue(value)
+	if err != nil {
+		return fmt.Errorf("value from secret is not a valid certificate: %w", err)
+	}
+	cert, err := a.baseClient.GetCertificate(ctx, *a.provider.VaultURL, secretName, "")
+	ok, err := canCreate(cert.Tags, err)
+	if err != nil {
+		return fmt.Errorf("cannot get certificate %v: %w", secretName, err)
+	}
+	if !ok {
+		return nil
+	}
+	b512 := sha3.Sum512(localCert.Raw)
+	if cert.Cer != nil && b512 == sha3.Sum512(*cert.Cer) {
+		return nil
+	}
+	params := keyvault.CertificateImportParameters{
+		Base64EncodedCertificate: &val,
+		Tags: map[string]*string{
+			"managed-by": pointer.String(managerLabel),
+		},
+	}
+	_, err = a.baseClient.ImportCertificate(ctx, *a.provider.VaultURL, secretName, params)
+	if err != nil {
+		return fmt.Errorf("could not import certificate %v: %w", secretName, err)
+	}
+	return nil
+}
+func equalKeys(newKey, oldKey keyvault.JSONWebKey) bool {
+	// checks for everything except KeyID and KeyOps
+	rsaCheck := newKey.E != nil && oldKey.E != nil && *newKey.E == *oldKey.E &&
+		newKey.N != nil && oldKey.N != nil && *newKey.N == *oldKey.N
+
+	symmetricCheck := newKey.Crv == oldKey.Crv &&
+		newKey.T != nil && oldKey.T != nil && *newKey.T == *oldKey.T &&
+		newKey.X != nil && oldKey.X != nil && *newKey.X == *oldKey.X &&
+		newKey.Y != nil && oldKey.Y != nil && *newKey.Y == *oldKey.Y
+
+	return newKey.Kty == oldKey.Kty && (rsaCheck || symmetricCheck)
+}
+func (a *Azure) setKeyVaultKey(ctx context.Context, secretName string, value []byte) error {
+	key, err := getKeyFromValue(value)
+	if err != nil {
+		return fmt.Errorf("could not load private key %v: %w", secretName, err)
+	}
+	jwKey, err := jwk.New(key)
+	if err != nil {
+		return fmt.Errorf("failed to generate a JWK from secret %v content: %w", secretName, err)
+	}
+	buf, err := json.Marshal(jwKey)
+	if err != nil {
+		return fmt.Errorf("error parsing key: %w", err)
+	}
+	azkey := keyvault.JSONWebKey{}
+	err = json.Unmarshal(buf, &azkey)
+	if err != nil {
+		return fmt.Errorf("error unmarshalling key: %w", err)
+	}
+	keyFromVault, err := a.baseClient.GetKey(ctx, *a.provider.VaultURL, secretName, "")
+	ok, err := canCreate(keyFromVault.Tags, err)
+	if err != nil {
+		return fmt.Errorf("cannot get key %v: %w", secretName, err)
+	}
+	if !ok {
+		return nil
+	}
+	if keyFromVault.Key != nil && equalKeys(azkey, *keyFromVault.Key) {
+		return nil
+	}
+	params := keyvault.KeyImportParameters{
+		Key:           &azkey,
+		KeyAttributes: &keyvault.KeyAttributes{},
+		Tags: map[string]*string{
+			"managed-by": pointer.String(managerLabel),
+		},
+	}
+	_, err = a.baseClient.ImportKey(ctx, *a.provider.VaultURL, secretName, params)
+	if err != nil {
+		return fmt.Errorf("could not import key %v: %w", secretName, err)
+	}
+	return nil
+}
+
+// PushSecret stores secrets into a Key vault instance.
 func (a *Azure) PushSecret(ctx context.Context, value []byte, remoteRef esv1beta1.PushRemoteRef) error {
-	return fmt.Errorf("not implemented")
+	objectType, secretName := getObjType(esv1beta1.ExternalSecretDataRemoteRef{Key: remoteRef.GetRemoteKey()})
+	switch objectType {
+	case defaultObjType:
+		return a.setKeyVaultSecret(ctx, secretName, value)
+	case objectTypeCert:
+		return a.setKeyVaultCertificate(ctx, secretName, value)
+	case objectTypeKey:
+		return a.setKeyVaultKey(ctx, secretName, value)
+	default:
+		return fmt.Errorf("secret type %v not supported", objectType)
+	}
 }
 
 // Implements store.Client.GetAllSecrets Interface.

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