Quellcode durchsuchen

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 vor 2 Jahren
Ursprung
Commit
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.
 	// 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. Required for ServicePrincipal auth type.
+	// TenantID configures the Azure Tenant to send requests to. Required for ServicePrincipal auth type. Optional for WorkloadIdentity.
 	// +optional
 	// +optional
 	TenantID *string `json:"tenantId,omitempty"`
 	TenantID *string `json:"tenantId,omitempty"`
 
 
@@ -72,7 +72,7 @@ type AzureKVProvider struct {
 	// +kubebuilder:default=PublicCloud
 	// +kubebuilder:default=PublicCloud
 	EnvironmentType AzureEnvironmentType `json:"environmentType,omitempty"`
 	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
 	// +optional
 	AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`
 	AuthSecretRef *AzureKVAuth `json:"authSecretRef,omitempty"`
 
 
@@ -88,10 +88,14 @@ type AzureKVProvider struct {
 
 
 // Configuration used to authenticate with Azure.
 // Configuration used to authenticate with Azure.
 type AzureKVAuth struct {
 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
 	// +optional
 	ClientID *smmeta.SecretKeySelector `json:"clientId,omitempty"`
 	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.
 	// The Azure ClientSecret of the service principle used for authentication.
 	// +optional
 	// +optional
 	ClientSecret *smmeta.SecretKeySelector `json:"clientSecret,omitempty"`
 	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)
 		*out = new(metav1.SecretKeySelector)
 		(*in).DeepCopyInto(*out)
 		(*in).DeepCopyInto(*out)
 	}
 	}
+	if in.TenantID != nil {
+		in, out := &in.TenantID, &out.TenantID
+		*out = new(metav1.SecretKeySelector)
+		(*in).DeepCopyInto(*out)
+	}
 	if in.ClientSecret != nil {
 	if in.ClientSecret != nil {
 		in, out := &in.ClientSecret, &out.ClientSecret
 		in, out := &in.ClientSecret, &out.ClientSecret
 		*out = new(metav1.SecretKeySelector)
 		*out = new(metav1.SecretKeySelector)

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

@@ -2139,11 +2139,12 @@ spec:
                     properties:
                     properties:
                       authSecretRef:
                       authSecretRef:
                         description: Auth configures how the operator authenticates
                         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:
                         properties:
                           clientId:
                           clientId:
                             description: The Azure clientId of the service principle
                             description: The Azure clientId of the service principle
-                              used for authentication.
+                              or managed identity used for authentication.
                             properties:
                             properties:
                               key:
                               key:
                                 description: |-
                                 description: |-
@@ -2179,6 +2180,25 @@ spec:
                                   to the namespace of the referent.
                                   to the namespace of the referent.
                                 type: string
                                 type: string
                             type: object
                             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
                         type: object
                       authType:
                       authType:
                         default: ServicePrincipal
                         default: ServicePrincipal
@@ -2236,7 +2256,8 @@ spec:
                         type: object
                         type: object
                       tenantId:
                       tenantId:
                         description: TenantID configures the Azure Tenant to send
                         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
                         type: string
                       vaultUrl:
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched
                         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:
                     properties:
                       authSecretRef:
                       authSecretRef:
                         description: Auth configures how the operator authenticates
                         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:
                         properties:
                           clientId:
                           clientId:
                             description: The Azure clientId of the service principle
                             description: The Azure clientId of the service principle
-                              used for authentication.
+                              or managed identity used for authentication.
                             properties:
                             properties:
                               key:
                               key:
                                 description: |-
                                 description: |-
@@ -2179,6 +2180,25 @@ spec:
                                   to the namespace of the referent.
                                   to the namespace of the referent.
                                 type: string
                                 type: string
                             type: object
                             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
                         type: object
                       authType:
                       authType:
                         default: ServicePrincipal
                         default: ServicePrincipal
@@ -2236,7 +2256,8 @@ spec:
                         type: object
                         type: object
                       tenantId:
                       tenantId:
                         description: TenantID configures the Azure Tenant to send
                         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
                         type: string
                       vaultUrl:
                       vaultUrl:
                         description: Vault Url from which the secrets to be fetched
                         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
                       description: AzureKV configures this store to sync secrets using Azure Key Vault provider
                       properties:
                       properties:
                         authSecretRef:
                         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:
                           properties:
                             clientId:
                             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:
                               properties:
                                 key:
                                 key:
                                   description: |-
                                   description: |-
@@ -2693,6 +2693,23 @@ spec:
                                     to the namespace of the referent.
                                     to the namespace of the referent.
                                   type: string
                                   type: string
                               type: object
                               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
                           type: object
                         authType:
                         authType:
                           default: ServicePrincipal
                           default: ServicePrincipal
@@ -2747,7 +2764,7 @@ spec:
                             - name
                             - name
                           type: object
                           type: object
                         tenantId:
                         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
                           type: string
                         vaultUrl:
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.
                           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
                       description: AzureKV configures this store to sync secrets using Azure Key Vault provider
                       properties:
                       properties:
                         authSecretRef:
                         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:
                           properties:
                             clientId:
                             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:
                               properties:
                                 key:
                                 key:
                                   description: |-
                                   description: |-
@@ -8080,6 +8097,23 @@ spec:
                                     to the namespace of the referent.
                                     to the namespace of the referent.
                                   type: string
                                   type: string
                               type: object
                               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
                           type: object
                         authType:
                         authType:
                           default: ServicePrincipal
                           default: ServicePrincipal
@@ -8134,7 +8168,7 @@ spec:
                             - name
                             - name
                           type: object
                           type: object
                         tenantId:
                         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
                           type: string
                         vaultUrl:
                         vaultUrl:
                           description: Vault Url from which the secrets to be fetched from.
                           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>
 <td>
 <td>
 <em>(Optional)</em>
 <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>
 </td>
 </tr>
 </tr>
 <tr>
 <tr>
@@ -911,7 +925,7 @@ string
 </td>
 </td>
 <td>
 <td>
 <em>(Optional)</em>
 <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>
 </td>
 </tr>
 </tr>
 <tr>
 <tr>
@@ -941,7 +955,7 @@ AzureKVAuth
 </td>
 </td>
 <td>
 <td>
 <em>(Optional)</em>
 <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>
 </td>
 </tr>
 </tr>
 <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' %}
 {% 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
 ### Update secret store
 Be sure the `azurekv` provider is listed in the `Kind=SecretStore`
 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"
 	errDataFromCert          = "cannot get use dataFrom to get certificate secret"
 	errDataFromKey           = "cannot get use dataFrom to get key secret"
 	errDataFromKey           = "cannot get use dataFrom to get key secret"
 	errMissingTenant         = "missing tenantID in store config"
 	errMissingTenant         = "missing tenantID in store config"
+	errMissingClient         = "missing clientID: either serviceAccountRef or service account annotation '%s' is missing"
 	errMissingSecretRef      = "missing secretRef in provider config"
 	errMissingSecretRef      = "missing secretRef in provider config"
 	errMissingClientIDSecret = "missing accessKeyID/secretAccessKey in store 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"
 	errFindSecret            = "could not find secret %s/%s: %w"
 	errFindDataKey           = "no data for %q in secret '%s/%s'"
 	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) {
 func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider tokenProviderFunc) (autorest.Authorizer, error) {
 	aadEndpoint := AadEndpointForType(a.provider.EnvironmentType)
 	aadEndpoint := AadEndpointForType(a.provider.EnvironmentType)
 	kvResource := kvResourceForProviderConfig(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.
 	// 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 {
 	if a.provider.ServiceAccountRef == nil {
 		clientID := os.Getenv("AZURE_CLIENT_ID")
 		clientID := os.Getenv("AZURE_CLIENT_ID")
 		tenantID := os.Getenv("AZURE_TENANT_ID")
 		tenantID := os.Getenv("AZURE_TENANT_ID")
@@ -825,13 +829,76 @@ func (a *Azure) authorizerForWorkloadIdentity(ctx context.Context, tokenProvider
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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}
 	audiences := []string{AzureDefaultAudience}
 	if len(a.provider.ServiceAccountRef.Audiences) > 0 {
 	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"
 		saToken       = "FAKETOKEN"
 		saName        = "az-wi"
 		saName        = "az-wi"
 		namespace     = "default"
 		namespace     = "default"
+		secretName    = "mi-spec"
 	)
 	)
 
 
 	// create a temporary file to imitate
 	// create a temporary file to imitate
@@ -89,6 +90,7 @@ func TestGetAuthorizorForWorkloadIdentity(t *testing.T) {
 	tokenFile := tf.Name()
 	tokenFile := tf.Name()
 
 
 	authType := esv1beta1.AzureWorkloadIdentity
 	authType := esv1beta1.AzureWorkloadIdentity
+
 	defaultProvider := &esv1beta1.AzureKVProvider{
 	defaultProvider := &esv1beta1.AzureKVProvider{
 		VaultURL: &vaultURL,
 		VaultURL: &vaultURL,
 		AuthType: &authType,
 		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,
 			provider: defaultProvider,
 			k8sObjects: []client.Object{
 			k8sObjects: []client.Object{
 				&corev1.ServiceAccount{
 				&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,
 			provider: defaultProvider,
 			k8sObjects: []client.Object{
 			k8sObjects: []client.Object{
 				&corev1.ServiceAccount{
 				&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) {
 		t.Run(row.name, func(t *testing.T) {
 			store := esv1beta1.SecretStore{
 			store := esv1beta1.SecretStore{