Browse Source

Rebase on master, and rework unit tests

Nicolas Courbet 4 years ago
parent
commit
0e49b84f6d

+ 15 - 1
apis/externalsecrets/v1alpha1/secretstore_azurekv_types.go

@@ -1,3 +1,17 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package v1alpha1
 
 import smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
@@ -5,7 +19,7 @@ import smmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
 // Configures an store to sync secrets using Azure KV.
 type AzureKVProvider struct {
 	// Vault Url from which the secrets to be fetched from.
-	VaultUrl *string `json:"vaultUrl"`
+	VaultURL *string `json:"vaultUrl"`
 	// TenantID configures the Azure Tenant to send requests to.
 	TenantID *string `json:"tenantId"`
 	// Auth configures how the operator authenticates with Azure.

+ 60 - 0
apis/externalsecrets/v1alpha1/zz_generated.deepcopy.go

@@ -78,6 +78,61 @@ func (in *AWSProvider) DeepCopy() *AWSProvider {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureKVAuth) DeepCopyInto(out *AzureKVAuth) {
+	*out = *in
+	if in.ClientID != nil {
+		in, out := &in.ClientID, &out.ClientID
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+	if in.ClientSecret != nil {
+		in, out := &in.ClientSecret, &out.ClientSecret
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVAuth.
+func (in *AzureKVAuth) DeepCopy() *AzureKVAuth {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureKVAuth)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *AzureKVProvider) DeepCopyInto(out *AzureKVProvider) {
+	*out = *in
+	if in.VaultURL != nil {
+		in, out := &in.VaultURL, &out.VaultURL
+		*out = new(string)
+		**out = **in
+	}
+	if in.TenantID != nil {
+		in, out := &in.TenantID, &out.TenantID
+		*out = new(string)
+		**out = **in
+	}
+	if in.AuthSecretRef != nil {
+		in, out := &in.AuthSecretRef, &out.AuthSecretRef
+		*out = new(AzureKVAuth)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureKVProvider.
+func (in *AzureKVProvider) DeepCopy() *AzureKVProvider {
+	if in == nil {
+		return nil
+	}
+	out := new(AzureKVProvider)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ClusterSecretStore) DeepCopyInto(out *ClusterSecretStore) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
@@ -483,6 +538,11 @@ func (in *SecretStoreProvider) DeepCopyInto(out *SecretStoreProvider) {
 		*out = new(AWSProvider)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.AzureKV != nil {
+		in, out := &in.AzureKV, &out.AzureKV
+		*out = new(AzureKVProvider)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.Vault != nil {
 		in, out := &in.Vault, &out.Vault
 		*out = new(VaultProvider)

+ 66 - 0
deploy/crds/external-secrets.io_clustersecretstores.yaml

@@ -176,6 +176,72 @@ spec:
                         type: string
                     required:
                     - auth
+                  azurekv:
+                    description: AzureKV configures this store to sync secrets using
+                      Azure Key Vault provider
+                    properties:
+                      authSecretRef:
+                        description: Auth configures how the operator authenticates
+                          with Azure.
+                        properties:
+                          clientId:
+                            description: The Azure clientId of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            required:
+                            - name
+                            type: object
+                          clientSecret:
+                            description: The Azure ClientSecret of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            required:
+                            - name
+                            type: object
+                        required:
+                        - clientId
+                        - clientSecret
+                        type: object
+                      tenantId:
+                        description: TenantID configures the Azure Tenant to send
+                          requests to.
+                        type: string
+                      vaultUrl:
+                        description: Vault Url from which the secrets to be fetched
+                          from.
+                        type: string
+                    required:
+                    - authSecretRef
+                    - tenantId
+                    - vaultUrl
                     type: object
                   vault:
                     description: Vault configures this store to sync secrets using

+ 66 - 0
deploy/crds/external-secrets.io_secretstores.yaml

@@ -176,6 +176,72 @@ spec:
                         type: string
                     required:
                     - auth
+                  azurekv:
+                    description: AzureKV configures this store to sync secrets using
+                      Azure Key Vault provider
+                    properties:
+                      authSecretRef:
+                        description: Auth configures how the operator authenticates
+                          with Azure.
+                        properties:
+                          clientId:
+                            description: The Azure clientId of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            required:
+                            - name
+                            type: object
+                          clientSecret:
+                            description: The Azure ClientSecret of the service principle
+                              used for authentication.
+                            properties:
+                              key:
+                                description: The key of the entry in the Secret resource's
+                                  `data` field to be used. Some instances of this
+                                  field may be defaulted, in others it may be required.
+                                type: string
+                              name:
+                                description: The name of the Secret resource being
+                                  referred to.
+                                type: string
+                              namespace:
+                                description: Namespace of the resource being referred
+                                  to. Ignored if referent is not cluster-scoped. cluster-scoped
+                                  defaults to the namespace of the referent.
+                                type: string
+                            required:
+                            - name
+                            type: object
+                        required:
+                        - clientId
+                        - clientSecret
+                        type: object
+                      tenantId:
+                        description: TenantID configures the Azure Tenant to send
+                          requests to.
+                        type: string
+                      vaultUrl:
+                        description: Vault Url from which the secrets to be fetched
+                          from.
+                        type: string
+                    required:
+                    - authSecretRef
+                    - tenantId
+                    - vaultUrl
                     type: object
                   vault:
                     description: Vault configures this store to sync secrets using

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

@@ -0,0 +1,119 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package fake
+
+import (
+	"context"
+
+	"github.com/Azure/azure-sdk-for-go/profiles/latest/keyvault/keyvault"
+	"github.com/google/uuid"
+	mock "github.com/stretchr/testify/mock"
+)
+
+type secretData = struct {
+	item           keyvault.SecretItem
+	secretVersions map[string]keyvault.SecretBundle
+	lastVersion    string
+}
+
+type AzureMock struct {
+	mock.Mock
+	knownSecrets map[string]map[string]*secretData
+}
+
+func (m *AzureMock) AddSecret(vaultBaseURL, secretName, secretContent string, enabled bool) string {
+	uid := uuid.NewString()
+	m.AddSecretWithVersion(vaultBaseURL, secretName, uid, secretContent, enabled)
+	return uid
+}
+
+func (m *AzureMock) AddSecretWithVersion(vaultBaseURL, secretName, secretVersion, secretContent string, enabled bool) {
+	if m.knownSecrets == nil {
+		m.knownSecrets = make(map[string]map[string]*secretData)
+	}
+	if m.knownSecrets[vaultBaseURL] == nil {
+		m.knownSecrets[vaultBaseURL] = make(map[string]*secretData)
+	}
+
+	secretItemID := vaultBaseURL + secretName
+	secretBundleID := secretItemID + "/" + secretVersion
+
+	if m.knownSecrets[vaultBaseURL][secretName] == nil {
+		m.knownSecrets[vaultBaseURL][secretName] = &secretData{
+			item:           newValidSecretItem(secretItemID, enabled),
+			secretVersions: make(map[string]keyvault.SecretBundle),
+		}
+	} else {
+		m.knownSecrets[vaultBaseURL][secretName].item.Attributes.Enabled = &enabled
+	}
+	m.knownSecrets[vaultBaseURL][secretName].secretVersions[secretVersion] = newValidSecretBundle(secretBundleID, secretContent)
+	m.knownSecrets[vaultBaseURL][secretName].lastVersion = secretVersion
+}
+
+func newValidSecretBundle(secretBundleID, secretContent string) keyvault.SecretBundle {
+	return keyvault.SecretBundle{
+		Value: &secretContent,
+		ID:    &secretBundleID,
+	}
+}
+
+func newValidSecretItem(secretItemID string, enabled bool) keyvault.SecretItem {
+	return keyvault.SecretItem{
+		ID:         &secretItemID,
+		Attributes: &keyvault.SecretAttributes{Enabled: &enabled},
+	}
+}
+
+func (m *AzureMock) ExpectsGetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) {
+	data := m.knownSecrets[vaultBaseURL][secretName]
+	version := secretVersion
+	if version == "" {
+		version = data.lastVersion
+	}
+	returnValue := data.secretVersions[version]
+	m.On("GetSecret", ctx, vaultBaseURL, secretName, secretVersion).Return(returnValue, nil)
+}
+
+func (m *AzureMock) ExpectsGetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) {
+	secretMap := m.knownSecrets[vaultBaseURL]
+	secretItems := make([]keyvault.SecretItem, len(secretMap))
+	i := 0
+	for _, value := range secretMap {
+		secretItems[i] = value.item
+		i++
+	}
+	firstPage := keyvault.SecretListResult{
+		Value:    &secretItems,
+		NextLink: nil,
+	}
+	returnValue := keyvault.NewSecretListResultIterator(keyvault.NewSecretListResultPage(firstPage, func(context.Context, keyvault.SecretListResult) (keyvault.SecretListResult, error) {
+		return keyvault.SecretListResult{}, nil
+	}))
+	m.On("GetSecretsComplete", ctx, vaultBaseURL, maxresults).Return(returnValue, nil)
+}
+
+func (m *AzureMock) GetKey(ctx context.Context, vaultBaseURL, keyName, keyVersion string) (result keyvault.KeyBundle, err error) {
+	args := m.Called(ctx, vaultBaseURL, keyName, keyVersion)
+	return args.Get(0).(keyvault.KeyBundle), args.Error(1)
+}
+
+func (m *AzureMock) GetSecret(ctx context.Context, vaultBaseURL, secretName, secretVersion string) (result keyvault.SecretBundle, err error) {
+	args := m.Called(ctx, vaultBaseURL, secretName, secretVersion)
+	return args.Get(0).(keyvault.SecretBundle), args.Error(1)
+}
+
+func (m *AzureMock) GetSecretsComplete(ctx context.Context, vaultBaseURL string, maxresults *int32) (result keyvault.SecretListResultIterator, err error) {
+	args := m.Called(ctx, vaultBaseURL, maxresults)
+	return args.Get(0).(keyvault.SecretListResultIterator), args.Error(1)
+}

+ 72 - 32
pkg/provider/azure/keyvault/keyvault.go

@@ -1,3 +1,17 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package keyvault
 
 import (
@@ -26,40 +40,56 @@ import (
 	"github.com/external-secrets/external-secrets/pkg/provider/schema"
 )
 
+// Provider satisfies the provider interface.
+type Provider struct{}
+
+// interface to keyvault.BaseClient.
+type SecretClient interface {
+	GetKey(ctx context.Context, vaultBaseURL string, keyName string, keyVersion string) (result keyvault.KeyBundle, err error)
+	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)
+}
+
+// Azure satisfies the provider.SecretsClient interface.
 type Azure struct {
 	kube       client.Client
 	store      esv1alpha1.GenericStore
-	baseClient *keyvault.BaseClient
-	vaultUrl   string
+	baseClient SecretClient
+	vaultURL   string
 	namespace  string
 }
 
 func init() {
-	schema.Register(&Azure{}, &esv1alpha1.SecretStoreProvider{
+	schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
 		AzureKV: &esv1alpha1.AzureKVProvider{},
 	})
 }
 
-func (a *Azure) New(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.Provider, error) {
+// NewClient constructs a new secrets client based on the provided store.
+func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
+	return newClient(ctx, store, kube, namespace)
+}
+
+func newClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
 	anAzure := &Azure{
 		kube:      kube,
 		store:     store,
 		namespace: namespace,
 	}
-	azClient, vaultUrl, err := anAzure.newAzureClient(ctx)
+	azClient, vaultURL, err := anAzure.newAzureClient(ctx)
 
 	if err != nil {
 		return nil, err
 	}
 
 	anAzure.baseClient = azClient
-	anAzure.vaultUrl = vaultUrl
+	anAzure.vaultURL = vaultURL
 	return anAzure, nil
 }
 
-//Implements store.Client.GetSecret Interface.
-//Retrieves a secret/Key/Certificate 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
+// Implements store.Client.GetSecret Interface.
+// Retrieves a secret/Key/Certificate 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 esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	version := ""
 	objectType := "secret"
@@ -80,16 +110,15 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretData
 	}
 
 	switch objectType {
-
 	case "secret":
-		secretResp, err := basicClient.GetSecret(context.Background(), a.vaultUrl, secretName, version)
+		secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
 		if err != nil {
 			return nil, err
 		}
 		secretValue = *secretResp.Value
 
 	case "cert":
-		secretResp, err := basicClient.GetSecret(context.Background(), a.vaultUrl, secretName, version)
+		secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, version)
 		if err != nil {
 			return nil, err
 		}
@@ -97,54 +126,64 @@ func (a *Azure) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretData
 		if secretResp.ContentType != nil && *secretResp.ContentType == "application/x-pkcs12" {
 			secretValue, err = getCertBundleForPKCS(*secretResp.Value)
 			// Do we really need to decode PKCS raw value to PEM ? or will that be achieved by the templating features ?
+			if err != nil {
+				return nil, err
+			}
 		} else {
 			secretValue = *secretResp.Value
 		}
 
 	case "key":
-		keyResp, err := basicClient.GetKey(context.Background(), a.vaultUrl, secretName, version)
+		keyResp, err := basicClient.GetKey(context.Background(), a.vaultURL, secretName, version)
 		if err != nil {
 			return nil, err
 		}
 		jwk := *keyResp.Key
 		// Do we really need to decode JWK raw value to PEM ? or will that be achieved by the templating features ?
 		secretValue, err = getPublicKeyFromJwk(jwk)
+		if err != nil {
+			return nil, err
+		}
 
 	default:
-		return nil, fmt.Errorf("Unknown Azure Keyvault object Type for %s", secretName)
+		return nil, fmt.Errorf("unknown Azure Keyvault object Type for %s", secretName)
 	}
 
 	return []byte(secretValue), nil
 }
 
-//Implements store.Client.GetSecretMap Interface.
-//etrieve ALL secrets in a specific keyvault
-func (a *Azure) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
+// Implements store.Client.GetSecretMap Interface.
+// retrieve ALL secrets in a specific keyvault.
+// ExternalSecretDataRemoteRef Key is mandatory, but with current model we do not use its content.
+func (a *Azure) GetSecretMap(ctx context.Context, _ esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	basicClient := a.baseClient
 	secretsMap := make(map[string][]byte)
 
-	secretListIter, err := basicClient.GetSecretsComplete(context.Background(), a.vaultUrl, nil)
+	secretListIter, err := basicClient.GetSecretsComplete(context.Background(), a.vaultURL, nil)
 	if err != nil {
 		return nil, err
 	}
 	for secretListIter.NotDone() {
 		secretList := secretListIter.Response().Value
 		for _, secret := range *secretList {
-			if *secret.Attributes.Enabled {
-				secretName := path.Base(*secret.ID)
-				secretResp, err := basicClient.GetSecret(context.Background(), a.vaultUrl, secretName, "")
-				secretValue := *secretResp.Value
-
-				if err != nil {
-					return nil, err
-				}
-				secretsMap[secretName] = []byte(secretValue)
+			if !*secret.Attributes.Enabled {
+				continue
+			}
+			secretName := path.Base(*secret.ID)
+			secretResp, err := basicClient.GetSecret(context.Background(), a.vaultURL, secretName, "")
+			secretValue := *secretResp.Value
 
+			if err != nil {
+				return nil, err
 			}
+			secretsMap[secretName] = []byte(secretValue)
+		}
+		err = secretListIter.Next()
+		if err != nil {
+			return nil, err
 		}
-		secretListIter.Next()
 	}
-	return secretsMap, err
+	return secretsMap, nil
 }
 
 // getCertBundle returns the certificate bundle.
@@ -208,7 +247,7 @@ func getPublicKeyFromJwk(jwk keyvault.JSONWebKey) (bundle string, err error) {
 		Bytes: der,
 	}
 	var out bytes.Buffer
-	pem.Encode(&out, block)
+	err = pem.Encode(&out, block)
 	if err != nil {
 		return "", err
 	}
@@ -218,7 +257,7 @@ func getPublicKeyFromJwk(jwk keyvault.JSONWebKey) (bundle string, err error) {
 func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, string, error) {
 	spec := *a.store.GetSpec().Provider.AzureKV
 	tenantID := *spec.TenantID
-	vaultUrl := *spec.VaultUrl
+	vaultURL := *spec.VaultURL
 
 	if spec.AuthSecretRef == nil {
 		return nil, "", fmt.Errorf("missing clientID/clientSecret in store config")
@@ -253,8 +292,9 @@ func (a *Azure) newAzureClient(ctx context.Context) (*keyvault.BaseClient, strin
 	basicClient := keyvault.New()
 	basicClient.Authorizer = authorizer
 
-	return &basicClient, vaultUrl, nil
+	return &basicClient, vaultURL, nil
 }
+
 func (a *Azure) secretKeyRef(ctx context.Context, namespace string, secretRef smmeta.SecretKeySelector, scoped bool) (string, error) {
 	var secret corev1.Secret
 	ref := types.NamespacedName{

+ 106 - 33
pkg/provider/azure/keyvault/keyvault_test.go

@@ -1,57 +1,130 @@
+/*
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 package keyvault
 
 import (
 	context "context"
 	"testing"
 
-	tassert "github.com/stretchr/testify/assert"
-	mock "github.com/stretchr/testify/mock"
-	"gotest.tools/assert"
-
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+	v1 "github.com/external-secrets/external-secrets/apis/meta/v1"
+	fake "github.com/external-secrets/external-secrets/pkg/provider/azure/keyvault/fake"
+	"github.com/external-secrets/external-secrets/pkg/provider/schema"
+	tassert "github.com/stretchr/testify/assert"
+	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-type azureMock struct {
-	mock.Mock
+func newAzure() (Azure, *fake.AzureMock) {
+	azureMock := &fake.AzureMock{}
+	testAzure := Azure{
+		baseClient: azureMock,
+		vaultURL:   "https://local.vault/",
+	}
+	return testAzure, azureMock
+}
+
+func TestNewClientNoCreds(t *testing.T) {
+	namespace := "internal"
+	vaultURL := "https://local.vault.url"
+	tenantID := "1234"
+	store := esv1alpha1.SecretStore{
+		Spec: esv1alpha1.SecretStoreSpec{Provider: &esv1alpha1.SecretStoreProvider{AzureKV: &esv1alpha1.AzureKVProvider{
+			VaultURL: &vaultURL,
+			TenantID: &tenantID,
+		}}},
+	}
+	provider, err := schema.GetProvider(&store)
+	tassert.Nil(t, err, "the return err should be nil")
+	k8sClient := clientfake.NewClientBuilder().Build()
+	secretClient, err := provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	tassert.EqualError(t, err, "missing clientID/clientSecret in store config")
+	tassert.Nil(t, secretClient)
+
+	store.Spec.Provider.AzureKV.AuthSecretRef = &esv1alpha1.AzureKVAuth{}
+	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
+	tassert.Nil(t, secretClient)
+
+	store.Spec.Provider.AzureKV.AuthSecretRef.ClientID = &v1.SecretKeySelector{Name: "user"}
+	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	tassert.EqualError(t, err, "missing accessKeyID/secretAccessKey in store config")
+	tassert.Nil(t, secretClient)
+
+	store.Spec.Provider.AzureKV.AuthSecretRef.ClientSecret = &v1.SecretKeySelector{Name: "password"}
+	secretClient, err = provider.NewClient(context.Background(), &store, k8sClient, namespace)
+	tassert.EqualError(t, err, "secrets \"user\" not found")
+	tassert.Nil(t, secretClient)
 }
 
-func TestGetSecret(t *testing.T) {
-	testAzure := new(Azure)
-	anAzureMock := new(azureMock)
+func TestGetSecretWithVersion(t *testing.T) {
+	testAzure, azureMock := newAzure()
 	ctx := context.Background()
-	property := "testProperty"
 	version := "v1"
 
 	rf := esv1alpha1.ExternalSecretDataRemoteRef{
-		Key:      "testName",
-		Property: property,
-		Version:  version,
+		Key:     "testName",
+		Version: version,
 	}
-	returnValue := make(map[string][]byte)
-	returnValue["key"] = []byte{'A'}
-	anAzureMock.On("getKeyVaultSecrets", ctx, "testName", "v1", "testProperty", false).Return(returnValue, nil)
-	_, err := testAzure.GetSecret(ctx, rf)
-	assert.NilError(t, err, "the return err should be nil")
-	anAzureMock.AssertExpectations(t)
+	azureMock.AddSecretWithVersion(testAzure.vaultURL, "testName", version, "My Secret", true)
+	azureMock.ExpectsGetSecret(ctx, testAzure.vaultURL, "testName", version)
+
+	secret, err := testAzure.GetSecret(ctx, rf)
+	azureMock.AssertExpectations(t)
+	tassert.Nil(t, err, "the return err should be nil")
+	tassert.Equal(t, []byte("My Secret"), secret)
 }
 
-func TestGetSecretMap(t *testing.T) {
-	testAzure := new(Azure)
-	anAzureMock := new(azureMock)
+func TestGetSecretWithoutVersion(t *testing.T) {
+	testAzure, azureMock := newAzure()
 	ctx := context.Background()
-	property := "testProperty"
-	version := "v1"
+
 	rf := esv1alpha1.ExternalSecretDataRemoteRef{
-		Key:      "testName",
-		Property: property,
-		Version:  version,
+		Key: "testName",
 	}
-	returnValue := make(map[string][]byte)
-	returnValue["key"] = []byte{'a'}
-	anAzureMock.On("getKeyVaultSecrets", ctx, "testName", "v1", "", true).Return(returnValue, nil)
-	_, err := testAzure.GetSecretMap(ctx, rf)
-	assert.NilError(t, err, "the return err should be nil")
-	anAzureMock.AssertExpectations(t)
+	azureMock.AddSecret(testAzure.vaultURL, "testName", "My Secret", true)
+	azureMock.ExpectsGetSecret(ctx, testAzure.vaultURL, "testName", "")
+
+	secret, err := testAzure.GetSecret(ctx, rf)
+	azureMock.AssertExpectations(t)
+	tassert.Nil(t, err, "the return err should be nil")
+	tassert.Equal(t, []byte("My Secret"), secret)
+}
+
+func TestGetSecretMap(t *testing.T) {
+	testAzure, azureMock := newAzure()
+	ctx := context.Background()
+	rf := esv1alpha1.ExternalSecretDataRemoteRef{}
+	azureMock.AddSecret(testAzure.vaultURL, "testName", "My Secret", true)
+	azureMock.ExpectsGetSecretsComplete(ctx, testAzure.vaultURL, nil)
+	azureMock.ExpectsGetSecret(ctx, testAzure.vaultURL, "testName", "")
+	secretMap, err := testAzure.GetSecretMap(ctx, rf)
+	azureMock.AssertExpectations(t)
+	tassert.Nil(t, err, "the return err should be nil")
+	tassert.Equal(t, secretMap, map[string][]byte{"testName": []byte("My Secret")})
+}
+
+func TestGetSecretMapNotEnabled(t *testing.T) {
+	testAzure, azureMock := newAzure()
+	ctx := context.Background()
+	rf := esv1alpha1.ExternalSecretDataRemoteRef{}
+	azureMock.AddSecret(testAzure.vaultURL, "testName", "My Secret", false)
+	azureMock.ExpectsGetSecretsComplete(ctx, testAzure.vaultURL, nil)
+	secretMap, err := testAzure.GetSecretMap(ctx, rf)
+	azureMock.AssertExpectations(t)
+	tassert.Nil(t, err, "the return err should be nil")
+	tassert.Empty(t, secretMap)
 }
 
 func TestGetCertBundleForPKCS(t *testing.T) {