Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
b5faa64
feat: support mount Azure File with user-provided managed identity token
andyzhangx Apr 21, 2026
44c7c2f
fix: redact command output in setCredentialCache error to avoid token…
andyzhangx Apr 22, 2026
5d26a0d
feat: ignore mountWithOAuthToken parameter in controller server
andyzhangx Apr 22, 2026
bbfa76f
fix: generalize error message in GetStorageAccountFromSecret
andyzhangx Apr 22, 2026
9b5f2ee
feat: support mountWithOAuthToken in NodePublishVolume for token refr…
andyzhangx Apr 25, 2026
bf769f2
Revert "feat: support mountWithOAuthToken in NodePublishVolume for to…
andyzhangx Apr 25, 2026
9382682
fix: NodePublishVolume should only refresh OAuth token credential cac…
andyzhangx Apr 25, 2026
2197965
address review: fix OAuth token refresh and error handling
andyzhangx Apr 25, 2026
f050aa7
address review: move OAuth refresh after idempotency check, resolve a…
andyzhangx Apr 25, 2026
344c2b7
perf: skip OAuth token credential cache refresh when token is unchanged
andyzhangx Apr 25, 2026
24708e2
fix: remove duplicate OAuth token refresh block in NodePublishVolume
andyzhangx Apr 25, 2026
0b2ebd3
refactor: extract getSecretNamespace helper to deduplicate namespace …
andyzhangx Apr 25, 2026
ac27467
test: add NodePublishVolume unit tests for mountWithOAuthToken path
andyzhangx Apr 25, 2026
e09bb5e
fix: validate secretName in NodePublishVolume before OAuth refresh
andyzhangx Apr 25, 2026
f02febf
fix: surface secret lookup errors and add OAuth token SHA dedup tests
andyzhangx Apr 25, 2026
cbc9225
fix: gofmt import ordering
andyzhangx Apr 25, 2026
84090c3
fix: remove secretName from empty server test to avoid secret lookup …
andyzhangx Apr 25, 2026
cae74bc
fix: validate mountWithOAuthToken in controller and move OAuth valida…
andyzhangx Apr 25, 2026
275c5ff
fix: address review comments - mutual exclusion, redact output, fresh…
andyzhangx Apr 25, 2026
fa6b4a4
fix: refresh OAuth token before ensureMountPoint to fix stale mount r…
andyzhangx Apr 28, 2026
a9e1078
address review: use field constants in error messages, merge duplicat…
andyzhangx Apr 28, 2026
e3a43b6
fix: update tests to match new error message format using field const…
andyzhangx Apr 28, 2026
54da608
add e2e test for mountWithOAuthToken dynamic provisioning
andyzhangx Apr 28, 2026
cb4b467
e2e: fail fast if OAuth token setup fails in BeforeSuite
andyzhangx Apr 28, 2026
f8dc87e
fix: syntax errors in suite_test.go and azure_helpers.go
andyzhangx Apr 28, 2026
08b9950
fix: move test-only deps to indirect in go.mod
andyzhangx Apr 28, 2026
e89fc8b
fix: move azidentity/armauthorization/armcompute to direct deps in go…
andyzhangx Apr 28, 2026
5806966
fix: get OAuth token from agent node via IMDS pod instead of Prow pod
andyzhangx Apr 29, 2026
90b9070
fix: schedule oauth token-fetcher pod on agent nodes only
andyzhangx Apr 29, 2026
e8fe3fa
chore: add --show-labels to kubectl get nodes in azurefile_log.sh
andyzhangx Apr 29, 2026
2e17142
fix: use GenerateName for token-fetcher pod to avoid collisions
andyzhangx Apr 29, 2026
3ae08bd
fix: move azidentity to indirect, fix secret update and IMDS resource…
andyzhangx Apr 29, 2026
ae73836
fix: skip storing account key when mountWithOAuthToken is true
andyzhangx Apr 29, 2026
2466437
test: log which node the oauth token fetcher pod ran on
andyzhangx Apr 30, 2026
70f22e6
move OAuth token setup from BeforeSuite to test case
andyzhangx Apr 30, 2026
26d2e53
fix: gofmt indentation
andyzhangx Apr 30, 2026
74888c4
fix: remove trailing slash from storage.azure.com resource, log OAuth…
andyzhangx Apr 30, 2026
39de2e8
fix: pin OAuth test pod to same node where token was fetched
andyzhangx May 1, 2026
c71e942
fix: include mountWithOAuthToken in requiresSmbOAuth condition
andyzhangx May 1, 2026
b9fe412
fix: resolve accountName from volume ID in NodePublishVolume for OAut…
andyzhangx May 2, 2026
374ac44
revert: remove node logging, token info logging, and node-pinning for…
andyzhangx May 2, 2026
cc850cb
refactor: consolidate storeAccountKey and requiresSmbOAuth logic
andyzhangx May 3, 2026
d34d66b
refactor: consolidate OAuth token server resolution and validation in…
andyzhangx May 3, 2026
ec43ac0
fix: update stale log message for identity-based mount
andyzhangx May 3, 2026
4bd685d
fix: update setCredentialCacheWithOAuthToken test for new signature
andyzhangx May 3, 2026
c34b38f
fix: restore secretName validation in NodeStageVolume, fix error mess…
andyzhangx May 3, 2026
547c715
fix: remove mountWithOAuthToken from shouldUseServiceAccountToken
andyzhangx May 3, 2026
977cb98
fix: use codes.Internal for setCredentialCacheWithOAuthToken errors i…
andyzhangx May 3, 2026
3fffe46
fix: improve error message when server cannot be resolved for mountWi…
andyzhangx May 3, 2026
e1e72e2
fix: return InvalidArgument for missing secretName and consolidate se…
andyzhangx May 5, 2026
1918f6c
test: update expected error code for missing secretName to InvalidArg…
andyzhangx May 5, 2026
85f32be
fix: return InvalidArgument for all validation errors in OAuth flow
andyzhangx May 5, 2026
5565e32
fix: extract status message to avoid double-wrapping, treat Unknown a…
andyzhangx May 5, 2026
5346a2c
simplify error handling for setCredentialCacheWithOAuthToken
andyzhangx May 9, 2026
8904019
fix: preserve gRPC status code from setCredentialCacheWithOAuthToken
andyzhangx May 9, 2026
11fd442
fix: return codes.Internal for setCredentialCache failures
andyzhangx May 9, 2026
4f17e7b
fix: return codes.Internal for secret fetch failures
andyzhangx May 9, 2026
4250b64
fix: address review comments
andyzhangx May 9, 2026
e4b4249
fix: reject mountWithOAuthToken for NFS and fix IMDS resource URL
andyzhangx May 10, 2026
cc88f7d
fix: also reject mountWithOAuthToken when fsType is nfs
andyzhangx May 13, 2026
f0802ca
test: add OIDC JWKS readiness check for workload identity e2e test
andyzhangx May 14, 2026
ac1d9d2
fix: propagate original gRPC status code from setCredentialCacheWithO…
andyzhangx May 14, 2026
9452ba5
address review: extract OAuth validation, improve logging and error m…
andyzhangx May 15, 2026
44d2691
fix: remove stale comment above validateMountWithOAuthToken
andyzhangx May 15, 2026
46bff09
fix: remove dead code and duplicate line from rebase
andyzhangx May 16, 2026
7a4d552
fix: move azidentity from indirect to direct dependency in go.mod
andyzhangx May 16, 2026
2e7cbc1
test: increase storage account limit from 17 to 20 in AfterSuite check
andyzhangx May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 36 additions & 11 deletions pkg/azurefile/azurefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,11 @@ const (
runtimeClassHandlerField = "runtimeclasshandler"
defaultRuntimeClassHandler = "kata-cc"
mountWithManagedIdentityField = "mountwithmanagedidentity"
mountWithOAuthTokenField = "mountwithoauthtoken"
mountWithWITokenField = "mountwithworkloadidentitytoken"

defaultSecretOAuthToken = "oauthtoken"

accountNotProvisioned = "StorageAccountIsNotProvisioned"
// this is a workaround fix for 429 throttling issue, will update cloud provider for better fix later
tooManyRequests = "TooManyRequests"
Expand Down Expand Up @@ -292,6 +295,8 @@ type Driver struct {
secretCacheMap azcache.Resource
// a map storing all volumes using data plane API <volumeID, value>
dataPlaneAPIVolMap sync.Map
// a map storing OAuth token SHA per server to avoid unnecessary credential cache refresh
oauthTokenSHAMap sync.Map
Comment thread
andyzhangx marked this conversation as resolved.
// a timed cache storing all storage accounts that are using data plane API temporarily
Comment thread
andyzhangx marked this conversation as resolved.
dataPlaneAPIAccountCache azcache.Resource
// a timed cache storing account search history (solve account list throttling issue)
Expand Down Expand Up @@ -824,7 +829,7 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r

var protocol, accountKey, secretName, pvcNamespace string
// getAccountKeyFromSecret indicates whether get account key only from k8s secret
var getAccountKeyFromSecret, getLatestAccountKey, mountWithManagedIdentity, mountWithWIToken bool
var getAccountKeyFromSecret, getLatestAccountKey, mountWithManagedIdentity, mountWithOAuthToken, mountWithWIToken bool
Comment thread
andyzhangx marked this conversation as resolved.
var clientID, tenantID, tokenFilePath, serviceAccountToken string

for k, v := range reqContext {
Expand Down Expand Up @@ -861,6 +866,10 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
if mountWithManagedIdentity, err = strconv.ParseBool(v); err != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, fmt.Errorf("invalid %s: %s in volume context", mountWithManagedIdentityField, v)
}
case mountWithOAuthTokenField:
if mountWithOAuthToken, err = strconv.ParseBool(v); err != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, fmt.Errorf("invalid %s: %s in volume context", mountWithOAuthTokenField, v)
}
case mountWithWITokenField:
if mountWithWIToken, err = strconv.ParseBool(v); err != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, fmt.Errorf("invalid %s: %s in volume context", mountWithWITokenField, v)
Expand Down Expand Up @@ -905,6 +914,21 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, nil
}

