Azure Kubernetes ServiceのPodからManaged identities(旧MSI)認証でKey Vaultの情報を取得する

Key Vaultに保存した情報をAKS上のPodから取得するような要件は良くあると思います。でもKey Vaultへの認証をどうしたものかと思っていました。Service Principal使えば良いのですが、AKSでもMSIを使えたら良いなと思い調べていたところ、以下の情報がドキュメントにありました。実際に試したので書いておきます。

(2020/04/10追記)英語で読むにしてもプレビューだったので勘違いしてましたが、 ja-jp ページでなく en-us ページを確認したらちょうどプレビュー外れてたので書き直しました。

Release Release 2020-03-16 · Azure/AKS · GitHub

docs.microsoft.com

前準備

バージョン 2.2.0 以上のAzure CLIで以下のコマンドで構築可能です。必要に応じてAzure CLIをアップデートします。

$ az version
{
  "azure-cli": "2.3.1",
  "azure-cli-command-modules-nspkg": "2.0.3",
  "azure-cli-core": "2.3.1",
  "azure-cli-nspkg": "3.0.4",
  "azure-cli-telemetry": "1.0.4",
  "extensions": {
    "azure-devops": "0.17.0"
  }
}

MSIが有効化されたAKSクラスタ作成

AKSでMSIを有効化するには、クラスタを作成する際に --enable-managed-identity のオプションを有効化します。

$ az aks create -g rg-my-resource-group -n my-aks-cluster --node-count 1 --enable-managed-identity

上記のコマンドで作成したクラスタでは、以下のように servicePrincipalProfile.clientId を見るとGUIDでなく、msi という文字列になっていることがわかります。

$ az aks show -g rg-my-resource-group -n my-aks-cluster --query servicePrincipalProfile
{
  "clientId": "msi"
}

実体はAzure AD側ではなく、自動生成されるMCから始まる方のリソースグループにマネージドIDという種類のリソースで入っています。

