Skip to content

Commit 3d16cf0

Browse files
committed
feat: support SMB mount with managed identity
chore: add kubelet mount path chore: mkdir kubeberos dir fix fix install sudo curl revert fix mountOptions support clientID support clientID in sc fix sh script golint error fix fix config file creation on host add SETUP_MI_AUTH env var fix azfilesauth_1.0 package add azfilesrefresh sidecar feat: mount with managed identity auth
1 parent 841e519 commit 3d16cf0

File tree

10 files changed

+142
-22
lines changed

10 files changed

+142
-22
lines changed
140 Bytes
Binary file not shown.

charts/latest/azurefile-csi-driver/templates/csi-azurefile-node.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,16 @@ spec:
9696
value: "{{ .Values.linux.kubelet }}"
9797
- name: MIGRATE_K8S_REPO
9898
value: "{{ .Values.node.azurefileProxy.migrateK8sRepo }}"
99+
- name: SETUP_MI_AUTH
100+
value: "{{ .Values.node.azurefileProxy.setUpMIAuth }}"
99101
volumeMounts:
100102
- name: host-usr
101103
mountPath: /host/usr
102104
- name: host-etc
103105
mountPath: /host/etc
106+
- name: mountpoint-dir
107+
mountPath: /var/lib/kubelet/
108+
mountPropagation: Bidirectional
104109
containers:
105110
- name: liveness-probe
106111
volumeMounts:
@@ -140,6 +145,27 @@ spec:
140145
- name: registration-dir
141146
mountPath: /registration
142147
resources: {{- toYaml .Values.linux.resources.nodeDriverRegistrar | nindent 12 }}
148+
- name: azfilesrefresh
149+
{{- if hasPrefix "/" .Values.image.azurefile.repository }}
150+
image: "{{ .Values.image.baseRepo }}{{ .Values.image.azurefile.repository }}:{{ .Values.image.azurefile.tag }}"
151+
{{- else }}
152+
image: "{{ .Values.image.azurefile.repository }}:{{ .Values.image.azurefile.tag }}"
153+
{{- end }}
154+
imagePullPolicy: {{ .Values.image.azurefile.pullPolicy }}
155+
command:
156+
- "azfilesrefresh"
157+
securityContext:
158+
privileged: true
159+
capabilities:
160+
drop:
161+
- ALL
162+
volumeMounts:
163+
- mountPath: /var/lib/kubelet/
164+
mountPropagation: Bidirectional
165+
name: mountpoint-dir
166+
- name: host-etc
167+
mountPath: /etc
168+
resources: {{- toYaml .Values.linux.resources.azfilesrefresh | nindent 12 }}
143169
- name: azurefile
144170
{{- if hasPrefix "/" .Values.image.azurefile.repository }}
145171
image: "{{ .Values.image.baseRepo }}{{ .Values.image.azurefile.repository }}:{{ .Values.image.azurefile.tag }}"

charts/latest/azurefile-csi-driver/values.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ controller:
8888
requests:
8989
cpu: 10m
9090
memory: 20Mi
91+
azfilesrefresh:
92+
limits:
93+
cpu: 1
94+
memory: 100Mi
95+
requests:
96+
cpu: 10m
97+
memory: 20Mi
9198
azurefile:
9299
limits:
93100
cpu: 2
@@ -128,6 +135,7 @@ node:
128135
enabled: true
129136
installAznfsMount: true
130137
migrateK8sRepo: false
138+
setUpMIAuth: true
131139

132140
snapshot:
133141
enabled: false

