Jelajahi Sumber

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>
Mykhailo Zahlada 2 tahun lalu
induk
melakukan
47cc50a9ed

+ 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.
@@ -7981,10 +7998,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: |-
@@ -8017,6 +8034,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
@@ -8071,7 +8105,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>

File diff ditekan karena terlalu besar
+ 8 - 0
docs/provider/azure-key-vault.md


+ 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{