if mountWithOAuthToken {
Comment thread
andyzhangx marked this conversation as resolved.
klog.V(2).Infof("mountWithOAuthToken is true, use OAuth token from secret for mount")
// Read accountName from secret if not already set
if accountName == "" && secretName != "" {
name, _, _, err := d.GetStorageAccountFromSecret(ctx, secretName, secretNamespace)
if err != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, fmt.Errorf("failed to get account name from secret for mountWithOAuthToken: %v", err)
}
if name != "" {
accountName = name
}
}
return rgName, accountName, accountKey, fileShareName, diskName, subsID, tenantID, tokenFilePath, nil
}
Comment thread
andyzhangx marked this conversation as resolved.

if mountWithWIToken {
if clientID == "" {
clientID = d.cloud.Config.AzureAuthConfig.UserAssignedIdentityID
Expand Down Expand Up @@ -957,7 +981,7 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
if secretName != "" {
var name string
// 2. if not found in cache, get account key from kubernetes secret
name, accountKey, err = d.GetStorageAccountFromSecret(ctx, secretName, secretNamespace)
name, accountKey, _, err = d.GetStorageAccountFromSecret(ctx, secretName, secretNamespace)
if name != "" {
accountName = name
}
Expand Down Expand Up @@ -1354,7 +1378,7 @@ func (d *Driver) GetStorageAccesskey(ctx context.Context, accountOptions *storag
if secretName == "" {
secretName = fmt.Sprintf(secretNameTemplate, accountName)
}
_, accountKey, err := d.GetStorageAccountFromSecret(ctx, secretName, secretNamespace)
_, accountKey, _, err := d.GetStorageAccountFromSecret(ctx, secretName, secretNamespace)
if err != nil {
klog.V(2).Infof("could not get account(%s) key from secret(%s), error: %v, use cluster identity to get account key instead", accountOptions.Name, secretName, err)
accountKey, err = d.GetStorageAccesskeyWithSubsID(ctx, accountOptions.SubscriptionID, accountOptions.Name, accountOptions.ResourceGroup, accountOptions.GetLatestAccountKey)
Expand All @@ -1378,21 +1402,22 @@ func (d *Driver) GetStorageAccesskeyWithSubsID(ctx context.Context, subsID, acco
return d.cloud.GetStorageAccesskey(ctx, accountClient, account, resourceGroup, getLatestAccountKey)
}

// GetStorageAccountFromSecret get storage account key from k8s secret
// return <accountName, accountKey, error>
func (d *Driver) GetStorageAccountFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, error) {
// GetStorageAccountFromSecret get storage account key and OAuth token from k8s secret
// return <accountName, accountKey, oauthToken, error>
func (d *Driver) GetStorageAccountFromSecret(ctx context.Context, secretName, secretNamespace string) (string, string, string, error) {
if d.kubeClient == nil {
return "", "", fmt.Errorf("could not get account key from secret(%s): KubeClient is nil", secretName)
return "", "", "", fmt.Errorf("could not get credentials from secret(%s): KubeClient is nil", secretName)
}

secret, err := d.kubeClient.CoreV1().Secrets(secretNamespace).Get(ctx, secretName, metav1.GetOptions{})
if err != nil {
return "", "", fmt.Errorf("could not get secret(%v): %v", secretName, err)
return "", "", "", fmt.Errorf("could not get secret(%v): %v", secretName, err)
Comment thread
andyzhangx marked this conversation as resolved.
}

accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName][:]))
accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey][:]))
return accountName, accountKey, nil
accountName := strings.TrimSpace(string(secret.Data[defaultSecretAccountName]))
accountKey := strings.TrimSpace(string(secret.Data[defaultSecretAccountKey]))
oauthToken := strings.TrimSpace(string(secret.Data[defaultSecretOAuthToken]))
Comment thread
andyzhangx marked this conversation as resolved.
return accountName, accountKey, oauthToken, nil
Comment thread
andyzhangx marked this conversation as resolved.
}

