--- title: KindでAzure ADのOpenID Connectと連携してRBACを設定するメモ tags: ["Kubernetes", "Kind", "Azure AD", "OIDC", "OpenID Connect"] categories: ["Dev", "CaaS", "Kubernetes", "kind"] date: 2023-08-03T07:01:10Z updated: 2023-08-04T01:43:51Z --- Kindで作るK8sクラスタに対してOIDCを設定したく、OIDC ProviderとしてAzure ADを使用するメモです。 RBACの検証をローカルで行いたい場合や、任意のk8sクラスタでOIDC連携したい場合に使えます。 > * EKSでOIDC連携する方法 https://docs.aws.amazon.com/eks/latest/userguide/authenticate-oidc-identity-provider.html > * GKEでOIDC連携する方法 https://cloud.google.com/kubernetes-engine/docs/how-to/oidc **目次** ### Azure ADでアプリケーションの登録 https://azure.github.io/kubelogin/topics/k8s-oidc-aad.html を参考に以下の手順でAzure AD上でアプリケーション (OIDCクライアント) を登録します。ここでは`kind-oidc`という名前で作成します。 > ℹ️ Azure ADのアプリケーション作成に関しては https://www.pomerium.com/docs/identity-providers/azure も参考にしました。 image "Allow public client flows"を"Yes"にします。 image "Microsoft Graph"でpermissionを追加します。 image `Directory.Read.All`、`Group.Read.All`、`User.Read.All`のpermissionを追加します。 image "Grant admin content for xxxx"を有効にします。 image image image `groups` claimを追加します。 image image OverviewからApplication (client) IDとDirectory (tenant) IDをコピーします。 image ``` AAD_CLIENT_ID=***** AAD_TENANT_ID=***** ``` `https://sts.windows.net/$AAD_TENANT_ID` がOIDCのissuer urlになるので、次のURLでメタデータを取得できます。 ``` curl -s https://sts.windows.net/$AAD_TENANT_ID/.well-known/openid-configuration ``` ### kubeloginのインストール Azure ADから`kubectl`用のアクセストークンを取得するために、[`kubelogin`](https://github.com/Azure/kubelogin)を使用します。 以下のドキュメントに従ってインストールします。 https://azure.github.io/kubelogin/install.html ``` brew install Azure/kubelogin/kubelogin ``` 次のコマンドを実行してトークンを取得できます。 ``` $ kubelogin get-token --environment AzurePublicCloud --server-id $AAD_CLIENT_ID --client-id $AAD_CLIENT_ID --tenant-id $AAD_TENANT_ID To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code IU3PMB29G to authenticate. ``` 出力されたメッセージにしたがって、ブラウザでログインします。 image ログインが成功するとトークンが出力されます。 このトークンは`~/.kube/cache/kubelogin`以下にキャッシュされています。`kubelogin remove-tokens`でキャッシュを削除できます。 以下のコマンドでアクセストークンの中身をデコードしてclaimをみることが出来ます。 ``` $ cat ~/.kube/cache/kubelogin/*.json | jq -r .access_token | jq -R 'split(".") | .[1] | @base64d | fromjson' { "aud": "*****", "iss": "https://sts.windows.net/*****/", "iat": 1691042264, "nbf": 1691042264, "exp": 1691047580, "acr": "1", "aio": "*****", "amr": [ "pwd", "mfa" ], "appid": "*****", "appidacr": "0", "email": "*****", "family_name": "*****", "given_name": "*****", "groups": [ "*****", "*****" ], "idp": "*****", "ipaddr": "*****", "name": "*****", "oid": "*****", "rh": "*****", "scp": "User.Read", "sub": "*****", "tid": "*****", "unique_name": "*****", "uti": "*****", "ver": "1.0", "wids": [ "*****", "*****" ] } ``` ### Kind Clusterの作成 作成したOIDCクライアントを使った認可を行うためにAPI Serverにオプションを追加してkindでクラスタを作成します。 API Serverの設定に関するドキュメントは https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server です。 ```yaml cat < kind-config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 kubeadmConfigPatches: - |- kind: ClusterConfiguration apiServer: extraArgs: oidc-client-id: $AAD_CLIENT_ID oidc-issuer-url: https://sts.windows.net/$AAD_TENANT_ID/ oidc-username-claim: email oidc-groups-claim: groups nodes: - role: control-plane EOF ``` ``` kind create cluster --config kind-config.yaml --image kindest/node:v1.26.6 ``` `kubectl`で`kubelogin`を使ったuserを設定します。 ``` kubectl config set-credentials "azure-user" \ --exec-api-version=client.authentication.k8s.io/v1beta1 \ --exec-command=kubelogin \ --exec-arg=get-token \ --exec-arg=--environment \ --exec-arg=AzurePublicCloud \ --exec-arg=--server-id \ --exec-arg=$AAD_CLIENT_ID \ --exec-arg=--client-id \ --exec-arg=$AAD_CLIENT_ID \ --exec-arg=--tenant-id \ --exec-arg=$AAD_TENANT_ID ``` 上記のuserを使用するcontextをkindクラスタに対して設定して、contextを切り替えます。 ``` kubectl config set-context kind-oidc --cluster=kind-kind --user=azure-user kubectl config use-context kind-oidc ``` このcontextで次のコマンドを実行すると次のようなエラーメッセージが出力されるはずです。 ``` $ kubectl get pod Error from server (Forbidden): pods is forbidden: User "****" cannot list resource "pods" in API group "" in the namespace "default" ``` Azure ADでログインしたユーザーはまだなんの権限もないので、K8sに対して何もできません。 ### ADグループの作成とメンバー追加 Developer用のADグループを作成します。 ``` GROUP_ID=$(az ad group create --display-name demo-developer --mail-nickname demo-developer --query id -o tsv) ``` `az login`でログインしたの自分のアカウントをこのグループに追加します。 ``` az login UPN=$(az ad signed-in-user show --query userPrincipalName -o tsv) OID=$(az ad user show --id ${UPN} --query id -o tsv) az ad group member add --group demo-developer --member-id ${OID} ``` groupに追加されていることを確認します。 ``` $ az ad group member list --group demo-developer [ { "@odata.type": "#microsoft.graph.user", "businessPhones": [], "displayName": "****", "givenName": "****", "id": "****", "jobTitle": null, "mail": "****", "mobilePhone": null, "officeLocation": null, "preferredLanguage": null, "surname": "****", "userPrincipalName": "****" } ] ``` `GROUP_ID`は次のコマンドでも取得できます。 ``` GROUP_ID=$(az ad group list --filter "displayname eq 'demo-developer'" --query '[0].id' -o tsv) ``` ユーザーをグループに追加したら、トークンを取得し直します。 ``` kubelogin remove-tokens kubelogin get-token --environment AzurePublicCloud --server-id $AAD_CLIENT_ID --client-id $AAD_CLIENT_ID --tenant-id $AAD_TENANT_ID ``` 以下のコマンドを実行し、`groups` claimに`$GROUP_ID`が追加されていることを確認してください。 ``` cat ~/.kube/cache/kubelogin/*.json | jq -r .access_token | jq -R 'split(".") | .[1] | @base64d | fromjson' ``` ### グループに対するRoleBindingの作成 cluster-admin権限で作成したグループに対して`dev` namespaceの`admin` clusterroleをbindします。 ``` kubectl create ns dev --context kind-kind kubectl create rolebinding -n dev --clusterrole=admin --group=${GROUP_ID} demo-admin --context kind-kind ``` ``` $ kubectl get rolebinding -n dev -owide NAME ROLE AGE USERS GROUPS SERVICEACCOUNTS demo-admin ClusterRole/admin 14s 7afc7680-534d-46aa-9c95-0195655caf57 ``` これで`dev` namespaceにはadminとして操作が可能になりますが、他のnamespaceでは引き続き操作が制限されています。 ``` $ kubectl get pod -n dev No resources found in dev namespace. $ kubectl get pod -n default Error from server (Forbidden): pods is forbidden: User "****" cannot list resource "pods" in API group "" in the namespace "default" ```