APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Azure Kubernetes Service でAzure AD ワークロード IDを使ってみた

はじめに

こんにちは、ACS事業部の吉川です。

本記事はQiitaのAzure Advent Calendar 2022の2日目の記事です。

qiita.com

さて、先月行われたMicrosoft Igniteで発表されたアップデートの中に、以下のものがありました。

azure.microsoft.com

個人的に待ち望んでいたアップデートなので、ここで紹介したいと思います。

従来のAKSにおける問題

Azure上のサーバーからストレージアカウントやKey Vaultなどの他のAzureリソースにアクセスする際、マネージドID を使って認証を行うのが一般的です。
AKS上のPodからもマネージドIDを利用できるのですが、ここに大きな問題があります。 マネージドIDの紐づく範囲がAKSクラスター全体であるため、以下の図に示すように 本来権限を与えたいPod以外からもマネージドIDが利用できてしまいます

単一のアプリケーションを動かしているだけのAKSクラスターであればこれでも問題ないでしょうが、複数のアプリケーションを動かすことを考えたときに権限分離ができないのは大きなマイナスです。
この問題を解消するのが Azure AD ワークロード ID です。

仕組みの概要

Azure AD ワークロードIDは、KubernetesのサービスアカウントとマネージドIDを紐づける機構です。
以下のような形で、Kubernetesのサービスアカウントに対しマネージドIDを紐づける ことができるため、1クラスター内のPodそれぞれに別の権限を割り当てることができます。

利用方法

ここからは実際に利用する方法を解説していきます。
公式ドキュメントも存在しますので、合わせてご参照ください。

learn.microsoft.com

1. プレビュー機能を有効化する

Azure AD ワークロードIDは現在プレビューの状態です。
これを利用するためにはAzure CLIへの拡張機能のインストール、およびサブスクリプションでの機能フラグ登録が必要です。

# Azure CLIの拡張機能インストール
az extension add --name aks-preview

# 過去にインストール済みの場合は以下コマンドで最新化する
az extension update --name aks-preview

# サブスクリプションで機能フラグを登録する
az feature register --namespace "Microsoft.ContainerService" --name "EnableWorkloadIdentityPreview"

# 登録完了まで数分かかる。以下コマンドの出力結果がRegisteredになればOK。
az feature list -o table --query "[?contains(name, 'Microsoft.ContainerService/EnableWorkloadIdentityPreview')].{Name:name,State:properties.state}"

# 機能フラグの登録が完了したら以下コマンドを実行する。
az provider register --namespace Microsoft.ContainerService

2. 必要なリソースの作成

必要となるAzure上のリソースを作成します。
AKSクラスターと、マネージドIDでアクセスする先のリソースとしてKey Vaultを作成します。
AKSクラスター作成時に --enable-oidc-issuer --enable-workload-identity というオプションを付ける必要があります。

# リソース名/リージョンの定義
export RG_NAME="rg-sample"
export KEYVAULT_NAME="kvsample"
export AKS_NAME="aks-sample"
export LOCATION="japaneast"

# リソースグループ作成
az group create --location $LOCATION --name $RG_NAME

# Key Vault作成
az keyvault create -g $RG_NAME -n $KEYVAULT_NAME -l $LOCATION

# AKSクラスター作成
az aks create -g $RG_NAME -n $AKS_NAME -l $LOCATION \
  --enable-oidc-issuer --enable-workload-identity \
  --node-count 1

# kubectlのインストール
sudo az aks install-cli

# AKSの認証情報取得
az aks get-credentials -g $RG_NAME -n $AKS_NAME

3. マネージドIDの作成

サービスアカウントと紐づけるマネージドIDを作成します。

MANAGEDID_NAME="mi-sample"

az identity create -n $MANAGEDID_NAME -g $RG_NAME -l $LOCATION

作成したマネージドIDがKey Vaultのシークレットにアクセスできるよう、アクセスポリシーの設定も行います。 ここでは、Key Vault内のシークレットに対する全ての操作を許可する設定を入れています。

export USER_ASSIGNED_CLIENT_ID="$(az identity show -g "${RG_NAME}" -n "${MANAGEDID_NAME}" --query 'clientId' -o tsv)"

az keyvault set-policy -n "${KEYVAULT_NAME}" --secret-permissions all --spn "${USER_ASSIGNED_CLIENT_ID}"

4. サービスアカウント作成

以下のコマンドでAKS上にサービスアカウントを作成します。

export SERVICE_ACCOUNT_NAME="workload-identity-sa"
export SERVICE_ACCOUNT_NAMESPACE="default"

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    azure.workload.identity/client-id: "${USER_ASSIGNED_CLIENT_ID}"
  labels:
    azure.workload.identity/use: "true"
  name: "${SERVICE_ACCOUNT_NAME}"
  namespace: "${SERVICE_ACCOUNT_NAMESPACE}"
EOF

5. フェデレーション資格情報の登録

マネージドIDとAKS上のサービスアカウントを紐づける、フェデレーション資格情報の登録を行います。

