
Google Kubernetes Engine( GKE)上で動くアプリケーションからAmazon Web Services( AWS)のAPIを呼び出したい場面は珍しくありません。Amazon Redshiftで分析クエリを実行する、Amazon S3バケットのデータを参照する、Amazon Pollyで音声合成を行う、その他のAWSサービスを使う——アプリケーションの要件はさまざまです。複数のクラウドを併用する企業が増えた今、こうしたマルチクラウド構成は当たり前になりつつあります。

クラウドをまたいだアクセスでは、新たな課題が生じます。あるクラウドから別のクラウド上のサービスを呼び出す際の認証情報をどう管理するか、という問題です。クラウドのシークレットをそのまま配布・保存する単純な方法は、セキュリティ面で望ましくありません。AWSサービスを利用する各サービスに長期有効な認証情報を配って回るやり方は、運用が煩雑なうえ、セキュリティリスクの温床にもなります。
既存のソリューション
各クラウドはこの課題に対する独自のソリューションを用意しており、単一クラウドで完結するならそれで十分です。
Google Cloudは、GKEアプリケーションが他のGoogle Cloudサービスへ認証・アクセスする推奨手段として Workload Identity を提供しています。Workload Identityは、KubernetesサービスアカウントとCloud IAMサービスアカウントを紐付ける仕組みです。これにより、Kubernetesネイティブな概念で「どのworkloadをどのIDで動かすか」を定義でき、KubernetesシークレットやIAMサービスアカウントキーを管理することなく、workloadから他のGoogle Cloudサービスへ自動的にアクセスできます。詳しくはDoiTのブログ記事 Kubernetes GKE Workload Identity をご覧ください。
AWS側にも IAM Roles for Service Accounts という同様の機能があります。Amazon EKSクラスターでこの機能を使うと、IAMロールをKubernetesサービスアカウントに関連付けられ、そのサービスアカウントを使うPod内のコンテナにAWSの権限を付与できます。これにより、PodからAWS APIを呼び出すためにワーカーノードのIAMロールへ過剰な権限を渡す必要がなくなります。
では、GKEクラスター上でworkloadを動かしつつ、セキュリティを犠牲にせずにAWSサービスへアクセスしたい場合はどうすればよいのでしょうか?
ユースケースの定義
すでにAWSアカウントとGKEクラスターがあり、社内方針としてマイクロサービス型アプリケーションをGKE上で動かすことが決まったとします。一方で、AWS上の他システムと連携するために、AWSアカウント内のリソース(Amazon S3やSNSなど)も引き続き活用したい——そんなシナリオを想定してみましょう。
たとえば、GKEクラスター内で動く オーケストレーションジョブ(Kubernetes Jobとしてデプロイ)が、データファイルをS3バケットにアップロードし、Amazon SNSトピックへメッセージを送信する必要があるとします。コマンドラインで書けばこのような処理です:
https://gist.github.com/90682305b879a96d273284df5d20fdcb
非常にシンプルな例です。これらのコマンドを成功させるには、オーケストレーションジョブ がAWS認証情報を保持し、その認証情報で対象のAPIを呼び出せる必要があります。
単純(かつ非セキュア)なアプローチ:IAMの長期認証情報
あるAWS IAMユーザーのアクセスキーとシークレットキーを書き出し、認証情報ファイルまたは環境変数として オーケストレーションジョブ に渡す方法です。直接埋め込むのではなく、 Kubernetes Secrets リソースに格納し、 RBAC認可ポリシー で保護するのが一般的でしょう。
この方法のリスクは、認証情報に有効期限がない点にあります。AWS環境からGCP環境へ何らかの手段で受け渡す必要があり、後から オーケストレーションジョブ を再作成できるよう、どこかに保管しておきたくなるのが人情です。
長期有効なAWS認証情報を扱う場合、AWSアカウントが侵害される経路はいくらでもあります。GitHubリポジトリへの誤コミット、Wikiへの平文保存、複数のサービスやアプリケーションでの使い回し、無制限のアクセス許可など、 枚挙にいとまがありません。
発行済みのIAMユーザー認証情報に対して適切な管理基盤を構築することは可能ですが、そもそも長期認証情報を発行しなければ、そうした対策自体が不要になります。
提案するアプローチ
基本的な発想は、Workload Identity や EKS IAM Roles for Service Accounts といった各クラウド固有の機能と同様に、GKE Podへ AWS IAMロール を割り当てる、というものです。
幸いなことに、AWSではIAMユーザーの代わりにOpenID Connect Federation( OIDC)アイデンティティプロバイダー向けのIAMロールを作成できます。一方Googleは、OIDCプロバイダーを実装し、Workload Identity を介してGKEと密に統合しています。Google Cloudサービスアカウントに紐付いたKubernetesサービスアカウント配下で動くGKE Podには、有効なOIDCトークンが提供されます。これらを組み合わせれば、GKEからAWSへの安全なアクセスを実現できそうです。
OIDCアクセストークンをIDトークンに交換する
パズルを完成させるには、もうひとつピースが足りません。Workflow Identity を正しく構成すると、GKE PodはGoogle Cloudサービスへのアクセスを許可するOIDCの アクセストークン を取得できます。しかし、AWS Security Token Service( STS)から一時的なAWS認証情報を取得するには、有効なOIDCの IDトークン を提示する必要があるのです。
AWS SDK(および aws-cli ツール)は、以下の環境変数が正しく設定されていれば、STSへ自動的に一時的なAWS認証情報をリクエストします:
AWS_WEB_IDENTITY_TOKEN_FILE- Web identity tokenファイル(OIDC IDトークン)へのパスAWS_ROLE_ARN- Podコンテナが引き受けるロールの ARNAWS_ROLE_SESSION_NAME- このassume-roleセッションに付ける名前
少々複雑に感じるかもしれませんが、ステップごとの手順と、セットアップを簡単にするためのオープンソースプロジェクト dointl/gtoken を順を追って紹介していきます。
gtoken-webhook Kubernetes Mutating Admission Webhook
gtoken-webhook はKubernetesのmutating admission webhookで、特定のアノテーションが付いたKubernetesサービスアカウントの下で動作するK8s Podに対してミューテーションを行います(詳細は後述)。
gtoken-webhookのミューテーションフロー
gtoken-webhook は対象のPodに gtoken``initContainer を注入し、さらに gtoken サイドキックコンテナ(OIDC IDトークンを期限切れ直前にリフレッシュする役割)を追加します。トークン用ボリュームをマウントし、AWS固有の3つの環境変数も注入します。gtoken コンテナは有効なGCP OIDC IDトークンを生成してトークンボリュームに書き出すと同時に、必要なAWS環境変数を注入します。
あとはAWS SDKが、対応する AssumeRoleWithWebIdentity をAWS STSへ自動的に呼び出します。インメモリキャッシュや、必要に応じた認証情報のリフレッシュも自動で処理してくれます。

