こちらはAmazon EKS #1 Advent Calendar 2019 7日目の記事です。
EKSでIAM RoleをUserAccountに紐付けたり、ServiceAccountをIAM Roleに紐付けたりする際、AWSのドキュメントに従って設定してはいるものの、その設定によって実際にどんな処理が行われているかを具体的に知らない方も多いのではないでしょうか?(私も今回の記事のために調べるまではそうでした。)
そこで今回の記事では、Kubernetesの認証認可の仕組みを解説したあと、AWSのIAMの認証情報をKubernetes内のUserAccountに紐付けるaws-iam-authenticatorの動作の仕組みとKubernetesのService AccountにIAM Roleを紐づける仕組みについて設定方法のレベルから一段掘り下げて実際の動作に焦点を当てながら説明していきます。
目次
- 目次
- Kubernetesの認証認可・AdmissionControl
- EKSのUserAccount(aws-iam-authenticator)
- ServiceAccountのIAM ロール(IRSA)
- まとめ
- 参考資料
Kubernetesの認証認可・AdmissionControl
まずは事前知識として、Kubernetesの認証認可およびAdmission Controlの仕組みについておさらいしていきましょう。KubernetesでのAPIサーバへのリクエストは、実際にその内容に従って処理を行う前に以下の各stageで順番に処理されます。
- Authentication(認証)
- Authorization(認可)
- Admission Control (リクエストのバリデーション・変更等)
それぞれ順番に見ていきましょう。
Authentication(認証)
認証方法
KubernetesではBasic認証を使用した方法やクライアント証明書を使用する方法など、様々な認証方法が提供されています。
EKSでは主に以下の2つの認証方法が使用されています。
- Service Account Tokens
- Webhook Token Authentication
認証主体
Kubernetesの認証主体は以下の2種類です。
- ServiceAccount
- UserAccount
ServiceAccount
ServiceAccountはKubernetesのAPIによって管理される名前空間に紐付いたリソースです。
ServiceAccountごとに、Kubernetesクラスタが発行したトークンをクラスタ内のSecretとして保持しています。Podのコンテナにこのトークンを埋め込み、Pod内のプロセスにこのトークンを使用させることでkube-apiserver
から認証されます。
ServiceAccountは以下のようなYAMLで定義します。
apiVersion: v1 kind: ServiceAccount metadata: name: test namespace: default secrets: # ServiceAccount作成後のSecret作成に伴って追加される - name: test-token-mfb7n
ServiceAccountを作成すると、TokenControllerによってトークンを保持する以下のようなSecretが作成されます。
# 見やすいよう項目の順番を入れ替えた apiVersion: v1 kind: Secret type: kubernetes.io/service-account-token metadata: # 省略 name: test-token-xxxxx namespace: default # 省略 data: ca.crt: LS0...(省略)...= # APIサーバのCA証明書がBase64エンコードされたもの namespace: ZGVmYXVsdA== # 名前空間名がBase64エンコードされたもの token: ZXI...(省略)...= # JWT形式のトークンがBase64エンコードされたもの
このようなSecretはServiceAccountを指定してPodを作成すると、自動でマウントされます。具体的には、後述するAdmission Controllerの仕組みによってPodに以下のような設定が追加されます。
volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: test-token-xxxx readOnly: true
volumes: - name: test-token-xxxx secret: defaultMode: 420 secretName: test-token-xxxxx
これにより、コンテナ内の/var/run/secrets/kubernetes.io/
に以下のような3つのファイルが作成されます。
なお、ServiceAccountを指定せずにPodを作成すると、Podと同じnamepaceのdefault
という名前のServiceAccountの指定が追加され、default
のServiceAccountに紐付いたトークンが埋め込まれます。これも後述するAdmission Controlによるものです。
KubernetesのGo ClientではPodにトークンが埋め込まれた状態で以下のような設定をすることで、APIサーバへのアクセス時にAuthorization: Bearer
ヘッダでトークンを送信するようになり、認証された状態で操作を行うことができるようになります。(エラーハンドリング処理は省略)
config, err := rest.InClusterConfig() clientset, err := kubernetes.NewForConfig(config)
UserAccount
UserAccountはServiceAccountとは違い、Kubernetesの外部で管理されているユーザです(=kubectlでUserAccountを作成するようなことはしない)。外部の認証情報とリンクして仕様されることが意図されており、例えばGKEではGCPのアカウントとリンクしていたりします。
EKSの場合はaws-iam-authenticatorという仕組みを使用し、IAMの認証情報から得たトークンを使用しKubernetes内のユーザに紐付けることができます。こちらは後ほど詳しく解説します。
Authorization(認可)
Kubernetesでの認可はRBAC(Role Based Access Control)と呼ばれる仕組みです。RBACではRole or ClusterRoleで許可する操作を定義し、RoleBinding or ClusterRoleBindingでServiceAccount等に紐付けます。Role or ClusterRoleはAWS IAMロールと名前は似ていますが認証される主体ではなく、むしろ権限を定義するIAMポリシーに近いものです。
以下はClusterRoleとClusterRoleBindingでServiceAccountにPodに対する操作を許可する場合の例です。
まず、ClusterRoleで許可する操作を定義します。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: namespace: default name: pod-reader rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "watch", "list"]
これをClusterRoleBindingでServiceAccountに紐付けます。
apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: read-pods-test subjects: - kind: ServiceAccount name: test apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: pod-reader apiGroup: rbac.authorization.k8s.io
これにより、ServiceAccountが紐付いたPod内からClusterRoleで許可された操作が行えるようになります。
Admission Control
Kubernetesでは、認証及び認可の処理が終わったリクエストを実行する前に、さらにAdmission Controlという仕組みで内容のバリデーションと修正を行います。
様々なAdmissionControllerが用意されており、基本的にはkube-apiserverと一緒にコンパイルされ同じバイナリにまとめられています。ただし、MutatingAdmissionWebhook
や ValidatingAdmissionWebhook
を使用することで Webhookで外部のプロセスにバリデーションおよび修正の処理を委譲できます。このようなWebhoookの設定はKubernetesのリソースとして管理されており、EKSではデフォルトで以下のような設定が生えています
$ kubectl get mutatingwebhookconfiguration NAME CREATED AT pod-identity-webhook 2019-11-22T12:59:57Z
先程のServiceAccountの解説の中でPod作成時に自動的にトークンがマウントされたり、ServiceAccountが指定されていないときにはdefaultのServiceAccountの指定が追加されていましたが、これはTokenController
によりPodの作成のリクエストが修正されたためです。
また、後で説明するServiceAccountにIAMロールを紐づける仕組みの中でもトークンや環境変数の設定を MutatingAdmissionWebhook
でPodに追加しています。
EKSのUserAccount(aws-iam-authenticator)
EKSではaws-iam-authenticatorによりIAMのエンティティとKubernetesのUserAccount/Groupを紐付けます。
ここでは、紐付けに必要な設定を見たあとで、実際にどのような処理が行われているかを掘り下げてみていきます。
設定内容
aws-iam-authenticatorでIAMのエンティティとKubernetesのUserAccount/Groupを紐づけを定義するのは以下のような aws-auth
ConfigMapです。
apiVersion: v1 data: mapRoles: | - rolearn: arn:aws:iam::XXXXXXXXXXXX:role/<ワーカーノードのロール名> username: system:node:{{EC2PrivateDNSName}} groups: - system:bootstrappers - system:nodes mapUsers: | - userarn: arn:aws:iam::XXXXXXXXXXXX:user/admin username: admin groups: - system:masters kind: ConfigMap metadata: # 省略 name: aws-auth namespace: kube-system # 省略
EKSのクラスタにIAMの認証情報からトークンを作成してアクセスするには以下のようなkubeconfigファイルを使用します。
# 省略 users: - name: arn:aws:eks:ap-northeast-1:<アカウントID>:cluster/<クラスタ名> user: exec: apiVersion: client.authentication.k8s.io/v1alpha1 command: aws args: - --region - ap-northeast-1 - eks - get-token - --cluster-name - <クラスタ名>
これらの設定により、クライアント側のIAMエンティティとaws-auth
ConfigMapで定義した紐付けに従ってKubernetesのUserAccount/Groupとして認証されます。
余談ですが、将来のaws-iam-authenticatorのバージョンでは、このようなCustomResourceにより管理するようになるようです。これにより、1つの大きなConfigMapを使ってすべての紐付けを管理せず、IAMエンティティごとに別々に紐付けを定義するようになるようです。(https://github.com/kubernetes-sigs/aws-iam-authenticator/pull/116)
実際の処理
では、実際には前節の設定でどんな処理が行われているのでしょうか?
aws-iam-authenticator
による認証の流れは以下のようになります。
まず、kubeconfigの設定により、kube-apiserverへのアクセス前に、トークンを取得するためaws eks get-token
コマンドが実行されます。これにより、STSのGetCallerIdentity
の署名付きURLが発行され、これをもとにトークンが作成されます。
トークンの検証時にはこのトークンから抜き出した署名付きURLでGetCallerIdentity
を実行します。GetCallerIdentity
では署名付きURLを発行したIAMエンティティの情報が得られます。これにより、クライアントが特定のIAMのエンティティであることが確かめられるというわけです。
クライアントがAPIサーバにリクエストする際には、Authorization: Bearer
ヘッダでトークンをいっしょに送信します。
ここから先はEKSのコントロールプレーン内での処理です。
トークンを受け取ったkube-apiserverはWebhook Token Authenticationの仕組みでaws-iam-authenticatorサーバにトークンの検証を委譲します。
aws-iam-authenticatorサーバではトークンから抜き出した署名付きURLでGetCallerIdentity
を実行します。結果として得られたIAMのエンティティの情報とaws-auth
ConfigMapの情報を突き合わせてIAMのエンティティに紐づくUserAccount/Groupを取得します。そして、kube-apiserverにUserAccount/Groupの情報を返します。
このような流れにより、リクエストが特定のグループの特定のユーザからのものとして認証されます。
ServiceAccountのIAM ロール(IRSA)
先程のaws-iam-authenticatorの仕組みは、IAMのエンティティをKubernetesのユーザアカウントに紐付ける仕組みでした。
逆に、Kubernetes内部にいるPodのService Accountの認証情報を使用してIAM Roleを引き受けるためにはいくつかの方法があります。
- Service Accountのトークンを使用してIAM Roleを引き受ける(IAM Role for Service Account 以下 IRSA)
- ノードのiptablesをいじくって、メタデータエンドポイントへのアクセスを横取りしていい感じにAssumeRoleできるようにする(KIAM/kube2iam)
- AWSの認証情報をSecretにして埋め込む
ここでは、IRSAによりService AccountにAWSのロールを紐付ける方法をみていきます。
実際にはeksctlの便利なコマンドが用意されているため、実際にどのような設定が行われるのか深く意識せずとも使えますが、ここでもあえて一段掘り下げて何をやっているのかを順番に見ていきます。
概要
実際の動作のフローとしては以下のようになります。
IAMロールがアノテートされたServiceAccountを準備
ServiceAccountのトークンでのAssumeRoleを許可したIAMロールを準備
このServiceAccountを指定したPod作成時、AdmissionControllerによりSTS向けのServiceAccountのトークンの埋め込みと必要な環境変数の設定が行われる
以下に示すバージョンより新しいAWS SDK動作時にはコンテナに埋め込まれているトークンを元にAssumeRoleWithWebIdenityを実行し、IAM Roleを引き受ける
図にすると以下です(Amazon Web Services ブログの記事より転載させていただきました)
設定
では実際の設定を見ていきながらそれぞれ確認していきましょう。
AWSでOIDCプロバイダーを作成
まず、IAMのOIDC providerというエンティティを作成します。これを作成することで、EKSクラスタから発行されたトークンを信頼するようになり、AssumeRoleWithWebIdentityによるWebフェデレーションが行えるようになります。
eksctlでは以下のようなコマンドで実行しますが、
eksctl utils associate-iam-oidc-provider \ --name floral-mongoose-1574427060 \ --approve
これは以下のようなコマンドと同等です。
EKSクラスタから発行されるIDトークンのISSUERのURLを取得し、IAMにOIDCのプロバイダとして設定することで、EKSクラスタが発行したIDトークンを信頼させています。
ISSUER_URL=$(aws eks describe-cluster \ --name irptest \ --query cluster.identity.oidc.issuer \ --output text) aws iam create-open-id-connect-provider \ --url $ISSUER_URL \ --thumbprint-list $ROOT_CA_FINGERPRINT \ --client-id-list sts.amazonaws.com
ServiceAccountとIAMRoleの作成
次にServiceAccountおよびそこからAsssumeRoleできるIAM Roleを作成していきます。eksctlを使用すると以下のようなコマンドで実行できます。
eksctl create iamserviceaccount --cluster floral-mongoose-1574427060 --name serviceaccount-iamrole-test --attach-policy-arn arn:aws:iam::aws:policy/PowerUserAccess --approve
このコマンドは、実際には以下のような信頼ポリシーを持つIAMロールを作成し、
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "$PROVIDER_ARN" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${ISSUER_HOSTPATH}:sub": "system:serviceaccount:default:<service account>" } } } ] }
以下のようなアノテーションがついたServiceAccountを作成しています。
eks.amazonaws.com/role-arn=<IAMロールのArn>
信頼ポリシーを見ると、Service Accountから発行されたトークンを使用してAssumeRoleできるように設定されていることがわかります。
PodでServiceAccountを指定
最後にPodでこのServiceAccountを指定し、Pod内のプロセスがIAMロールを引き受けられるようにします。
AssumeRole先のIAM Roleに関するアノテーションがついたServiceAccountを指定してPodを作成すると、 MutatingAdmissionWebhookで処理が委譲されたamazon-eks-pod-identity-webhookにより、Podにいくつか設定が追加されます。
まず、KubernetesのService Account Token Volume projectionという仕組みでaud
がsts.amazonaws.com
となっているIDトークンが発行され、/var/run/secrets/eks.amazonaws.com/serviceaccount/token
に埋め込むように設定が追加されます。
volumeMounts: # 省略 - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount name: aws-iam-token readOnly: true
volumes: - name: aws-iam-token projected: defaultMode: 420 sources: - serviceAccountToken: audience: sts.amazonaws.com expirationSeconds: 86400 path: token
そして、トークンの場所及び引き受けるIAMロールを表す環境変数が追加されます。
containers: - env: - name: AWS_ROLE_ARN value: arn:aws:iam::795113267886:role/eksctl-floral-mongoose-1574427060-addon-iams-Role1-1BHQ40Q5V2FWF - name: AWS_WEB_IDENTITY_TOKEN_FILE value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
このように環境変数が設定されトークンが埋め込まれている場合、ある一定のバージョン以上のAWS SDKであれば勝手にトークンを使用してAssumeRoleWithWebIdentity
でIAMロールを引き受けてくれます。
このように、IRSAは、Kubernetesの外部audience向けにService AccountのIDトークンを発行する仕組み(Service Account Token Volume Projection)とAWSのウェブ ID フェデレーションの仕組みがうまく噛み合うことで実現されています。
まとめ
aws-iam-authenticatorおよびIRSAの仕組みを解説しました。
aws-iam-authenticatorはAssumeRoleWithWebIdentityでは、署名付きURLを用いてクライアントが実際にIAMで認証する巧妙な方法で実現されていました。またIRSAの仕組みはOIDCというオープンな仕様の上でKubernetesとAWSの実装がうまく噛み合って実現されていました。
個人的に気になりつつも調べきれていなかったところではあったのですが、アドベントカレンダーという機会で半強制的に自分にプレッシャーを掛けて勉強できたので良かったなと感じています。
これからもEKSやっていくぞ!
参考資料
全体
- Kubernetes完全ガイド
- Controlling Access to the Kubernetes API - Kubernetes
- Authenticating - Kubernetes
- Kubernetesのユーザー管理と認証・権限確認機構を理解しよう | さくらのナレッジ
aws-iam-authenticator
- クラスター認証の管理 - Amazon EKS
- GitHub - kubernetes-sigs/aws-iam-authenticator: A tool to use AWS IAM credentials to authenticate to a Kubernetes cluster
- Authenticating - Kubernetes
- 署名付き URL の使用 - Amazon CloudFront
IRSA
- Kubernetes サービスアカウントに対するきめ細やかな IAM ロール割り当ての紹介 | Amazon Web Services ブログ
- Configure Service Accounts for Pods - Kubernetes
- Dynamic Admission Control - Kubernetes
- GitHub - aws/amazon-eks-pod-identity-webhook: Amazon EKS Pod Identity Webhook
- サービスアカウントの IAM ロール - Amazon EKS
- OpenID Connect(OIDC)ID プロバイダーの作成 - AWS Identity and Access Management