記事一覧はこちら
- NodeレベルでのIAMロール割当しちゃってる…
- Kubernetesの中での権限制御
- Kubernetesからawsリソースを操作する時の権限制御
- IAM Roles for Service Accounts
- ミドルウェアもどんどんpod単位の権限制御に
NodeレベルでのIAMロール割当しちゃってる…
という私用クラスタを使ってしまっている僕がPodレベルでの権限割当について学んだので、その備忘録です。ALB Controllerの利用をするときに、Pod単位での権限割当を行う導入フローになっているのを見て、そろそろちゃんと勉強せねばと思った次第です。
学ぶときに利用したリソースは下記です。
- Kubernetes サービスアカウントに対するきめ細やかな IAM ロール割り当ての紹介 | Amazon Web Services ブログ
- クラスターの IAM OIDC プロバイダーの作成 - Amazon EKS
- AWS ロードバランサーコントローラー - Amazon EKS
Kubernetesの中での権限制御
これはRole Based Access Control(RBAC)を利用することになると思います。
Podに割り当てるServiceAccount
リソースと「どのリソースに対してどの操作を許すか」という宣言を行ったRole
もしくはClusterRole
を用意し、RoleBinding
やClusterRoleBinding
で紐付けることで制御します。ArgoCDなどのCDツールでは様々なk8sリソースの参照/変更権限が必要になるため、デフォルトのServiceAccountでは権限が足らず、必要な権限を付与したServiceAccountを用意することになりました。
では、podがk8sリソースではなくAWSのリソースを操作するときには、どのような仕組みで制御されているか(例えばS3からデータを取得するなど)。
Kubernetesからawsリソースを操作する時の権限制御
ノード単位での権限制御
EKSローンチ当初はノード単位でのIAMロール割当しか出来ませんでした。
なので、least-privileged
を守るためには、権限の大きいノードグループを別で作成し、podのnodeSelectorでスケジューリングされるノードグループを別々にする必要がありました。
podはスケジューリングされているノードのインスタンスプロファイルを利用してAWSリソースにアクセスします。EC2メタデータサービスを利用して、アクセスキーとシークレットアクセスキーを取得できる例のやつです(instance-metadata-security-credentials)。
pod単位での権限制御
EKSのv1.13から利用できるようになった、podごとにIAMロールを割り当てる方法です。
といっても公式で出たのがそのタイミングというだけで、記事にもありますが、3rd Partyでは kube2iam などがEKSローンチ当初から利用されていたように感じます(kops
で利用されてて実績があったんですかね)。
それらの3rd Partyは「EC2 メタデータ API へのリクエストをインターセプトし、STS API を呼び出して一時クレデンシャルを取得する」という手法を取っていたようです。EC2メタデータのエンドポイントである 169.254.169.254
へのアクセスをノードのiptablesでポート8181で待ち構えているdaemonsetにルーティングさせて、podのiam.amazonaws.com/role
のアノテーションを見に行って指定したRoleにAssumeRoleさせるようです。
AWS公式の方法は、IAM Roles for Service Accounts (IRSA)
という方法で上記の方法とはアプローチが違うようです。
前述の方法だとiptableをいじることにコストとか、KubernetesのRBACがIAMロールと結びつかないことによって直感的でないことがもしかしていまいちだったのかもしれません(多分もっとちゃんとした理由がある)。
IAM Roles for Service Accounts
OIDC (OpenID Connect)
AWSがpodに権限を付与するために用いた手段では、IAM OIDCプロバイダを利用することが前提になっています。恥ずかしながら、認証周りに疎いのでOIDC知りませんでした。
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. - OpenID Connect | OpenID
OAuth2.0 の上にあるシンプルなidentityレイヤなんですね(分からん)。identity
というのはset of attributes related to an entity
を指すそうで、ここでのentity
は人や機械、サービスのことのようです。identityレイヤがもたらすのはIDトークンとユーザ情報のAPIの仕様ということなんだそうな(参考)。OAuth 2.0はUser IDの取得方法やProfile APIの仕様については定めてなかったのか!
そして、AWSではIAMでウェブIDフェデレーションをサポートしています。IDトークンを AWS アカウントのリソースを使用するためのアクセス許可を持つ IAM ロールにマッピングすることが可能です(参考)。APIでいうと、AssumeRoleWithWebIdentity
が呼ばれるそうです。
OIDC互換のプロバイダといえば、FacebookやGoogleなどがそれにあたりますが、そのプロバイダのIAMエンティティが「IAM OIDC identity provider」です。これにIdP とその設定について埋め込むことで、AWS アカウントと IdP の間の「信頼」が確立されます。
Amazon EKS now hosts a public OIDC discovery endpoint per cluster containing the signing keys for the ProjectedServiceAccountToken JSON web tokens so external systems, like IAM, can validate and accept the OIDC tokens issued by Kubernetes.
- Technical overview - Amazon EKS
このPodに権限を付与するという文脈で用いられるIdPは、EKSクラスタのことになります。
なので、EKSが公開するOIDCディスカバリエンドポイントを設定した「IAM OIDC identity provider」作成する作業がまず必要になります。そうすることで、OIDC プロバイダーを使った認証が有効化され、IAM ロールの引き受けを可能とする JSON Web Token (JWT) の取得ができるようになります。
AssumeRoleWebIdentity
ではJWTトークンを渡すことになります(参考)。IDトークンに含まれるフィールドiss
でプロバイダのエンドポイントが分かるので、それがIAM OIDCプロバイダに存在するのかを確認するはず。もし存在すれば、STSからEKSにTokenの検証リクエストを投げます。検証し問題なければ、AssumeRoleの際にリクエストしたRoleを被ることができるユーザかどうかの確認が走るはず(この辺はドキュメントベースでないのでかなり怪しい)。エンドユーザの識別子はIDトークンのsub
フィールドに含まれるそうな。
AssumeRole先のIAMロールには下記のようなポリシーをアタッチすることでk8sのサービスアカウントがどのIAMロールをAssumeできるかを制限します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/<OIDC_PROVIDER>" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "<OIDC_PROVIDER>:sub": "system:serviceaccount:<SERVICE_ACCOUNT_NAMESPACE>:<SERVICE_ACCOUNT_NAME>" } } } ] }
最終的には、STSからAssumeしたRoleのACCESS_KEY
とSECRET_ACCESS_KEY
をpodに返却し、podはその認証情報を利用してAWSリソースの操作を行います。
Service Account Token Volume Projection
では、そもそもEKSからpodにOIDC互換のIDトークンをどう渡しているのでしょうか?
Kubernetes v1.12からサポートされたProjectedVolumeをつかって受け渡しているそうです。ServiceAccountのアノテーションに、eks.amazonaws.com/role-arn
で引き受けるIAMロールの名称を宣言するわけですが、(おそらく)そのアノテーションが付与されていると、Mutating Admission Controller
が、環境変数とprojectedVolume
をpodSpec
に付与してくれます。なので、運用者がk8s環境で行うことはサービスアカウントをアノテートするだけですね。
以下、参考ドキュメントから引用
apiVersion: v1 kind: ServiceAccount metadata: annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-irptest-addon-iamsa-default-my-serviceaccount-Role1-UCGG6NDYZ3UE name: my-serviceaccount secrets: - name: my-serviceaccount-token-m5msn --- apiVersion: apps/v1 kind: Pod metadata: name: myapp spec: serviceAccountName: my-serviceaccount containers: - name: myapp image: myapp:1.2 env: - name: AWS_ROLE_ARN value: arn:aws:iam::123456789012:role/eksctl-irptest-addon-iamsa-default-my-serviceaccount-Role1-UCGG6NDYZ3UE - name: AWS_WEB_IDENTITY_TOKEN_FILE value: /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
ミドルウェアもどんどんpod単位の権限制御に
自分は気づけてなかったのですが、external-dns
やaws-load-balancer-controller
などのリソースは既にpod単位の権限制御前提で導入手順とか記載されていますね。
セキュリティ的にも重要なのでしっかり理解しておきたいです。以上!