From c3c0a73500416442c058becf862f57db61e2411b Mon Sep 17 00:00:00 2001 From: weizhichen Date: Mon, 4 Nov 2024 15:58:22 +0000 Subject: [PATCH 1/2] feat: support workload identity for blobfuse2 --- docs/workload-identity-static-pv-mount.md | 2 +- pkg/blob/blob.go | 46 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/workload-identity-static-pv-mount.md b/docs/workload-identity-static-pv-mount.md index ef937d591..c52967548 100644 --- a/docs/workload-identity-static-pv-mount.md +++ b/docs/workload-identity-static-pv-mount.md @@ -33,7 +33,7 @@ export IDENTITY_TENANT=$(az aks show --name $CLUSTER_NAME --resource-group $RESO export ACCOUNT_SCOPE=$(az storage account show --name $ACCOUNT --query id -o tsv) # please retry if you meet `Cannot find user or service principal in graph database` error, it may take a while for the identity to propagate -az role assignment create --role "Storage Account Contributor" --assignee $USER_ASSIGNED_CLIENT_ID --scope $ACCOUNT_SCOPE +az role assignment create --role "Storage Blob Data Contributor" --assignee $USER_ASSIGNED_CLIENT_ID --scope $ACCOUNT_SCOPE ``` ### 4. Create service account on AKS diff --git a/pkg/blob/blob.go b/pkg/blob/blob.go index bcd7da6dd..c009f3d71 100644 --- a/pkg/blob/blob.go +++ b/pkg/blob/blob.go @@ -18,6 +18,7 @@ package blob import ( "context" + "encoding/json" "errors" "flag" "fmt" @@ -156,6 +157,9 @@ const ( FSGroupChangeNone = "None" // define tag value delimiter and default is comma tagValueDelimiterField = "tagvaluedelimiter" + + DefaultTokenAudience = "api://AzureADTokenExchange" //nolint:gosec // G101 ignore this! + ) var ( @@ -568,14 +572,19 @@ func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attr tenantID = d.cloud.TenantID } - // if client id is specified, we only use service account token to get account key + // if client id is specified, we only use workload identity for blobfuse auth if clientID != "" { - klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID) - if subsID == "" { - subsID = d.cloud.SubscriptionID + klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID) + + workload_identity_token, err := parseServiceAccountToken(serviceAccountToken) + if err != nil { + return rgName, accountName, accountKey, containerName, authEnv, err } - accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken) - authEnv = append(authEnv, "AZURE_STORAGE_ACCESS_KEY="+accountKey) + + authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID) + authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID) + authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workload_identity_token) + return rgName, accountName, accountKey, containerName, authEnv, err } @@ -1136,3 +1145,28 @@ func generateVolumeName(clusterName, pvName string, maxLength int) string { } return prefix + "-" + pvName } + +// serviceAccountToken represents the service account token sent from NodePublishVolume Request. +// ref: https://kubernetes-csi.github.io/docs/token-requests.html +type serviceAccountToken struct { + APIAzureADTokenExchange struct { + Token string `json:"token"` + ExpirationTimestamp time.Time `json:"expirationTimestamp"` + } `json:"api://AzureADTokenExchange"` +} + +// parseServiceAccountToken parses the bound service account token from the token passed from NodePublishVolume Request. +// ref: https://kubernetes-csi.github.io/docs/token-requests.html +func parseServiceAccountToken(tokenStr string) (string, error) { + if len(tokenStr) == 0 { + return "", fmt.Errorf("service account token is empty") + } + token := serviceAccountToken{} + if err := json.Unmarshal([]byte(tokenStr), &token); err != nil { + return "", fmt.Errorf("failed to unmarshal service account tokens, error: %w", err) + } + if token.APIAzureADTokenExchange.Token == "" { + return "", fmt.Errorf("token for audience %s not found", DefaultTokenAudience) + } + return token.APIAzureADTokenExchange.Token, nil +} From a02013bf650da4c9eb0245b83cee0e730ac3208d Mon Sep 17 00:00:00 2001 From: weizhichen Date: Tue, 5 Nov 2024 04:17:25 +0000 Subject: [PATCH 2/2] fix go lint --- pkg/blob/blob.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/blob/blob.go b/pkg/blob/blob.go index c009f3d71..4ea4e4013 100644 --- a/pkg/blob/blob.go +++ b/pkg/blob/blob.go @@ -576,14 +576,14 @@ func (d *Driver) GetAuthEnv(ctx context.Context, volumeID, protocol string, attr if clientID != "" { klog.V(2).Infof("clientID(%s) is specified, use workload identity for blobfuse auth", clientID) - workload_identity_token, err := parseServiceAccountToken(serviceAccountToken) + workloadIdentityToken, err := parseServiceAccountToken(serviceAccountToken) if err != nil { return rgName, accountName, accountKey, containerName, authEnv, err } authEnv = append(authEnv, "AZURE_STORAGE_SPN_CLIENT_ID="+clientID) authEnv = append(authEnv, "AZURE_STORAGE_SPN_TENANT_ID="+tenantID) - authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workload_identity_token) + authEnv = append(authEnv, "WORKLOAD_IDENTITY_TOKEN="+workloadIdentityToken) return rgName, accountName, accountKey, containerName, authEnv, err }