この記事について
AKS上のシークレットをAzure Key Vaultに保存するシークレットストアCSIドライバを設定した。Key Vaultへの認証にworkload identityを使用しようとしたところ、なかなか苦戦したのでメモ。
環境
- Kunbernetes
- Kubernetes Version: 1.26.6
- Agentpoolのノードサイズと数: Standard_B2ms x 1
- Azure CLI
- version: 2.52.0
手順
基本的に以下のMSの公式の手順を踏んでいるが、一部そのままでは動かなかったため他の手順とマージしている。
AKSクラスタの設定
AKSクラスタを作成する。ポイントは以下。
- KeyVault シークレットドライバを有効にするために
azure-keyvault-secrets-provider
アドオンを有効化する。 - Workload identityを有効にするために、
--enable-oidc-issuer
と--enable-workload-identity
を指定する。
export AKS_CLUSTER_NAME="myAKSCluster"
export AKS_RG_NAME="myResourceGroup"
export LOCATION="japaneast"
az group create -n ${AKS_RG_NAME} -l ${LOCATION}
az aks create -n ${AKS_CLUSTER_NAME} -g ${AKS_RG_NAME} -l ${LOCATION} \
--enable-addons azure-keyvault-secrets-provider \
--enable-oidc-issuer \
--enable-workload-identity \
--enable-secret-rotation \
--node-vm-size Standard_B2ms \
--node-count 1
Azure KeyVaultの設定
Key Vaultを新しく作成する。
export KEYVAULT_NAME="your keyvault name"
export KEYVAULT_RG_NAME="myResourceGroup"
az keyvault create -n ${KEYVAULT_NAME} -g ${KEYVAULT_RG_NAME} -l ${LOCATION}
サンプルのシークレットを登録する
export KV_SECRET_USERNAME_KEY="username"
export KV_SECRET_USERNAME_VALUE="Obi-Wan"
export KV_SECRET_PASSWORD_KEY="password"
export KV_SECRET_PASSWORD_VALUE="MayThe4thBWU"
az keyvault secret set --vault-name ${KEYVAULT_NAME} \
-n ${KV_SECRET_USERNAME_KEY} --value ${KV_SECRET_USERNAME_VALUE}
az keyvault secret set --vault-name ${KEYVAULT_NAME} \
-n ${KV_SECRET_PASSWORD_KEY} --value ${KV_SECRET_PASSWORD_VALUE}
workload idの設定
OIDC IssuerのURLを取得し環境変数に格納する
export AKS_OIDC_ISSUER="$(az aks show -n "${AKS_CLUSTER_NAME}" -g "${AKS_RG_NAME}" --query "oidcIssuerProfile.issuerUrl" -otsv)"
Managed IDを作成し、クライアントIDとテナントIDを環境変数に格納する
export USER_ASSIGNED_ID_NAME="myIdentity"
export USER_ASSIGNED_ID_RG="myResourceGroup"
export SUBSCRIPTION="$(az account show --query id --output tsv)"
az identity create --name "${USER_ASSIGNED_ID_NAME}" \
--resource-group "${USER_ASSIGNED_ID_RG}" \
--location "${LOCATION}" \
--subscription "${SUBSCRIPTION}"
export USER_ASSIGNED_CLIENT_ID="$(az identity show --resource-group "${USER_ASSIGNED_ID_RG}" --name "${USER_ASSIGNED_ID_NAME}" --query 'clientId' -otsv)"
export IDENTITY_TENANT=$(az aks show --name ${AKS_CLUSTER_NAME} --resource-group ${AKS_RG_NAME} --query identity.tenantId -o tsv)
https://learn.microsoft.com/ja-jp/azure/aks/csi-secrets-store-identity-access#configure-workload-identity
上記のサイトには、Managed IDに対しKey Vault Administrator
のRoleを付与せよとあるが、これではうまく動かなかった。
https://learn.microsoft.com/ja-jp/azure/aks/workload-identity-deploy-cluster#optional---grant-permissions-to-access-azure-key-vault
上記のサイトには、Key Vaultのset-policy
の方を設定せよと書いてあり、こっちが正解。
Managed IDからKey Vaultへのアクセスを許可する。
az keyvault set-policy --name "${KEYVAULT_NAME}" \
--secret-permissions get --spn "${USER_ASSIGNED_CLIENT_ID}"
Kubernetesのサービスアカウントの作成
kubeconfigを取得する
az aks get-credentials -n ${AKS_CLUSTER_NAME} -g "${AKS_RG_NAME}"
ネームスペースを作成する。サービスアカウントはネームスペース毎に独立なので、このあとの手順で作成する各種Kubernetesリソースはすべて同じネームスペースを指定すること。
export MY_NAMESPACE="csidrivertest"
kubectl create namespace ${MY_NAMESPACE}
サービスアカウントを作成する
-
azure.workload.identity/client-id
のannotationで、先程作成したManaged IDと紐付ける
export SERVICE_ACCOUNT_NAME="workload-identity-sa"
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: "${USER_ASSIGNED_CLIENT_ID}"
name: "${SERVICE_ACCOUNT_NAME}"
namespace: "${MY_NAMESPACE}"
EOF
フェデレーションID資格情報を確立する。やっていることは、Managed ID側へのサービスアカウントの情報の登録。
export FEDERATED_IDENTITY_CREDENTIAL_NAME="myFedIdentity"
az identity federated-credential create \
--name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} \
--identity-name "${USER_ASSIGNED_ID_NAME}" \
--resource-group "${USER_ASSIGNED_ID_RG}" \
--issuer "${AKS_OIDC_ISSUER}" \
--subject system:serviceaccount:"${MY_NAMESPACE}":"${SERVICE_ACCOUNT_NAME}" --audience api://AzureADTokenExchange
Secret Provider Classの設定
Secret Provider Classを作成する。
- Podから環境変数としてクレデンシャルを参照する場合には、
secretObjects
の設定が必要。
export SECRET_PROVIDER_CLASS_NAME="azure-kv-spc"
export SECRET_NAME="azure-kv-secret"
export SECRET_USERNAME_KEY="username"
export SECRET_PASSWORD_KEY="password"
cat <<EOF | kubectl apply -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: ${SECRET_PROVIDER_CLASS_NAME}
namespace: ${MY_NAMESPACE}
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "false"
clientID: "${USER_ASSIGNED_CLIENT_ID}"
keyvaultName: ${KEYVAULT_NAME}
cloudName: ""
objects: |
array:
- |
objectName: ${KV_SECRET_USERNAME_KEY}
objectType: secret
objectVersion: ""
- |
objectName: ${KV_SECRET_PASSWORD_KEY}
objectType: secret
objectVersion: ""
tenantId: "${IDENTITY_TENANT}"
secretObjects:
- data:
- key: ${SECRET_USERNAME_KEY}
objectName: ${KV_SECRET_USERNAME_KEY}
- key: ${SECRET_PASSWORD_KEY}
objectName: ${KV_SECRET_PASSWORD_KEY}
secretName: ${SECRET_NAME}
type: Opaque
EOF
シークレットを参照するPodをデプロイする
以下、ポイント。
- シークレットを取得するためには、ボリュームとしてマウントする方法と、環境変数として参照する方法がある。
- 環境変数として参照する場合にも、ボリュームのマウントは必要。
export MY_POD_NAME="busybox-secrets-store-inline-wi"
cat <<EOF | kubectl apply -f -
kind: Pod
apiVersion: v1
metadata:
name: ${MY_POD_NAME}
namespace: ${MY_NAMESPACE}
spec:
serviceAccountName: ${SERVICE_ACCOUNT_NAME}
containers:
- name: busybox
image: registry.k8s.io/e2e-test-images/busybox:1.29-4
command:
- "/bin/sleep"
- "10000"
volumeMounts:
- name: secrets-store01-inline
mountPath: "/mnt/secrets-store"
readOnly: true
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: ${SECRET_NAME}
key: ${SECRET_USERNAME_KEY}
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: ${SECRET_NAME}
key: ${SECRET_PASSWORD_KEY}
volumes:
- name: secrets-store01-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: ${SECRET_PROVIDER_CLASS_NAME}
EOF
https://learn.microsoft.com/ja-jp/azure/aks/csi-secrets-store-identity-access#configure-workload-identity
上記のサイトにはmetadata.labelsにazure.workload.identity/use=true
を指定せよと記載されているが、今回は記載しなくても問題なく動作した。
(通常、workload identityを有効にするためには、Podのmetadata.labelsにazure.workload.identity/use=true
が必要。)
今回azure.workload.identity/use=true
を省略しても動作した理由は不明だが、SecretProviderClassがシークレットを取得してくれている(Podは認証通す必要がない?)からかもしれない。
動作確認
シークレットを取得。
k get secret -n ${MY_NAMESPACE}
以下のように、自動的にシークレットが作成されていることを確認。
NAME TYPE DATA AGE
azure-kv-secret Opaque 2 2s
次に、PodをDescribeしてみる。
k describe po -n ${MY_NAMESPACE} ${MY_POD_NAME}
以下、表示の抜粋。
-
/mnt/secrets-store
にシークレットがマウントされている - 環境変数として
SECRET_USERNAME
、SECRET_PASSWORD
が登録されている。
Name: busybox-secrets-store-inline-wi
Namespace: csidrivertest
Service Account: workload-identity-sa
Labels: <none>
Annotations: <none>
Status: Running
Containers:
busybox:
--- 中略 ---
Ready: True
Environment:
SECRET_USERNAME: <set to the key 'username' in secret 'azure-kv-secret'> Optional: false
SECRET_PASSWORD: <set to the key 'password' in secret 'azure-kv-secret'> Optional: false
Mounts:
/mnt/secrets-store from secrets-store01-inline (ro)
/var/run/secrets/azure/tokens from azure-identity-token (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-wt9hp (ro)
--- 中略 ---
Volumes:
secrets-store01-inline:
Type: CSI (a Container Storage Interface (CSI) volume source)
Driver: secrets-store.csi.k8s.io
FSType:
ReadOnly: true
VolumeAttributes: secretProviderClass=azure-kv-spc
シークレットがマウントされているパスをlsしてみる。
k exec -n ${MY_NAMESPACE} ${MY_POD_NAME} -- ls -l /mnt/secrets-store
以下の様に、ファイルとしてシークレットが格納されている。
lrwxrwxrwx 1 root root 15 Sep 17 13:36 password -> ..data/password
lrwxrwxrwx 1 root root 15 Sep 17 13:36 username -> ..data/username
まとめ
AKSのシークレットをAzure Key Vaultに保存し、認証をworkload identityで行うことができた。シークレットの管理を外部に任せつつ、namespace単位でアクセス権限を分けられるので、シークレットを安全に運用できそう。