Repository: deislabs/ratify (go)

Issue:
Store CRD supports to provide the workload identity clientID
### What would you like to be added?

For oras using workload identity as auth provider, the clientID is only available from service account. Would like to support Store CRD to provide the ClientID same as certificateStore instead of from service account.

Current:
https://github.com/deislabs/ratify/blob/main/charts/ratify/templates/store.yaml
Store can only define the workload identity name.

Purpose:
support like
···
apiVersion: config.ratify.deislabs.io/v1alpha1
kind: Store
metadata:
  name: store-oras
spec:
  name: oras
  parameters:
    authProvider:
      clientID: 190d0258-5c9d-4ef2-890f-e12c163a2b3e
      name: azureWorkloadIdentity
    cacheEnabled: true
    capacity: 100
    keyNumber: 10000
    ttl: 10
···

### Anything else you would like to add?

_No response_

### Are you willing to submit PRs to contribute to this feature?

- [X] Yes, I am willing to implement it.

Pull request description:
feat: add client id specification for oras store workload identity auth provider
# Description

## What this PR does / why we need it:
Support Store CRD to provide the workload identity client id to avoid modify service account when need to change oras configuration.

## Which issue(s) this PR fixes *(optional, using `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when the PR gets merged)*:
Fixes #666 

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration

- [ ] Test A
- [ ] Test B

# Checklist:

- [x] Does the affected code have corresponding tests?
- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on your project blog.
- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version?
- [x] Do all new files have appropriate license header?


Patch:
diff --git a/charts/ratify/templates/configmap.yaml b/charts/ratify/templates/configmap.yaml
index 08ee015..7e1cd94 100644
--- a/charts/ratify/templates/configmap.yaml
+++ b/charts/ratify/templates/configmap.yaml
@@ -22,14 +22,15 @@ data:
                 {{- if .Values.oras.authProviders.azureWorkloadIdentityEnabled }}
                 ,
                 "authProvider": {
-                    "name": "azureWorkloadIdentity"
+                    "name": "azureWorkloadIdentity",
+                    "clientID": {{ .Values.azureWorkloadIdentity.clientId | quote }}
                 }
                 {{- end }}
                 {{- if .Values.oras.authProviders.azureManagedIdentityEnabled }}
                 ,
                 "authProvider": {
                     "name": "azureManagedIdentity",
-                    "clientID": {{ .Values.azureManagedIdentity.clientId }}
+                    "clientID": {{ .Values.azureManagedIdentity.clientId | quote }}
                 }
                 {{- end }}
                 {{- if .Values.oras.authProviders.k8secretsEnabled }}
diff --git a/charts/ratify/templates/serviceaccount.yaml b/charts/ratify/templates/serviceaccount.yaml
index 26df690..16713ac 100644
--- a/charts/ratify/templates/serviceaccount.yaml
+++ b/charts/ratify/templates/serviceaccount.yaml
@@ -6,8 +6,6 @@ metadata:
     {{- include "ratify.labels" . | nindent 4 }}
   {{- if .Values.azureWorkloadIdentity.clientId }}
     azure.workload.identity/use: "true"
-  annotations:
-    azure.workload.identity/client-id: {{ .Values.azureWorkloadIdentity.clientId }}
   {{- end }}
   name: {{ include "ratify.serviceAccountName" . }}
 {{- end }}
\ No newline at end of file
diff --git a/charts/ratify/templates/store.yaml b/charts/ratify/templates/store.yaml
index 1cdb41c..f304933 100644
--- a/charts/ratify/templates/store.yaml
+++ b/charts/ratify/templates/store.yaml
@@ -14,6 +14,7 @@ spec:
     {{- if .Values.oras.authProviders.azureWorkloadIdentityEnabled }}
     authProvider:
       name: azureWorkloadIdentity
+      clientID: {{ .Values.azureWorkloadIdentity.clientId }}
     {{- end }}
     {{- if .Values.oras.authProviders.azureManagedIdentityEnabled }}
     authProvider:
diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go
index 93cd1fc..7b629cd 100644
--- a/pkg/common/oras/authprovider/azure/azureworkloadidentity.go
+++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity.go
@@ -39,7 +39,8 @@ type azureWIAuthProvider struct {
 }
 
 type azureWIAuthProviderConf struct {
-	Name string `json:"name"`
+	Name     string `json:"name"`
+	ClientID string `json:"clientID,omitempty"`
 }
 
 const (
@@ -68,10 +69,12 @@ func (s *AzureWIProviderFactory) Create(authProviderConfig provider.AuthProvider
 	if tenant == "" {
 		return nil, fmt.Errorf("azure tenant id environment variable is empty")
 	}
-
-	clientID := os.Getenv("AZURE_CLIENT_ID")
+	clientID := conf.ClientID
 	if clientID == "" {
-		return nil, fmt.Errorf("azure client id environment variable is empty")
+		clientID = os.Getenv("AZURE_CLIENT_ID")
+		if clientID == "" {
+			return nil, fmt.Errorf("no client ID provided and AZURE_CLIENT_ID environment variable is empty")
+		}
 	}
 
 	// retrieve an AAD Access token


Test patch:
diff --git a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go
index 3b2c98c..97c21f5 100644
--- a/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go
+++ b/pkg/common/oras/authprovider/azure/azureworkloadidentity_test.go
@@ -81,6 +81,25 @@ func TestAzureWIValidation_EnvironmentVariables_ExpectedResults(t *testing.T) {
 		t.Fatal("failed to set env variable AZURE_TENANT_ID")
 	}
 
+	authProviderConfigWithClientID := map[string]interface{}{
+		"name":     "azureWorkloadIdentity",
+		"clientID": "client id from config",
+	}
+
+	_, err = authprovider.CreateAuthProviderFromConfig(authProviderConfigWithClientID)
+
+	expectedErr = fmt.Errorf("required environment variables not set, AZURE_FEDERATED_TOKEN_FILE: , AZURE_AUTHORITY_HOST: ")
+	if err == nil || err.Error() != expectedErr.Error() {
+		t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err)
+	}
+
+	_, err = authprovider.CreateAuthProviderFromConfig(authProviderConfig)
+
+	expectedErr = fmt.Errorf("no client ID provided and AZURE_CLIENT_ID environment variable is empty")
+	if err == nil || err.Error() != expectedErr.Error() {
+		t.Fatalf("create auth provider should have failed: expected err %s, but got err %s", expectedErr, err)
+	}
+
 	err = os.Setenv("AZURE_CLIENT_ID", "client id")
 	if err != nil {
 		t.Fatal("failed to set env variable AZURE_CLIENT_ID")