Explorar o código

Workloadidentity clientid from secret ref (#3367)

* updates documentation: extends workloadIdentity auth configuration

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>

* adds and updates tests

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>

* extends provider configuration to accept clientId and tenantId as auth SecretRef

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>

* updates service account example

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>

* updates docs

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>

---------

Signed-off-by: Mykhailo Zahlada <myzahlad@microsoft.com>
Co-authored-by: Mykhailo Zahlada <myzahlad@microsoft.com>
Co-authored-by: Gustavo Fernandes de Carvalho <gusfcarvalho@gmail.com>
Signed-off-by: Bill Hamilton <bill.hamilton@delinea.com>
Mykhailo Zahlada %!s(int64=2) %!d(string=hai) anos
pai
achega
914bd25fa1

+ 7 - 3
apis/externalsecrets/v1beta1/secretstore_azurekv_types.go

@@ -61,7 +61,7 @@ type AzureKVProvider struct {
 	// Vault Url from which the secrets to be fetched from.
 	VaultURL *string `json:"vaultUrl"`
 
-	// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
+	// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
 	// +optional
 	TenantID *string `json:"tenantId,omitempty"`
 
@@ -72,7 +72,7 @@ type AzureKVProvider struct {
 	// +kubebuilder:default=PublicCloud
 	EnvironmentType AzureEnvironmentType `json:"environmentType,omitempty"`
 
-	// Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
+	// Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
 	// +optional
 	AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`
 
@@ -88,10 +88,14 @@ type AzureKVProvider struct {
 
 // Configuration used to authenticate with Azure.
 type AzureKVAuth struct {
-	// The Azure clientId of the service principle used for authentication.
+	// The Azure clientId of the service principle or managed identity used for authentication.
 	// +optional
 	ClientID *smmeta.SecretKeySelector `json:"clientId,omitempty"`
 
+	// The Azure tenantId of the managed identity used for authentication.
+	// +optional
+	TenantID *smmeta.SecretKeySelector `json:"tenantId,omitempty"`
+
 	// The Azure ClientSecret of the service principle used for authentication.
 	// +optional
 	ClientSecret *smmeta.SecretKeySelector `json:"clientSecret,omitempty"`

+ 5 - 0
apis/externalsecrets/v1beta1/zz_generated.deepcopy.go

@@ -319,6 +319,11 @@ func (in *AzureKVAuth) DeepCopyInto(out *AzureKVAuth) {
 		*out = new(metav1.SecretKeySelector)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.TenantID != nil {
+		in, out := &in.TenantID, &out.TenantID
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.ClientSecret != nil {
 		in, out := &in.ClientSecret, &out.ClientSecret
 		*out = new(metav1.SecretKeySelector)

+ 24 - 3
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -2139,11 +2139,12 @@ spec:
                     properties:
                       authSecretRef:
                         description: Auth configures how the operator authenticates
-                          with Azure. Required for ServicePrincipal auth type.
+                          with Azure. Required for ServicePrincipal auth type. Optional
+                          for WorkloadIdentity.
                         properties:
                           clientId:
                             description: The Azure clientId of the service principle
-                              used for authentication.
+                              or managed identity used for authentication.
                             properties:
                               key:
                                 description: |-
@@ -2179,6 +2180,25 @@ spec:
                                   to the namespace of the referent.
                                 type: string
                             type: object
+                          tenantId:
+                            description: The Azure tenantId of the managed identity
+                              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
+                            type: object
                         type: object
                       authType:
                         default: ServicePrincipal
@@ -2236,7 +2256,8 @@ spec:
                         type: object
                       tenantId:
                         description: TenantID configures the Azure Tenant to send
-                          requests to. Required for ServicePrincipal auth type.
+                          requests to. Required for ServicePrincipal auth type. Optional
+                          for WorkloadIdentity.
                         type: string
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched

+ 24 - 3
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -2139,11 +2139,12 @@ spec:
                     properties:
                       authSecretRef:
                         description: Auth configures how the operator authenticates
-                          with Azure. Required for ServicePrincipal auth type.
+                          with Azure. Required for ServicePrincipal auth type. Optional
+                          for WorkloadIdentity.
                         properties:
                           clientId:
                             description: The Azure clientId of the service principle
-                              used for authentication.
+                              or managed identity used for authentication.
                             properties:
                               key:
                                 description: |-
@@ -2179,6 +2180,25 @@ spec:
                                   to the namespace of the referent.
                                 type: string
                             type: object
+                          tenantId:
+                            description: The Azure tenantId of the managed identity
+                              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
+                            type: object
                         type: object
                       authType:
                         default: ServicePrincipal
@@ -2236,7 +2256,8 @@ spec:
                         type: object
                       tenantId:
                         description: TenantID configures the Azure Tenant to send
-                          requests to. Required for ServicePrincipal auth type.
+                          requests to. Required for ServicePrincipal auth type. Optional
+                          for WorkloadIdentity.
                         type: string
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched

+ 40 - 6
deploy/crds/bundle.yaml

@@ -2657,10 +2657,10 @@ spec:
                       description: AzureKV configures this store to sync secrets using Azure Key Vault provider
                       properties:
                         authSecretRef:
-                          description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
+                          description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           properties:
                             clientId:
-                              description: The Azure clientId of the service principle used for authentication.
+                              description: The Azure clientId of the service principle or managed identity used for authentication.
                               properties:
                                 key:
                                   description: |-
@@ -2693,6 +2693,23 @@ spec:
                                     to the namespace of the referent.
                                   type: string
                               type: object
+                            tenantId:
+                              description: The Azure tenantId of the managed identity 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
+                              type: object
                           type: object
                         authType:
                           default: ServicePrincipal
@@ -2747,7 +2764,7 @@ spec:
                             - name
                           type: object
                         tenantId:
-                          description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
+                          description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           type: string
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.
@@ -8044,10 +8061,10 @@ spec:
                       description: AzureKV configures this store to sync secrets using Azure Key Vault provider
                       properties:
                         authSecretRef:
-                          description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.
+                          description: Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           properties:
                             clientId:
-                              description: The Azure clientId of the service principle used for authentication.
+                              description: The Azure clientId of the service principle or managed identity used for authentication.
                               properties:
                                 key:
                                   description: |-
@@ -8080,6 +8097,23 @@ spec:
                                     to the namespace of the referent.
                                   type: string
                               type: object
+                            tenantId:
+                              description: The Azure tenantId of the managed identity 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
+                              type: object
                           type: object
                         authType:
                           default: ServicePrincipal
@@ -8134,7 +8168,7 @@ spec:
                             - name
                           type: object
                         tenantId:
-                          description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.
+                          description: TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
                           type: string
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.

+ 17 - 3
docs/api/spec.md

@@ -838,7 +838,21 @@ External Secrets meta/v1.SecretKeySelector
 </td>
 <td>
 <em>(Optional)</em>
-<p>The Azure clientId of the service principle used for authentication.</p>
+<p>The Azure clientId of the service principle or managed identity used for authentication.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>tenantId</code></br>
+<em>
+<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
+External Secrets meta/v1.SecretKeySelector
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The Azure tenantId of the managed identity used for authentication.</p>
 </td>
 </tr>
 <tr>
@@ -911,7 +925,7 @@ string
 </td>
 <td>
 <em>(Optional)</em>
-<p>TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type.</p>
+<p>TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.</p>
 </td>
 </tr>
 <tr>
@@ -941,7 +955,7 @@ AzureKVAuth
 </td>
 <td>
 <em>(Optional)</em>
-<p>Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type.</p>
+<p>Auth configures how the operator authenticates with Azure. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.</p>
 </td>
 </tr>
 <tr>

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

@@ -90,6 +90,14 @@ You run the controller without service account (effectively without azure permis
 {% include 'azkv-workload-identity.yaml' %}
 ```
 
+In case you don't have the clientId when deploying the SecretStore, such as when deploying a Helm chart that includes instructions for creating a [Managed Identity](https://github.com/Azure/azure-service-operator/blob/main/v2/samples/managedidentity/v1api20181130/v1api20181130_userassignedidentity.yaml) using [Azure Service Operator](https://azure.github.io/azure-service-operator/) next to the SecretStore definition, you may encounter an interpolation problem. Helm lacks dependency management, which means it can create an issue when the clientId is only known after everything is deployed. Although the Service Account can inject `clientId` and `tenantId` into a pod, it doesn't support secretKeyRef/configMapKeyRef. Therefore, you can deliver the clientId and tenantId directly, bypassing the Service Account.
+
+The following example demonstrates using the secretRef field to directly deliver the `clientId` and `tenantId` to the SecretStore while utilizing Workload Identity authentication.
+
+```yaml
+{% include 'azkv-workload-identity-secretref.yaml' %}
+```
+
 ### Update secret store
 Be sure the `azurekv` provider is listed in the `Kind=SecretStore`
 

+ 28 - 0
docs/snippets/azkv-workload-identity-secretref.yaml

@@ -0,0 +1,28 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  # this service account was created by azwi
+  name: workload-identity-sa
+  annotations: {}
+---
+apiVersion: external-secrets.io/v1beta1
+kind: SecretStore
+metadata:
+  name: azure-store
+spec:
+  provider:
+    azurekv:
+      # tenantId spec option #1
+      tenantId: "5a02a20e-xxxx-xxxx-xxxx-0ad5b634c5d8"
+      authType: WorkloadIdentity
+      vaultUrl: "https://xx-xxxx-xx.vault.azure.net"
+      serviceAccountRef:
+        name: workload-identity-sa
+      authSecretRef:
+        clientId:
+          name: umi-secret
+          key: clientId
+        # tenantId spec option #2
+        tenantId:
+          name: umi-secret
+          key: tenantId

+ 75 - 8
pkg/provider/azure/keyvault/keyvault.go

@@ -73,8 +73,11 @@ const (
 	errDataFromCert          = "cannot get use dataFrom to get certificate secret"
 	errDataFromKey           = "cannot get use dataFrom to get key secret"
 	errMissingTenant         = "missing tenantID in store config"
+	errMissingClient         = "missing clientID: either serviceAccountRef or service account annotation '%s' is missing"
 	errMissingSecretRef      = "missing secretRef in provider config"
 	errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store config"
+	errMultipleClientID      = "multiple clientID found. Check secretRef and serviceAccountRef"
+	errMultipleTenantID      = "multiple tenantID found. Check secretRef, 'spec.provider.azurekv.tenantId', and serviceAccountRef"
 	errFindSecret            = "could not find secret %s/%s: %w"
 	errFindDataKey           = "no data for %q in secret '%s/%s'"
 
@@ -793,9 +796,10 @@ func getSecretMapProperties(tags map[string]*string, key, property string) map[s
 func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
 	aadEndpoint := AadEndpointForType(a.provider.EnvironmentType)
 	kvResource := kvResourceForProviderConfig(a.provider.EnvironmentType)
-	// if no serviceAccountRef was provided
+	// If no serviceAccountRef was provided
 	// we expect certain env vars to be present.
-	// They are set by the azure workload identity webhook.
+	// They are set by the azure workload identity webhook
+	// by adding the label `azure.workload.identity/use: "true"` to the external-secrets pod
 	if a.provider.ServiceAccountRef == nil {
 		clientID := os.Getenv("AZURE_CLIENT_ID")
 		tenantID := os.Getenv("AZURE_TENANT_ID")
@@ -825,13 +829,76 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 	if err != nil {
 		return nil, err
 	}
-	clientID, ok := sa.ObjectMeta.Annotations[AnnotationClientID]
-	if !ok {
-		return nil, fmt.Errorf(errMissingSAAnnotation, AnnotationClientID)
+	// Extract clientID
+	var clientID string
+	// First check if AuthSecretRef is set and clientID can be fetched from there
+	if a.provider.AuthSecretRef != nil {
+		if a.provider.AuthSecretRef.ClientID == nil {
+			return nil, fmt.Errorf(errMissingClientIDSecret)
+		}
+		clientID, err = resolvers.SecretKeyRef(
+			ctx,
+			a.crClient,
+			a.store.GetKind(),
+			a.namespace, a.provider.AuthSecretRef.ClientID)
+		if err != nil {
+			return nil, err
+		}
 	}
-	tenantID, ok := sa.ObjectMeta.Annotations[AnnotationTenantID]
-	if !ok {
-		return nil, fmt.Errorf(errMissingSAAnnotation, AnnotationTenantID)
+	// If AuthSecretRef is not set, use default (Service Account) implementation
+	// Try to get clientID from Annotations
+	if len(sa.ObjectMeta.Annotations) > 0 {
+		if val, found := sa.ObjectMeta.Annotations[AnnotationClientID]; found {
+			// If clientID is defined in both Annotations and AuthSecretRef, return an error
+			if clientID != "" {
+				return nil, fmt.Errorf(errMultipleClientID)
+			}
+			clientID = val
+		}
+	}
+	// Return an error if clientID is still empty
+	if clientID == "" {
+		return nil, fmt.Errorf(errMissingClient, AnnotationClientID)
+	}
+	// Extract tenantID
+	var tenantID string
+	// First check if AuthSecretRef is set and tenantID can be fetched from there
+	if a.provider.AuthSecretRef != nil {
+		// We may want to set tenantID explicitly in the `spec.provider.azurekv` section of the SecretStore object
+		// So that is okay if it is not there
+		if a.provider.AuthSecretRef.TenantID != nil {
+			tenantID, err = resolvers.SecretKeyRef(
+				ctx,
+				a.crClient,
+				a.store.GetKind(),
+				a.namespace, a.provider.AuthSecretRef.TenantID)
+			if err != nil {
+				return nil, err
+			}
+		}
+	}
+	// Check if spec.provider.azurekv.tenantId is set
+	if tenantID == "" && a.provider.TenantID != nil {
+		tenantID = *a.provider.TenantID
+	}
+	// Try to get tenantID from Annotations first. Default implementation.
+	if len(sa.ObjectMeta.Annotations) > 0 {
+		if val, found := sa.ObjectMeta.Annotations[AnnotationTenantID]; found {
+			// If tenantID is defined in both Annotations and AuthSecretRef, return an error
+			if tenantID != "" {
+				return nil, fmt.Errorf(errMultipleTenantID)
+			}
+			tenantID = val
+		}
+	}
+	// Fallback: use the AZURE_TENANT_ID env var which is set by the azure workload identity webhook
+	// https://azure.github.io/azure-workload-identity/docs/topics/service-account-labels-and-annotations.html#service-account
+	if tenantID == "" {
+		tenantID = os.Getenv("AZURE_TENANT_ID")
+	}
+	// Return an error if tenantID is still empty
+	if tenantID == "" {
+		return nil, errors.New(errMissingTenant)
 	}
 	audiences := []string{AzureDefaultAudience}
 	if len(a.provider.ServiceAccountRef.Audiences) > 0 {

+ 132 - 3
pkg/provider/azure/keyvault/keyvault_auth_test.go

@@ -76,6 +76,7 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 		saToken       = "FAKETOKEN"
 		saName        = "az-wi"
 		namespace     = "default"
+		secretName    = "mi-spec"
 	)
 
 	// create a temporary file to imitate
@@ -89,6 +90,7 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 	tokenFile := tf.Name()
 
 	authType := esv1beta1.AzureWorkloadIdentity
+
 	defaultProvider := &esv1beta1.AzureKVProvider{
 		VaultURL: &vaultURL,
 		AuthType: &authType,
@@ -136,7 +138,7 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 			},
 		},
 		{
-			name:     "missing sa annotations",
+			name:     "missing sa annotations, tenantID, and clientId/tenantId AuthSecretRef",
 			provider: defaultProvider,
 			k8sObjects: []client.Object{
 				&corev1.ServiceAccount{
@@ -147,10 +149,72 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 					},
 				},
 			},
-			expErr: "missing service account annotation: azure.workload.identity/client-id",
+			expErr: "missing clientID: either serviceAccountRef or service account annotation 'azure.workload.identity/client-id' is missing",
+		},
+		{
+			name: "duplicated clientId",
+			provider: &esv1beta1.AzureKVProvider{
+				VaultURL: &vaultURL,
+				AuthType: &authType,
+				TenantID: pointer.To(tenantID),
+				ServiceAccountRef: &v1.ServiceAccountSelector{
+					Name: saName,
+				},
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{Name: secretName, Namespace: pointer.To(namespace), Key: clientID},
+					TenantID: &v1.SecretKeySelector{Name: secretName, Namespace: pointer.To(namespace), Key: tenantID},
+				},
+			},
+			k8sObjects: []client.Object{
+				&corev1.ServiceAccount{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      saName,
+						Namespace: namespace,
+						Annotations: map[string]string{
+							AnnotationClientID: clientID,
+							AnnotationTenantID: tenantID,
+						},
+					},
+				},
+				&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      secretName,
+						Namespace: namespace,
+					},
+					Data: map[string][]byte{
+						clientID: []byte("clientid"),
+						tenantID: []byte("tenantid"),
+					},
+				},
+			},
+			expErr: "multiple clientID found. Check secretRef and serviceAccountRef",
+		},
+		{
+			name: "duplicated tenantId",
+			provider: &esv1beta1.AzureKVProvider{
+				VaultURL: &vaultURL,
+				AuthType: &authType,
+				TenantID: pointer.To(tenantID),
+				ServiceAccountRef: &v1.ServiceAccountSelector{
+					Name: saName,
+				},
+			},
+			k8sObjects: []client.Object{
+				&corev1.ServiceAccount{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      saName,
+						Namespace: namespace,
+						Annotations: map[string]string{
+							AnnotationClientID: clientID,
+							AnnotationTenantID: tenantID,
+						},
+					},
+				},
+			},
+			expErr: "multiple tenantID found. Check secretRef, 'spec.provider.azurekv.tenantId', and serviceAccountRef",
 		},
 		{
-			name:     "successful case",
+			name:     "successful case #1: ClientID, TenantID from ServiceAccountRef",
 			provider: defaultProvider,
 			k8sObjects: []client.Object{
 				&corev1.ServiceAccount{
@@ -165,6 +229,71 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "successful case #2: ClientID, TenantID from AuthSecretRef",
+			provider: &esv1beta1.AzureKVProvider{
+				VaultURL: &vaultURL,
+				AuthType: &authType,
+				ServiceAccountRef: &v1.ServiceAccountSelector{
+					Name: saName,
+				},
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{Name: secretName, Namespace: pointer.To(namespace), Key: clientID},
+					TenantID: &v1.SecretKeySelector{Name: secretName, Namespace: pointer.To(namespace), Key: tenantID},
+				},
+			},
+			k8sObjects: []client.Object{
+				&corev1.ServiceAccount{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        saName,
+						Namespace:   namespace,
+						Annotations: map[string]string{},
+					},
+				},
+				&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      secretName,
+						Namespace: namespace,
+					},
+					Data: map[string][]byte{
+						clientID: []byte("clientid"),
+						tenantID: []byte("tenantid"),
+					},
+				},
+			},
+		},
+		{
+			name: "successful case #3: ClientID from AuthSecretRef, TenantID from provider",
+			provider: &esv1beta1.AzureKVProvider{
+				VaultURL: &vaultURL,
+				AuthType: &authType,
+				TenantID: pointer.To(tenantID),
+				ServiceAccountRef: &v1.ServiceAccountSelector{
+					Name: saName,
+				},
+				AuthSecretRef: &esv1beta1.AzureKVAuth{
+					ClientID: &v1.SecretKeySelector{Name: secretName, Namespace: pointer.To(namespace), Key: clientID},
+				},
+			},
+			k8sObjects: []client.Object{
+				&corev1.ServiceAccount{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:        saName,
+						Namespace:   namespace,
+						Annotations: map[string]string{},
+					},
+				},
+				&corev1.Secret{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      secretName,
+						Namespace: namespace,
+					},
+					Data: map[string][]byte{
+						clientID: []byte("clientid"),
+					},
+				},
+			},
+		},
 	} {
 		t.Run(row.name, func(t *testing.T) {
 			store := esv1beta1.SecretStore{