AWS EKSのIAM Roles for Service Accountsの仕組みを完全に理解する記事。

IAM Roles for Service Accounts (IRSA)とは

IAM Roles for Service Accounts (IRSA)は、IAMロールをEKSのServiceAccountに紐づける機能。

IRSAを使うには、IAMにOIDCプロバイダの設定をちょろっとして、特定の信頼ポリシーを付けたIAMロールを作っておいて、ServiceAccountのアノテーションにeks.amazonaws.com/role-arn: <IAMロールのARN>を付けるだけ。 そうしておくと、そのServiceAccountを付けたPodがそのIAMロールの権限でAWSサービスにアクセスできる。

IRSAを構成する要素

  • Kubernetes側の要素

    • Mutating Admission Webhook

      KubernetesのAdmission Controllersの一つ。

      Kubernetesのkube-apiserverは、何らかのAPIリクエストを受けたとき、それに対して認証(Authentication)、認可(Authorization)、許可(Admission)の処理を実行する。

      Admission Controllersは許可の部分の処理を担当するコンポーネントで、LimitRangeのチェックをしたり、PodSecurityPolicyのチェックをしたりするいろいろなcontrollerで構成されるんだけど、そのなかの一つで、APIオブジェクトをいじる任意の処理を差し込めるcontrollerがMutating Admission Webhook。

      KubernetesにMutatingWebhookConfigurationを登録しておくと、そこで指定したAPIリクエストがkube-apiserverに来た時に、指定したServiceやURLにリクエストの内容を送ってくれるので、受け取ったサーバ側でAPIオブジェクトをいじって返してやるとそれが反映される、という寸法。

    • Service Account Token Volume Projection

      PodのServiceAccountを認証するトークン(i.e. ServiceAccountトークン)をPodのボリュームに挿入する、Kubernetesの機能。

      PodのボリュームにProjected Volumeを追加して、ソースとしてserviceAccountTokenを指定しておくと、kubeletがPodを起動するときに、kube-apiserverからServiceAccountトークンを取得して挿入してくれる。

      Podにはデフォルトで一つService Account Token Volume Projectionがついて、KubernetesのAPIにアクセスする用のServiceAccountトークンが挿入される。このトークンをHTTPのAuthorizationヘッダにベアラトークンとして入れれば、KubernetesのAPIをPodのServiceAccountとして実行できる。

      ServiceAccountトークンはOIDCのIDトークンの形式で、発行者(iss)がkube-apiserverのURLになってるので、kube-apiserverのOIDC Discoveryエンドポイント(/.well-known/openid-configuration)でJWKSエンドポイントのURL(/openid/v1/jwks)を取得し、そこから公開鍵を取得し、IDトークンについている署名を検証することができる。 (EKSの場合、issはhttps://oidc.eks.ap-northeast-1.amazonaws.com/id/11223344456677889900AABBCCDDEEFFみたいな、EKSクラスタ毎のOIDC専用のURLになっている。)

  • AWS側の要素

    • IAMのOIDC IDプロバイダとWeb IDフェデレーション

      Web IDフェデレーションは、AWSのリソースに、OIDC互換な外部のIDプロバイダが認証したユーザでアクセスできるようにする、IAMの機能。

      IAM OIDC IDプロバイダというIAMリソースを作成し、issとかそのTLSサーバ証明書のサムプリントを登録しておくと、そのissが発行したIDトークンの内容をIAMが信用してくれるようになる。

    • AssumeRoleWithWebIdentity

      Web IDフェデレーションしたIDプロバイダが発行したIDトークンを使ってIAMロールを引き受ける、STSのAPI。 実行すると、指定したIAMロールの権限でAWSリソースにアクセスできる、一時的な認証情報(i.e. アクセスキーIDとシークレットアクセスキーとセッショントークン)が返ってくる。

      引き受けられるIAMロールの方には、対応するIAM OIDC IDプロバイダを指定する信頼ポリシーを付けておく必要がある。

    • Amazon EKS Pod Identity Webhook

      IRSAの肝。

      Amazon EKS Pod Identity Webhook(以下PIdW)はMutating Admission Webhookの実装で、eks.amazonaws.com/role-arnが付いたServiceAccountが割り当てられたPodをいじって、Web IDフェデレーションのためのService Account Token Volume Projectionと、環境変数のAWS_WEB_IDENTITY_TOKEN_FILEとかAWS_ROLE_ARNとかを挿入する。

      挿入するService Account Token Volume Projectionでコンテナの/var/run/secrets/eks.amazonaws.com/serviceaccount/tokenにServiceAccountトークンをマウントさせて、AWS_WEB_IDENTITY_TOKEN_FILEでそのパスを指定してやることで、コンテナ内で動くAWSクライアントがそのトークンを読んで、AWS_ROLE_ARNで指定されたIAMロールに対してAssumeRoleWithWebIdentityをコールするという寸法。

      PIdWはEKS環境ではコントロールプレーンで動いているっぽいけど、ノードでPodとして動かすこともできる。

図解IRSA

irsa.png

IRSAを図にするとこんな感じになるはず。 左側がEKSの世界で、右側がIAMの世界。青い実線矢印はHTTPS通信。

処理の流れは、細かい間違いはあるかもしれないけど以下のような感じのはず。

  1. EKSのkube-apiserverにPodを作るリクエストが来たら、kube-apiserverがその内容をPIdWに送る。
  2. PIdWは、Podに紐づくServiceAccountを取得し、そのアノテーションにeks.amazonaws.com/role-arnが付いてたら、Podの定義をいじってService Account Token Volume Projectionと、環境変数のAWS_WEB_IDENTITY_TOKEN_FILEとかAWS_ROLE_ARNとかを挿入する。
  3. kubeletは挿入されたService Account Token Volume Projectionを見て、それに対応するServiceAccountトークンをkube-apiserverに発行してもらう。
  4. kubeletは、発行されたServiceAccountトークンをPodにマウントさせる。
  5. Pod内で起動したコンテナは、AWS_WEB_IDENTITY_TOKEN_FILEが指定する場所にマウントされたServiceAccountトークンと、AWS_ROLE_ARNで指定されたIAMロールのARNを付けて、AssumeRoleWithWebIdentity APIを実行する。
  6. AssumeRoleWithWebIdentityを呼ばれたSTSは、受け取ったServiceAccountトークンのissクレームに書いてあるURLからOIDC Discovery Documentを取得する。さらに、そのドキュメントのjwks_uriに書いてあるURLにアクセスしてJWKSを取得し、そこからトークン署名キーを取得する。
  7. 取得したトークン署名キーを使ってServiceAccountトークンを検証する。
  8. STSは次に、ServiceAccountトークンのissとかaudとかをみて、IAMのOIDC ID provider設定によって信頼している発行者からSTSに対して発行されたトークンであることを確認する。
  9. STSは、ServiceAccountトークンの正当性を完全に確かめられたので、AssumeRoleWithWebIdentityリクエストを認証する。認証されるユーザはServiceAccountトークンのsubクレームに、system:serviceaccount:kube-system:vaultのように書いてあって、つまりAssumeRoleWithWebIdentityを呼んだPodのServiceAccountになる。STSは最後に、AssumeRoleWithWebIdentityで指定されたIAMロールの信頼ポリシーをみて、そのIAMロールがServiceAccountトークンの発行者を信頼し、それによって認証されたユーザによるAssumeRoleWithWebIdentityを許可していることを確認する。
  10. ここまで来てやっとAssumeRoleWithWebIdentityリクエストを認可し、アクセスキーIDとかの認証情報を発行し、Podに返す。