Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit 15c7f99
Merge: 8388a01 7fe3ebc
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 27 09:37:50 2023 +0000

    Merge branch 'master' of https://github.com/kubernetes-sigs/azurefile-csi-driver into workload-identity

commit 8388a01
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 27 09:33:51 2023 +0000

    fix

commit 44c4812
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 27 06:15:20 2023 +0000

    fix

commit 09e5b67
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 27 04:00:47 2023 +0000

    fix

commit 354f4d7
Author: weizhichen <weizhichen@microsoft.com>
Date:   Tue Dec 26 08:29:07 2023 +0000

    helm

commit 960912d
Merge: 0c58154 5dd86f5
Author: weizhichen <weizhichen@microsoft.com>
Date:   Tue Dec 26 08:00:51 2023 +0000

    Merge branch 'master' of https://github.com/kubernetes-sigs/azurefile-csi-driver into workload-identity

commit 0c58154
Merge: e3adfa4 d519073
Author: weizhichen <weizhichen@microsoft.com>
Date:   Mon Dec 18 02:18:53 2023 +0000

    Merge branch 'master' of https://github.com/kubernetes-sigs/azurefile-csi-driver into workload-identity

commit e3adfa4
Author: weizhichen <weizhichen@microsoft.com>
Date:   Thu Dec 14 03:17:25 2023 +0000

    spell

commit 78c82b5
Author: weizhichen <weizhichen@microsoft.com>
Date:   Thu Dec 14 03:09:16 2023 +0000

    doc

commit 7bb959f
Author: weizhichen <weizhichen@microsoft.com>
Date:   Thu Dec 14 03:05:08 2023 +0000

    doc

commit 2dde59d
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 13 14:45:37 2023 +0000

    doc

commit e774ff5
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 13 13:46:37 2023 +0000

    update go mod

commit 2e79ca3
Merge: 7846026 6cfe218
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 13 13:19:41 2023 +0000

    Merge branch 'master' of https://github.com/kubernetes-sigs/azurefile-csi-driver into workload-identity

commit 7846026
Author: weizhichen <weizhichen@microsoft.com>
Date:   Wed Dec 13 13:07:48 2023 +0000

    update go mod

commit 0446a46
Author: weizhichen <weizhichen@microsoft.com>
Date:   Fri Nov 24 08:52:03 2023 +0000

    update cloud-provider-azure

commit ff2aeb4
Author: weizhichen <weizhichen@microsoft.com>
Date:   Tue Nov 14 12:29:14 2023 +0000

    doc

commit 633641b
Author: weizhichen <weizhichen@microsoft.com>
Date:   Tue Nov 14 12:01:02 2023 +0000

    add docs

commit afcb818
Author: weizhichen <weizhichen@microsoft.com>
Date:   Tue Nov 14 11:20:40 2023 +0000

    feat: support workload identity setting in static PV mount on AKS
  • Loading branch information
cvvz committed Dec 27, 2023
1 parent 7fe3ebc commit 64c67e3
Show file tree
Hide file tree
Showing 30 changed files with 404 additions and 52 deletions.
Binary file modified charts/latest/azurefile-csi-driver-v0.0.0.tgz
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ spec:
- Persistent
- Ephemeral
fsGroupPolicy: {{ .Values.feature.fsGroupPolicy }}
tokenRequests:
- audience: api://AzureADTokenExchange
File renamed without changes.
179 changes: 179 additions & 0 deletions docs/workload-identity-static-pv-mount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Example of static PV mount with workload identity

> Note:
> - Available kubernetes version >= v1.20
## prerequisite


### 1. Create a cluster with oidc-issuer enabled and get the credential