export AKS_OIDC_ISSUER="$(az aks show -n ${AKS_NAME} -g ${RG_NAME} --query "oidcIssuerProfile.issuerUrl" -o tsv)"

az identity federated-credential create --name myfederatedIdentity \
  --identity-name "${MANAGEDID_NAME}" \
  --resource-group "${RG_NAME}" --issuer "${AKS_OIDC_ISSUER}" \
  --subject system:serviceaccount:"${SERVICE_ACCOUNT_NAMESPACE}":"${SERVICE_ACCOUNT_NAME}"

上記のコマンド実行後、ポータルでマネージドIDのリソースを表示すると、以下のように設定が入っていることが確認できます。

これで設定は完了です!

Podから利用してみる

実際にPodを起動して、意図した挙動となるか確認してみましょう。
以下のように、Podにサービスアカウントを割り当てて起動します。

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  namespace: default
spec:
  serviceAccountName: ${SERVICE_ACCOUNT_NAME}
  containers:
  - name: test
    image: python:3.11
    command: ["sleep", "3600"]
    env:
    - name: KEY_VAULT_NAME
      value: ${KEYVAULT_NAME}
EOF

Podが起動したら以下コマンドでBashを起動しましょう。

kubectl exec -it test-pod -- /bin/bash

Key Vaultのシークレットを読み書きする以下のPythonスクリプトを動かしてみましょう。 こちらから持ってきたものです。

learn.microsoft.com

import os
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

keyVaultName = os.environ["KEY_VAULT_NAME"]
KVUri = f"https://{keyVaultName}.vault.azure.net"

credential = DefaultAzureCredential()
client = SecretClient(vault_url=KVUri, credential=credential)

secretName = input("Input a name for your secret > ")
secretValue = input("Input a value for your secret > ")

print(f"Creating a secret in {keyVaultName} called '{secretName}' with the value '{secretValue}' ...")

client.set_secret(secretName, secretValue)

print(" done.")

print(f"Retrieving your secret from {keyVaultName}.")

retrieved_secret = client.get_secret(secretName)

print(f"Your secret is '{retrieved_secret.value}'.")
print(f"Deleting your secret from {keyVaultName} ...")

poller = client.begin_delete_secret(secretName)
deleted_secret = poller.result()

print(" done.")

上記のPythonスクリプトを kv_secrets.py という名前で作成し、以下コマンドで起動します。

pip install azure-identity azure-keyvault-secrets
python kv_secrets.py

対話型でシークレットの名前・値を入力すると以下のように 作成 → 読み出し → 削除 の順に処理が動きます。
サービスアカウントに紐づくマネージドIDに割り当てた権限によって、これらの操作が実現できています。

Incomplete environment configuration. These variables are set: AZURE_CLIENT_ID, AZURE_TENANT_ID
Input a name for your secret > test-secret  # ← Key Vault内に作成するシークレットの名前を入力する
Input a value for your secret > test-value  # ← Key Vault内に作成するシークレットの値を入力する
Creating a secret in kvsample called 'test-secret' with the value 'test-value' ...
 done.  # ※シークレットの作成が完了したメッセージ
Retrieving your secret from kvsample.
Your secret is 'test-value'.  # ※保存したシークレットの読み出しが完了したメッセージ
Deleting your secret from kvsample ... # ※シークレットの削除が完了したメッセージ
 done.

以下のようにサービスアカウントを割り当てていないPodを起動して同じことを実行しようとすると、権限エラーとなることが確認できます。

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ng-pod
  namespace: default
spec:
  containers:
  - name: test
    image: python:3.11
    command: ["sleep", "3600"]
    env:
    - name: KEY_VAULT_NAME
      value: ${KEYVAULT_NAME}
EOF

kubectl exec -it ng-pod -- /bin/bash
pip install azure-identity azure-keyvault-secrets
python kv_secrets.py
Input a name for your secret > ng-secret
Input a value for your secret > ng-value
Creating a secret in kvsample called 'ng-secret' with the value 'ng-value' ...
Traceback (most recent call last):
~中略~
Inner error: {
    "code": "AccessDenied"
}

本記事冒頭に記載した、1クラスター内のPodそれぞれに別の権限を割り当てる が実現できています!
今回は一例としてKey Vaultにアクセスする例を紹介しましたが、マネージドIDで認証可能なものであれば他のAzureサービスに対しても同様の設定が可能です。

おわりに

ひとつのAKSクラスター内で複数アプリを起動している場合、権限分離したいケースもあるかと思います。
従来でもサービスプリンシパルを別途割り当ててあげれば同様のことは実現できましたが、より取り扱いやすいマネージドIDが利用できるようになったのは便利なアップデートですね。
AKSにおけるAzure ADワークロードIDは現時点ではプレビューの状態です。GAが待ち遠しいですね!

本記事の投稿者: 吉川 俊甫
AKS/ACAをメインにインフラ系のご支援を担当しています。
Shunsuke Yoshikawa - Credly