deploy/csi-azurefile-node.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,16 @@ spec:
5858
value: "true"
5959
- name: INSTALL_AZNFS_MOUNT
6060
value: "true"
61+
- name: SETUP_MI_AUTH
62+
value: "true"
6163
volumeMounts:
6264
- name: host-usr
6365
mountPath: /host/usr
6466
- name: host-etc
6567
mountPath: /host/etc
68+
- name: mountpoint-dir
69+
mountPath: /var/lib/kubelet/
70+
mountPropagation: Bidirectional
6671
containers:
6772
- name: liveness-probe
6873
volumeMounts:
@@ -163,12 +168,36 @@ spec:
163168
name: device-dir
164169
- mountPath: /run/kata-containers/shared/direct-volumes
165170
name: kata-direct-volumes
171+
- name: host-etc
172+
mountPath: /etc
166173
resources:
167174
limits:
168175
memory: 400Mi
169176
requests:
170177
cpu: 10m
171178
memory: 20Mi
179+
- name: azfilesrefresh
180+
image: mcr.microsoft.com/k8s/csi/azurefile-csi:latest
181+
imagePullPolicy: IfNotPresent
182+
command:
183+
- "azfilesrefresh"
184+
securityContext:
185+
privileged: true
186+
capabilities:
187+
drop:
188+
- ALL
189+
volumeMounts:
190+
- mountPath: /var/lib/kubelet/
191+
mountPropagation: Bidirectional
192+
name: mountpoint-dir
193+
- name: host-etc
194+
mountPath: /etc
195+
resources:
196+
limits:
197+
memory: 100Mi
198+
requests:
199+
cpu: 10m
200+
memory: 20Mi
172201
volumes:
173202
- name: host-usr
174203
hostPath:

pkg/azurefile-proxy/init.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,28 @@
1717
set -xe
1818

1919
MIGRATE_K8S_REPO=${MIGRATE_K8S_REPO:-false}
20+
SETUP_MI_AUTH=${SETUP_MI_AUTH:-true}
2021

2122
KUBELET_PATH=${KUBELET_PATH:-/var/lib/kubelet}
2223
if [ "$KUBELET_PATH" != "/var/lib/kubelet" ];then
2324
echo "kubelet path is $KUBELET_PATH, update azurefile-proxy.service...."
2425
sed -i "s#--azurefile-proxy-endpoint[^ ]*#--azurefile-proxy-endpoint=unix:/${KUBELET_PATH}/plugins/file.csi.azure.com/azurefile-proxy.sock#" /azurefile-proxy/azurefile-proxy.service
2526
echo "azurefile-proxy endpoint is updated to unix:/$KUBELET_PATH/plugins/file.csi.azure.com/azurefile-proxy.sock"
26-
fi
27+
fi
2728

2829
HOST_CMD="nsenter --mount=/proc/1/ns/mnt"
2930

31+
if [ "$SETUP_MI_AUTH" = "true" ];then
32+
echo "set up /etc/krb5.conf on host"
33+
printf '[libdefaults]\ndefault_ccache_name = FILE:/var/lib/kubelet/kerberos/krb5cc_%s\n' "%{uid}" > /host/etc/krb5.conf
34+
35+
mkdir -p /var/lib/kubelet/kerberos
36+
37+
echo "set up /etc/azfilesauth/config.yaml on host"
38+
mkdir -p /host/etc/azfilesauth
39+
printf 'USER_UID: 0\nKRB5_CC_NAME: /var/lib/kubelet/kerberos/krb5cc_0\n' > /host/etc/azfilesauth/config.yaml
40+
fi
41+
3042
DISTRIBUTION=$($HOST_CMD cat /etc/os-release | grep ^ID= | cut -d'=' -f2 | tr -d '"')
3143
ARCH=$($HOST_CMD uname -m)
3244
echo "Linux distribution: $DISTRIBUTION, Arch: $ARCH"