// getSubnetResourceID get default subnet resource ID from cloud provider config
Expand Down
33 changes: 20 additions & 13 deletions pkg/azurefile/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}
var sku, subsID, resourceGroup, location, account, fileShareName, diskName, fsType, secretName string
var secretNamespace, pvcNamespace, protocol, customTags, storageEndpointSuffix, networkEndpointType, shareAccessTier, accountAccessTier, rootSquashType, tagValueDelimiter string
var createAccount, useSeretCache, matchTags, selectRandomMatchingAccount, getLatestAccountKey, encryptInTransit, mountWithManagedIdentity, mountWithWIToken bool
var createAccount, useSeretCache, matchTags, selectRandomMatchingAccount, getLatestAccountKey, encryptInTransit, mountWithManagedIdentity, mountWithWIToken, mountWithOAuthToken bool
var vnetResourceGroup, vnetName, vnetLinkName, publicNetworkAccess, subnetName, shareNamePrefix, fsGroupChangePolicy, useDataPlaneAPI, privateDNSZoneResourceGroup string
var requireInfraEncryption, disableDeleteRetentionPolicy, enableLFS, isMultichannelEnabled, allowSharedKeyAccess, allowCrossTenantReplication *bool
var provisionedBandwidthMibps, provisionedIops *int32
Expand Down Expand Up @@ -327,20 +327,33 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid %s: %s in storage class", mountWithWITokenField, v)
}
case mountWithOAuthTokenField:
mountWithOAuthToken, err = strconv.ParseBool(v)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid %s: %s in storage class", mountWithOAuthTokenField, v)
}
default:
return nil, status.Errorf(codes.InvalidArgument, "invalid parameter %q in storage class", k)
}
Comment thread
andyzhangx marked this conversation as resolved.
}