AKSのnodeにSSHでログインして /etc/kubernetes/azure.json を確認してみると my-aks-cluster-agentpool のAPP IDが割り当てられていることがわかります。(nodeにSSHする方法はこちら

認証情報取得できるか確認してみます。

# コンテナ立ち上げログイン
local$ kubectl run --generator=run-pod/v1 -it --rm aks-ssh --image=debian

# curlをインストール
root@aks-ssh:/# apt update && apt install -y curl

# MetadataからAccess Tokenを取得できることを確認
root@aks-ssh:/# curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F' -H Metadata:true -s
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSIsImtpZCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuYXp1cmUuY29tLyIsImlzcyI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzA4NTAyZDk1LTQ0NjQtNDU3YS1iZjhlLTA3YTU0ODM0OTczMC8iLCJpYXQiOjE1ODY1NzcyODAsIm5iZiI6MTU4NjU3NzI4MCwiZXhwIjoxNTg2NjYzOTgwLCJhaW8iOiI0MmRnWURoU1l6cm54VzdXZVdVT3Y5aHZjRDJmQmdBPSIsImFwcGlkIjoiNGQzN2RjZDEtNDgzNC00YjkzLTkzMWUtYzIzMDkyMzJkYmI3IiwiYXBwaWRhY3IiOiIyIiwiaWRwIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvMDg1MDJkOTUtNDQ2NC00NTdhLWJmOGUtMDdhNTQ4MzQ5NzMwLyIsIm9pZCI6ImQ0MGU2ODNlLWQ5OWYtNDNkZS1iOWQ0LWE4OTg2MjM4NTNkZCIsInN1YiI6ImQ0MGU2ODNlLWQ5OWYtNDNkZS1iOWQ0LWE4OTg2MjM4NTNkZCIsInRpZCI6IjA4NTAyZDk1LTQ0NjQtNDU3YS1iZjhlLTA3YTU0ODM0OTczMCIsInV0aSI6IklXaGFyZW5zZDB5NlJtTlhudzhEQUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfbWlyaWQiOiIvc3Vic2NyaXB0aW9ucy9iNmEwOWU5NS04Nzc2LTRhNzAtOWE5Yy00NjY4ZmVkZmQ0ZDQvcmVzb3VyY2Vncm91cHMvTUNfTXlSZXNvdXJjZUdyb3VwX015TWFuYWdlZENsdXN0ZXJfamFwYW5lYXN0L3Byb3ZpZGVycy9NaWNyb3NvZnQuTWFuYWdlZElkZW50aXR5L3VzZXJBc3NpZ25lZElkZW50aXRpZXMvTXlNYW5hZ2VkQ2x1c3Rlci1hZ2VudHBvb2wifQ.dINhbysiuKZxO-DX_O40AAb9HXk1IBZuzTWM-p33axRcJzTY9y_vSZWjz6vKiptlFbZSOgJ7Mv_J-bc6HX4uHNcA3ayWMBwngno4-Tr92TvXtGrvqDj3OE6GyRfvRXv18FWDnIdMftpzEFUCCUpsO-ISXwRsFd9LEpjyqocn_OcITU0wCXvmiHC2P-lwpahuTZvq3edkMckp2pPmOmO_kltJagrOhty7N9qLV_chKg8-W0UOxF3LGNZX4kXI2djRC-Ly0uic-3Ci_tjnniZREZEto6aLAtRHvoUTCWZFVvDd_HZkdSlcFcIjJdXgRo9vWisnTcIGh5uBTNyGnOQa3w","client_id":"4d37dcd1-4834-4b93-931e-c2309232dbb7","expires_in":"86399","expires_on":"1586663980","ext_expires_in":"86399","not_before":"1586577280","resource":"https://management.azure.com/","token_type":"Bearer"}

MSIへの権限割り当て

最低限の権限をMSIに割り当ててみます。まず、ACRからPullできないと始まらないので、ACRのPull権限を割り当てます。

ACR_RG_NAME=rg-my-resource-group
ACR_NAME=myacr
SP_DISPLAY_NAME=my-aks-cluster-agentpool
acr_id=$(az acr show -g ${ACR_RG_NAME} --name ${ACR_NAME} --query 'id' --output tsv)
sp_id=$(az ad sp list --query "[?displayName==\`${SP_DISPLAY_NAME}\`].objectId"  --output tsv)
az role assignment create --role "AcrPull" --assignee ${sp_id} --scope ${acr_id}

次にKey VaultのSecretもGetできるようにポリシーをセットしておきます。

$ az keyvault set-policy --name kv-my-vault --object-id $sp_id --secret-permissions get

Pythonの実行するPodの準備

やっとタイトルの話になります。MSIで認証してKey Vaultの情報を取得してみます。

以下のファイルを準備します。

my_app/
├── Dockerfile
├── app.py
└── requirements.txt
manifests/
└──  job-msi.yaml 

コンテナのビルド

アプリはKey Vaultの値を取得して表示するだけのシンプルなものです。 ManagedIdentityCredential() でシームレスにMSI使った認証ができるはずです。

KeyVaultの名前と、Secretの名前は環境変数で引き渡すようにしてます。

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

secret_name = os.environ.get("SECRET_NAME")
vault_name = os.environ.get("VAULT_URL")
vault_url = f"https://{vault_name}.vault.azure.net/"

# MSIで認証
credential = ManagedIdentityCredential()

# MSIの認証を使ってKey VaultからSecretをGet
kv_client = SecretClient(vault_url=vault_url, credential=credential)
secret_bundle = kv_client.get_secret(secret_name)

print(secret_bundle.name)
print(secret_bundle.value)

requirements.txtの内容です。

azure-identity
azure-keyvault-secrets

Dockerfileもシンプルなものです。

FROM python:3.7.7-slim-buster

WORKDIR /usr/src/app

COPY requirements.txt ./

RUN pip install -r requirements.txt

COPY ./app.py ./

CMD ["python","app.py"]

ACR上にビルドします。

$ az acr build  --registry myacr --image msi-test ./my_app

k8sのPod定義を準備

ジョブとして実行するためのk8sのManifestとして、job-msi.yaml を準備します。こちらもかなりシンプルなものです。

環境変数でKey Vaultの名前と、Secretの名前を設定しています。

apiVersion: batch/v1
kind: Job
metadata:
  name:  msi-test
spec:
  backoffLimit: 0
  completions: 1
  parallelism: 1
  template:
    spec:
      containers:
        - name: msi-test
          image: myacr.azurecr.io/msi-test
          env:
          - name: VAULT_URL
            value: kv-my-vault
          - name: SECRET_NAME
            value: my-secret-text
      restartPolicy: Never

実行してみる

Key VaultにSecretを設定する

Key VaultにSecretを設定します。この値が出力されるはずです。

$ az keyvault secret set  --vault-name kv-my-vault --name my-secret-text --value yomon8

先程作ったManifestファイル使ってJobを実行してみます。

$ kubectl apply -f manifests/job-msi.yaml 

Completeしています。どうやら認証通ってそう。

$ kubectl get po
NAME             READY   STATUS      RESTARTS   AGE
msi-test-dsdlp   0/1     Completed   0          5s

ちゃんとKey Vaultに設定したSecretの値が出力されていることが確認できました。

$ kubectl logs msi-test-dsdlp
my-secret-text
yomon8