構成手順ガイド
gtoken-webhook をデプロイする
gtoken-webhookサーバーをデプロイするには、Kubernetesクラスター内にwebhookサービスとデプロイメントを作成します。基本的にはシンプルですが、ひとつだけ注意が必要なのがサーバーのTLS構成です。 deployment.yaml を見てみると、証明書と対応する秘密鍵ファイルがコマンドライン引数から読み込まれており、それらのパスはKubernetesシークレットを指すボリュームマウント経由で渡されていることがわかります:
https://gist.github.com/b9fc2fe5acb556e3b48ee89af7db368e
大事なのは、後ほどwebhook構成内で対応するCA証明書を設定することを忘れない点です。これがあって初めて、apiserver はその証明書を信頼すべきだと判断できます。ここではIstioチームが当初書いた、証明書署名要求を生成するスクリプトを再利用します。リクエストをKubernetes APIへ送信し、証明書を取得して、そこから必要なシークレットを作成するという流れです。
まず webhook-create-signed-cert.sh スクリプトを実行し、証明書と鍵を保持するシークレットが作成されたか確認します:
https://gist.github.com/91d2bd8cddf116aa0fa8cefbf3208bcd
シークレットが作成できたら、デプロイメントとサービスを作成します。これらは標準的なKubernetesリソースです。ここまでで用意できたのは、ポート 443 のサービス経由でリクエストを受け付けるHTTPサーバーに過ぎません:
https://gist.github.com/34ec56018700a610f37a09a9e846eecb
Mutating Admission Webhookを構成する
webhookサーバーが起動したので、apiserver からのリクエストを受け付けられる状態になりました。ただしその前に、Kubernetes側でいくつかの構成リソースを作成しておく必要があります。まずはvalidating webhookから始め、後でmutating webhookを構成しましょう。 webhook構成 を見ると、CA_BUNDLE のプレースホルダーが含まれていることに気づくはずです:
https://gist.github.com/d40101fba8203cc77d62629dde4d6a8f
構成内のCA_BUNDLEプレースホルダーをこのCAで置き換える 小さなスクリプト が用意されています。validating webhook構成を作成する前に、次のコマンドを実行してください:
https://gist.github.com/5e0ed44892fc7a21709ac18b381cb5f6
続いて、mutating webhook構成を作成します:
https://gist.github.com/a545e146e1168f3b88d04b3f4c490d68
gtoken-webhook用のRBACを構成する
gtoken-webhook で使用するKubernetesサービスアカウントを作成します:
https://gist.github.com/aabfa30bc005ae8654b360814523c9bc
webhookサービスアカウントのRBAC権限を定義します:
https://gist.github.com/5ceb8a6e1ca19d9be3b8ed2d810ddc96
フロー変数
以下の変数のうち、ユーザーが指定するものと、自動生成されて後続のステップで再利用されるものがあります。
PROJECT_ID- GCPプロジェクトID(ユーザー指定)CLUSTER_NAME- GKEクラスター名(ユーザー指定)GSA_NAME- Google Cloudサービスアカウント名(ユーザー指定)GSA_ID- Google Cloudサービスアカウントの一意ID(Googleが生成)KSA_NAME- Kubernetesサービスアカウント名(ユーザー指定)KSA_NAMESPACE- Kubernetes名前空間(ユーザー指定)AWS_ROLE_NAME- AWS IAMロール名(ユーザー指定)AWS_POLICY_NAME- IAMロールに割り当てるAWS IAMポリシー(ユーザー指定)AWS_ROLE_ARN- AWS IAMロールのARN識別子(AWSが生成)
Google Cloud:GKE Workload Identityを有効化する
Workload Identity を有効にした新しいGKEクラスターを作成します:
https://gist.github.com/0fcbc309cd1967edac19bac6795d651d
あるいは、既存のクラスターを更新します:
https://gist.github.com/05a94c349aa4a274fc27daae45448be9
Google Cloud:Google Cloudサービスアカウントを作成する
Google Cloudサービスアカウントを作成します:
https://gist.github.com/1790d9e61c63123937d01c8146c1a67d
GSA_NAME サービスアカウントに次のロールを付与します:
roles/iam.workloadIdentityUser- GKE workloadからサービスアカウントを借用するroles/iam.serviceAccountTokenCreator- サービスアカウントを借用してOAuth2アクセストークン、署名付きBlob、署名付きJWTを発行する
https://gist.github.com/19570300e584798d4d5c3a234ceb0d62
AWS:Google OIDC FederationでAWS IAMロールを作成する
Google OIDCプロバイダー用のロール信頼ポリシードキュメントを準備します:
https://gist.github.com/d80514354a5270bd0f6766dee7947902
Google Web Identityを使ってAWS IAMロールを作成します:
https://gist.github.com/94f0ce213cf60cfce6ac987af51afbed
AWSロールに必要なポリシーを割り当てます:
https://gist.github.com/afd9ecc776cfba5148137769aaf71a5f
K8s SAアノテーションで使うAWSロールARNを取得します:
https://gist.github.com/d407769dffd4eb276ed22f7b723cbdc4
GKE:Kubernetesサービスアカウントを作成する
K8s名前空間を作成します:
https://gist.github.com/59dafb6baf1223552eb7d9fc3c3cbea3
K8sサービスアカウントを作成します:
https://gist.github.com/58fd6924e79270857f39d924a0f12011
K8sサービスアカウントにGKE Workload Identity(GCPサービスアカウントのメールアドレス)のアノテーションを付与します:
https://gist.github.com/0be380a02ff228a38b2a9e5400b75dcb
K8sサービスアカウントにAWSロールARNのアノテーションを付与します:
https://gist.github.com/01620978944525a8df6c3d76ddc1fb67
デモを実行する
K8s ``${KSA_NAME}``` サービスアカウントを使って、新しいK8s Podを起動します:
https://gist.github.com/9c5a30f5dd3825d5c3bc6adc14b76fbc
関連リンク
- GitHub: doitintl/gtoken を使ってGKEクラスターからAWSサービスへ安全にアクセス
- AWS Docs: Web IdentityまたはOpenID Connect Federation用のロール作成
- Blog: Kubernetes GKE Workload Identity リンク
- AWS Blog: サービスアカウント向けのきめ細かいIAMロールの紹介 リンク
- GitHub: Google Cloudから Web Identity Federation を使ったAWS認証 shrikant0013/gcp-aws-webidentityfederation GitHubプロジェクト
- Blog: Colin Panissetによる、GCPサービスアカウントを使ってAWS IAMロールにアクセスする ブログ記事
まとめ
本記事がお役に立てば幸いです。コメントやご質問をお待ちしています。
ほかの記事も読みたい方は、 ブログ をご覧いただくか、Twitterで Alexeiをフォロー してください。