if mountWithManagedIdentity && mountWithWIToken {
return nil, status.Error(codes.InvalidArgument, "mountwithmanagedidentity and mountwithworkloadidentitytoken cannot be both true in storage class")
if (mountWithManagedIdentity && mountWithWIToken) || (mountWithManagedIdentity && mountWithOAuthToken) || (mountWithWIToken && mountWithOAuthToken) {
return nil, status.Errorf(codes.InvalidArgument, "only one of %s, %s, and %s can be true in storage class", mountWithManagedIdentityField, mountWithOAuthTokenField, mountWithWITokenField)
}
Comment thread
andyzhangx marked this conversation as resolved.

Comment thread
andyzhangx marked this conversation as resolved.
if mountWithOAuthToken && secretName == "" {
Comment thread
andyzhangx marked this conversation as resolved.
return nil, status.Errorf(codes.InvalidArgument, "%s is required when %s is true", secretNameField, mountWithOAuthTokenField)
}

if mountWithOAuthToken && (protocol == nfs || fsType == nfs) {
return nil, status.Errorf(codes.InvalidArgument, "%s is not supported with NFS protocol", mountWithOAuthTokenField)
}

// When using managed identity or workload identity token for mount,
// the account key should not be stored in the secret since mount
// authentication uses identity-based tokens, not account keys.
if mountWithManagedIdentity || mountWithWIToken {
var requiresSmbOAuth *bool
if mountWithManagedIdentity || mountWithWIToken || mountWithOAuthToken {
storeAccountKey = false
Comment thread
andyzhangx marked this conversation as resolved.
klog.V(2).Info("enabling smb oauth for identity-based mount")
requiresSmbOAuth = to.Ptr(true)
}
Comment thread
andyzhangx marked this conversation as resolved.

if matchTags && account != "" {
Expand Down Expand Up @@ -572,12 +585,6 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
}
}

var requiresSmbOAuth *bool
if mountWithManagedIdentity || mountWithWIToken {
klog.V(2).Info("enabling smb oauth for managed identity or work identity token based mount")
requiresSmbOAuth = to.Ptr(true)
}

accountOptions := &storage.AccountOptions{
Name: account,
Type: sku,
Expand Down
2 changes: 1 addition & 1 deletion pkg/azurefile/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,7 +1374,7 @@ var _ = ginkgo.Describe("TestCreateVolume", func() {
},
}

expectedErr := status.Errorf(codes.InvalidArgument, "%s and %s cannot be both true in storage class", mountWithManagedIdentityField, mountWithWITokenField)
expectedErr := status.Errorf(codes.InvalidArgument, "only one of %s, %s, and %s can be true in storage class", mountWithManagedIdentityField, mountWithOAuthTokenField, mountWithWITokenField)
_, err := d.CreateVolume(ctx, req)
gomega.Expect(err).To(gomega.Equal(expectedErr))
})
Expand Down
Loading
Loading