pkg/azurefile/azurefile.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ const (
160160
premium = "premium"
161161
selectRandomMatchingAccountField = "selectrandommatchingaccount"
162162
accountQuotaField = "accountquota"
163+
mountWithManagedIdentityField = "mountwithmanagedidentity"
163164

164165
accountNotProvisioned = "StorageAccountIsNotProvisioned"
165166
// this is a workaround fix for 429 throttling issue, will update cloud provider for better fix later
@@ -776,7 +777,7 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
776777

777778
var protocol, accountKey, secretName, pvcNamespace string
778779
// getAccountKeyFromSecret indicates whether get account key only from k8s secret
779-
var getAccountKeyFromSecret, getLatestAccountKey bool
780+
var getAccountKeyFromSecret, getLatestAccountKey, mountWithManagedIdentity bool
780781
var clientID, tenantID, serviceAccountToken string
781782

782783
for k, v := range reqContext {
@@ -809,6 +810,10 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
809810
}
810811
case clientIDField:
811812
clientID = v
813+
case mountWithManagedIdentityField:
814+
if mountWithManagedIdentity, err = strconv.ParseBool(v); err != nil {
815+
return rgName, accountName, accountKey, fileShareName, diskName, subsID, fmt.Errorf("invalid %s: %s in volume context", mountWithManagedIdentityField, v)
816+
}
812817
case tenantIDField:
813818
tenantID = v
814819
case strings.ToLower(serviceAccountTokenField):
@@ -838,7 +843,11 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
838843
}
839844
}
840845

841-
// if client id is specified, we only use service account token to get account key
846+
if mountWithManagedIdentity {
847+
klog.V(2).Infof("mountWithManagedIdentity is true, use managed identity auth")
848+
return rgName, accountName, accountKey, fileShareName, diskName, subsID, nil
849+
}
850+
842851
if clientID != "" {
843852
klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
844853
accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)

pkg/azurefile/controllerserver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ func (d *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest)
228228
// no op, only used in NodeStageVolume
229229
case folderNameField:
230230
// no op, only used in NodeStageVolume
231+
case mountWithManagedIdentityField:
232+
// no op, only used in NodeStageVolume
231233
case clientIDField:
232234
// no op, only used in NodeStageVolume
233235
case fsGroupChangePolicyField:

pkg/azurefile/nodeserver.go

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
7676
mountPermissions := d.mountPermissions
7777
context := req.GetVolumeContext()
7878
if context != nil {
79-
// token request
80-
if getValueInMap(context, serviceAccountTokenField) != "" && getValueInMap(context, clientIDField) != "" {
79+
if !strings.EqualFold(getValueInMap(context, mountWithManagedIdentityField), trueValue) && getValueInMap(context, serviceAccountTokenField) != "" && getValueInMap(context, clientIDField) != "" {
8180
klog.V(2).Infof("NodePublishVolume: volume(%s) mount on %s with service account token, clientID: %s", volumeID, target, getValueInMap(context, clientIDField))
8281
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
8382
StagingTargetPath: target,
@@ -233,7 +232,7 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
233232
volumeID := req.GetVolumeId()
234233
context := req.GetVolumeContext()
235234

236-
if getValueInMap(context, clientIDField) != "" && getValueInMap(context, serviceAccountTokenField) == "" {
235+
if getValueInMap(context, clientIDField) != "" && !strings.EqualFold(getValueInMap(context, mountWithManagedIdentityField), trueValue) && getValueInMap(context, serviceAccountTokenField) == "" {
237236
klog.V(2).Infof("Skip NodeStageVolume for volume(%s) since clientID %s is provided but service account token is empty", volumeID, getValueInMap(context, clientIDField))
238237
return &csi.NodeStageVolumeResponse{}, nil
239238
}
@@ -262,9 +261,8 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
262261
}
263262
// don't respect fsType from req.GetVolumeCapability().GetMount().GetFsType()
264263
// since it's ext4 by default on Linux
265-
var fsType, server, protocol, ephemeralVolMountOptions, storageEndpointSuffix, folderName string
266-
var ephemeralVol bool
267-
var encryptInTransit bool
264+
var fsType, server, protocol, ephemeralVolMountOptions, storageEndpointSuffix, folderName, clientID string
265+
var ephemeralVol, encryptInTransit, mountWithManagedIdentity bool
268266
fileShareNameReplaceMap := map[string]string{}
269267

270268
mountPermissions := d.mountPermissions
@@ -298,7 +296,6 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
298296
fileShareNameReplaceMap[pvNameMetadata] = v
299297
case mountPermissionsField:
300298
if v != "" {
301-
var err error
302299
var perm uint64
303300
if perm, err = strconv.ParseUint(v, 8, 32); err != nil {
304301
return nil, status.Errorf(codes.InvalidArgument, "invalid mountPermissions %s", v)
@@ -310,11 +307,17 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
310307
}
311308
}
312309
case encryptInTransitField:
313-
var err error
314310
encryptInTransit, err = strconv.ParseBool(v)
315311
if err != nil {
316312
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Volume context property %q must be a boolean value: %v", k, err))
317313
}
314+
case mountWithManagedIdentityField:
315+
mountWithManagedIdentity, err = strconv.ParseBool(v)
316+
if err != nil {
317+
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Volume context property %q must be a boolean value: %v", k, err))
318+
}
319+
case clientIDField:
320+
clientID = v
318321
}
319322
}
320323

@@ -379,18 +382,29 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
379382
mountOptions = util.JoinMountOptions(mountFlags, []string{"vers=4,minorversion=1,sec=sys"})
380383
mountOptions = appendDefaultNfsMountOptions(mountOptions, d.appendNoResvPortOption, d.appendActimeoOption)
381384
} else {
382-
if accountName == "" || accountKey == "" {
383-
return nil, status.Errorf(codes.Internal, "accountName(%s) or accountKey is empty", accountName)
384-
}
385-
if runtime.GOOS == "windows" {
386-
mountOptions = []string{fmt.Sprintf("AZURE\\%s", accountName)}
387-
sensitiveMountOptions = []string{accountKey}
385+
if mountWithManagedIdentity && runtime.GOOS != "windows" {
386+
if clientID == "" {
387+
clientID = d.cloud.Config.AzureAuthConfig.UserAssignedIdentityID
388+
}
389+
sensitiveMountOptions = []string{"sec=krb5,cruid=0,upcall_target=mount", fmt.Sprintf("username=%s", clientID)}
390+
klog.V(2).Infof("using managed identity %s for volume %s with mount options: %v", clientID, volumeID, sensitiveMountOptions)
388391
} else {
389-
if err := os.MkdirAll(targetPath, os.FileMode(mountPermissions)); err != nil {
390-
return nil, status.Error(codes.Internal, fmt.Sprintf("MkdirAll %s failed with error: %v", targetPath, err))
392+
if accountName == "" || accountKey == "" {
393+
return nil, status.Errorf(codes.Internal, "accountName(%s) or accountKey is empty", accountName)
391394
}
392-
// parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/
393-
sensitiveMountOptions = []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)}
395+
if runtime.GOOS == "windows" {
396+
mountOptions = []string{fmt.Sprintf("AZURE\\%s", accountName)}
397+
sensitiveMountOptions = []string{accountKey}
398+
} else {
399+
if err := os.MkdirAll(targetPath, os.FileMode(mountPermissions)); err != nil {
400+
return nil, status.Error(codes.Internal, fmt.Sprintf("MkdirAll %s failed with error: %v", targetPath, err))
401+
}
402+
// parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/
403+
sensitiveMountOptions = []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)}
404+
}
405+
}
406+
407+
if runtime.GOOS != "windows" {
394408
if ephemeralVol {
395409
cifsMountFlags = util.JoinMountOptions(cifsMountFlags, strings.Split(ephemeralVolMountOptions, ","))
396410
}
@@ -434,6 +448,11 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe
434448
klog.V(2).Infof("mount with proxy succeeded for %s", cifsMountPath)
435449
} else {
436450
execFunc := func() error {
451+
if mountWithManagedIdentity && protocol != nfs && runtime.GOOS != "windows" {
452+
if out, err := setCredentialCache(server, clientID); err != nil {
453+
return fmt.Errorf("setCredentialCache failed for %s with error: %v, output: %s", server, err, out)
454+
}
455+
}
437456
return SMBMount(d.mounter, source, cifsMountPath, mountFsType, mountOptions, sensitiveMountOptions)
438457
}
439458
timeoutFunc := func() error { return fmt.Errorf("time out") }

pkg/azurefile/utils.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"fmt"
2222
"os"
23+
"os/exec"
2324
"regexp"
2425
"strconv"
2526
"strings"
@@ -374,3 +375,10 @@ func removeOptionIfExists(options []string, removeOption string) ([]string, bool
374375
}
375376
return options, false
376377
}
378+
379+
func setCredentialCache(server, clientID string) ([]byte, error) {
380+
cmd := exec.Command("azfilesauthmanager", "set", "https://"+server, "--imds-client-id", clientID)
381+
cmd.Env = append(os.Environ(), cmd.Env...)
382+
klog.V(2).Infof("Executing command: %q", cmd.String())
383+
return cmd.CombinedOutput()
384+
}