Following the [documentation](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer#create-an-aks-cluster-with-oidc-issuer) to create an AKS cluster with the `--enable-oidc-issuer` parameter and get the AKS credentials. And export following environment variables:
```
export RESOURCE_GROUP=<your resource group name>
export CLUSTER_NAME=<your cluster name>
export REGION=<your region>
```


### 2. Create a new storage account and fileshare

Following the [documentation](https://learn.microsoft.com/en-us/azure/storage/files/storage-how-to-use-files-portal?tabs=azure-cli) to create a new storage account and fileshare or use your own. And export following environment variables:
```
export ACCOUNT=<your storage account name>
export SHARE=<your fileshare name>
```

### 3. Create managed identity and role assignment
```
export UAMI=<your managed identity name>
az identity create --name $UAMI --resource-group $RESOURCE_GROUP
export USER_ASSIGNED_CLIENT_ID="$(az identity show -g $RESOURCE_GROUP --name $UAMI --query 'clientId' -o tsv)"
export IDENTITY_TENANT=$(az aks show --name $CLUSTER_NAME --resource-group $RESOURCE_GROUP --query identity.tenantId -o tsv)
export ACCOUNT_SCOPE=$(az storage account show --name $ACCOUNT --query id -o tsv)
az role assignment create --role "Storage Account Contributor" --assignee $USER_ASSIGNED_CLIENT_ID --scope $ACCOUNT_SCOPE
```

### 4. Create service account on AKS
```
export SERVICE_ACCOUNT_NAME=<your sa name>
export SERVICE_ACCOUNT_NAMESPACE=<your sa namespace>
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${SERVICE_ACCOUNT_NAME}
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
EOF
```

### 5. Create the federated identity credential between the managed identity, service account issuer, and subject using the `az identity federated-credential create` command.
```
export FEDERATED_IDENTITY_NAME=<your federated identity name>
export AKS_OIDC_ISSUER="$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --query "oidcIssuerProfile.issuerUrl" -o tsv)"
az identity federated-credential create --name $FEDERATED_IDENTITY_NAME \
--identity-name $UAMI \
--resource-group $RESOURCE_GROUP \
--issuer $AKS_OIDC_ISSUER \
--subject system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}
```

## option#1: static provision with PV
```
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: file.csi.azure.com
name: pv-azurefile
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: azurefile-csi
mountOptions:
- dir_mode=0777
- file_mode=0777
- uid=0
- gid=0
- mfsymlinks
- cache=strict
- nosharesock
csi:
driver: file.csi.azure.com
# make sure volumeid is unique for every identical share in the cluster
# the # character is reserved for internal use
volumeHandle: unique_volume_id
volumeAttributes:
storageaccount: $ACCOUNT # required
shareName: $SHARE # required
clientID: $USER_ASSIGNED_CLIENT_ID # required
resourcegroup: $RESOURCE_GROUP # required, please make sure your account is NOT created under AKS node resource group(prefix with `MC_`)
# tenantID: $IDENTITY_TENANT #optional, only specified when workload identity and AKS cluster are in different tenant
# subscriptionid: $SUBSCRIPTION #optional, only specified when workload identity and AKS cluster are in different subscription
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-azurefile
labels:
app: nginx
spec:
podManagementPolicy: Parallel
serviceName: statefulset-azurefile
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
serviceAccountName: $SERVICE_ACCOUNT_NAME #required, Pod does not use this service account has no permission to mount the volume
nodeSelector:
"kubernetes.io/os": linux
containers:
- name: statefulset-azurefile
image: mcr.microsoft.com/oss/nginx/nginx:1.19.5
command:
- "/bin/bash"
- "-c"
- set -euo pipefail; while true; do echo $(date) >> /mnt/azurefile/outfile; sleep 1; done
volumeMounts:
- name: persistent-storage
mountPath: /mnt/azurefile
updateStrategy:
type: RollingUpdate
selector:
matchLabels:
app: nginx
volumeClaimTemplates:
- metadata:
name: persistent-storage
spec:
storageClassName: azurefile-csi
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 10Gi
EOF
```

## option#2: Pod with ephemeral inline volume
```
cat <<EOF | kubectl apply -f -
kind: Pod
apiVersion: v1
metadata:
name: nginx-azurefile-inline-volume
spec:
serviceAccountName: $SERVICE_ACCOUNT_NAME #required, Pod does not use this service account has no permission to mount the volume
nodeSelector:
"kubernetes.io/os": linux
containers:
- image: mcr.microsoft.com/oss/nginx/nginx:1.19.5
name: nginx-azurefile
command:
- "/bin/bash"
- "-c"
- set -euo pipefail; while true; do echo $(date) >> /mnt/azurefile/outfile; sleep 1; done
volumeMounts:
- name: persistent-storage
mountPath: "/mnt/azurefile"
volumes:
- name: persistent-storage
csi:
driver: file.csi.azure.com
volumeAttributes:
storageaccount: $ACCOUNT # required
shareName: $SHARE # required
clientID: $USER_ASSIGNED_CLIENT_ID # required
resourcegroup: $RESOURCE_GROUP # optional, specified when the storage account is not under AKS node resource group(which is prefixed with `MC_`)
# tenantID: $IDENTITY_TENANT # optional, only specified when workload identity and AKS cluster are in different tenant
# subscriptionid: $SUBSCRIPTION # optional, only specified when workload identity and AKS cluster are in different subscription
EOF
```
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ require (
k8s.io/mount-utils v0.29.0
k8s.io/pod-security-admission v0.27.4
k8s.io/utils v0.0.0-20231127182322-b307cd553661
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231208091050-e3e6d2dc1575
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231213062409-f1ce7de3fdcb
sigs.k8s.io/yaml v1.4.0
)

require (
sigs.k8s.io/cloud-provider-azure/pkg/azclient v0.0.0-20231208091050-e3e6d2dc1575
sigs.k8s.io/cloud-provider-azure/pkg/azclient/configloader v0.0.0-20231208091050-e3e6d2dc1575
sigs.k8s.io/yaml v1.4.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,8 @@ k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0=
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231208091050-e3e6d2dc1575 h1:UihOKKsKQBM8HLOHspLFZ5TRGTIAYkehNg8yUhcBWh0=
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231208091050-e3e6d2dc1575/go.mod h1:gMeGwBCdjFKaDIS2D+i+YfDJKAOjeeAt1SV2tO89HVc=
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231213062409-f1ce7de3fdcb h1:YApm24ngCVkpQTUxu0/wYV/oiccfqWEPZnX1BbpadKY=
sigs.k8s.io/cloud-provider-azure v1.27.1-0.20231213062409-f1ce7de3fdcb/go.mod h1:UkVMiNELbKLa07K/ubQ+vg8AK3XFyd2FMr5vCIYk0Pg=
sigs.k8s.io/cloud-provider-azure/pkg/azclient v0.0.0-20231208091050-e3e6d2dc1575 h1:iy/dQ2LBVWc39dzqQPVeIk9w2YyJEUzyUGbHhJ+FvHo=
sigs.k8s.io/cloud-provider-azure/pkg/azclient v0.0.0-20231208091050-e3e6d2dc1575/go.mod h1:dckGAqm0wUQNqqvCEeWhfXKL7DB/r9zchDq9xdcF/Qk=
sigs.k8s.io/cloud-provider-azure/pkg/azclient/configloader v0.0.0-20231208091050-e3e6d2dc1575 h1:3kuJE8InuN7dFI486rf3wk50h344HwvFDLMQaT7n+T0=
Expand Down
27 changes: 25 additions & 2 deletions pkg/azurefile/azurefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ const (
fsGroupChangePolicyField = "fsgroupchangepolicy"
ephemeralField = "csi.storage.k8s.io/ephemeral"
podNamespaceField = "csi.storage.k8s.io/pod.namespace"
serviceAccountTokenField = "csi.storage.k8s.io/serviceAccount.tokens"
clientIDField = "clientID"
tenantIDField = "tenantID"
mountOptionsField = "mountoptions"
mountPermissionsField = "mountpermissions"
falseValue = "false"
Expand Down Expand Up @@ -714,6 +717,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 bool
var clientID, tenantID, serviceAccountToken string

for k, v := range reqContext {
switch strings.ToLower(k) {
Expand Down Expand Up @@ -743,9 +747,18 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
if getLatestAccountKey, err = strconv.ParseBool(v); err != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, fmt.Errorf("invalid %s: %s in volume context", getLatestAccountKeyField, v)
}
case strings.ToLower(clientIDField):
clientID = v
case strings.ToLower(tenantIDField):
tenantID = v
case strings.ToLower(serviceAccountTokenField):
serviceAccountToken = v
}
}

if tenantID == "" {
tenantID = d.cloud.TenantID
}
if rgName == "" {
rgName = d.cloud.ResourceGroup
}
Expand All @@ -765,8 +778,16 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
}
}

// if client id is specified, we only use service account token to get account key
if clientID != "" {
klog.V(2).Infof("clientID(%s) is specified, use service account token to get account key", clientID)
accountKey, err := d.cloud.GetStorageAccesskeyFromServiceAccountToken(ctx, subsID, accountName, rgName, clientID, tenantID, serviceAccountToken)
return rgName, accountName, accountKey, fileShareName, diskName, subsID, err
}

if len(secrets) == 0 {
// read account key from cache first
// if request context does not contain secrets, get secrets in the following order:
// 1. get account key from cache first
cache, errCache := d.accountCacheMap.Get(accountName, azcache.CacheReadTypeDefault)
if errCache != nil {
return rgName, accountName, accountKey, fileShareName, diskName, subsID, errCache
Expand All @@ -779,11 +800,13 @@ 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)
if name != "" {
accountName = name
}
if err != nil {
// 3. if failed to get account key from kubernetes secret, use cluster identity to get account key
klog.Warningf("GetStorageAccountFromSecret(%s, %s) failed with error: %v", secretName, secretNamespace, err)
if !getAccountKeyFromSecret && d.cloud.StorageAccountClient != nil && accountName != "" {
klog.V(2).Infof("use cluster identity to get account key from (%s, %s, %s)", subsID, rgName, accountName)
Expand All @@ -795,7 +818,7 @@ func (d *Driver) GetAccountInfo(ctx context.Context, volumeID string, secrets, r
}
}
}
} else {
} else { // if request context contains secrets, get account name and key directly
var account string
account, accountKey, err = getStorageAccount(secrets)
if account != "" {
Expand Down
28 changes: 28 additions & 0 deletions pkg/azurefile/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ func (d *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu
mountPermissions := d.mountPermissions
context := req.GetVolumeContext()
if context != nil {
// token request
if context[serviceAccountTokenField] != "" && getClientID(context) != "" {
klog.V(2).Infof("NodePublishVolume: volume(%s) mount on %s with service account token, clientID: %s", volumeID, target, getClientID(context))
_, err := d.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{
StagingTargetPath: target,
VolumeContext: context,
VolumeCapability: volCap,
VolumeId: volumeID,
})
return &csi.NodePublishVolumeResponse{}, err
}

// ephemeral volume
if strings.EqualFold(context[ephemeralField], trueValue) {
setKeyValueInMap(context, secretNamespaceField, context[podNamespaceField])
if !d.allowInlineVolumeKeyAccessWithIdentity {
Expand Down Expand Up @@ -155,6 +168,12 @@ func (d *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRe

volumeID := req.GetVolumeId()
context := req.GetVolumeContext()

if getClientID(context) != "" && context[serviceAccountTokenField] == "" {
klog.V(2).Infof("Skip NodeStageVolume for volume(%s) since clientID %s is provided but service account token is empty", volumeID, getClientID(context))
return &csi.NodeStageVolumeResponse{}, nil
}

mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
volumeMountGroup := req.GetVolumeCapability().GetMount().GetVolumeMountGroup()
gidPresent := checkGidPresentInMountFlags(mountFlags)
Expand Down Expand Up @@ -595,3 +614,12 @@ func checkGidPresentInMountFlags(mountFlags []string) bool {
}
return false
}

func getClientID(context map[string]string) string {
for k, v := range context {
if strings.EqualFold(k, clientIDField) && v != "" {
return v
}
}
return ""
}
Loading

0 comments on commit 64c67e3

Please sign in to comment.