pkg/azurefileplugin/Dockerfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ARG ARCH
2222

2323
RUN apt update \
2424
&& apt install -y curl \
25+
&& curl -Lso /tmp/azfilesauth_amd64.deb https://raw.githubusercontent.com/andyzhangx/demo/refs/heads/master/aks/azfilesauth_1.0-4_${ARCH}.deb \
2526
&& curl -Ls https://azcopyvnext-awgzd8g7aagqhzhe.b02.azurefd.net/releases/release-10.29.1-20250515/azcopy_linux_${ARCH}_10.29.1.tar.gz \
2627
| tar xvzf - --strip-components=1 -C /usr/local/bin/ --wildcards "*/azcopy"
2728

@@ -33,18 +34,24 @@ ARG binary=./_output/${ARCH}/azurefileplugin
3334
COPY ${binary} /azurefileplugin
3435
COPY --from=builder --chown=root:root /usr/local/bin/azcopy /usr/local/bin/azcopy
3536

36-
RUN apt update && apt upgrade -y && apt-mark unhold libcap2 && clean-install ca-certificates cifs-utils util-linux e2fsprogs mount udev xfsprogs nfs-common netbase
37+
RUN apt update && apt upgrade -y && apt-mark unhold libcap2 && clean-install ca-certificates cifs-utils util-linux e2fsprogs mount udev xfsprogs nfs-common netbase curl python3-requests
3738

3839
COPY ./pkg/azurefile-proxy/init.sh /azurefile-proxy/
3940
COPY ./pkg/azurefile-proxy/install-proxy.sh /azurefile-proxy/
4041
COPY ./pkg/azurefile-proxy/azurefile-proxy.service /azurefile-proxy/
4142
COPY ./_output/${ARCH}/azurefile-proxy /azurefile-proxy/
4243

44+
COPY --from=builder --chown=root:root /tmp/azfilesauth_amd64.deb /azurefile-proxy/azfilesauth_amd64.deb
45+
4346
RUN chmod +x /azurefile-proxy/init.sh && \
4447
chmod +x /azurefile-proxy/install-proxy.sh && \
4548
chmod +x /azurefile-proxy/azurefile-proxy.service && \
4649
chmod +x /azurefile-proxy/azurefile-proxy
4750

51+
RUN if [ "$ARCH" = "amd64" ] ; then \
52+
clean-install libcurl4-gnutls-dev \
53+
&& apt update && apt install -y /azurefile-proxy/azfilesauth_amd64.deb; fi
54+
4855
LABEL maintainers="andyzhangx"
4956
LABEL description="AzureFile CSI Driver"
5057

0 commit comments

Comments
 (0)