GKE Workload Identity について詳しく調べてみた(DeepDive)
こんにちはミクシィの 開発本部 SREグループ の riddle です
GKE の Workload Identity は Kubernetes Service Account(SA) と Google Service Account(SA) を紐付けることで、Pod に対して GCP リソースを操作させる仕組みです。

非常に便利ですが 「GKE 上の Pod がどのように Google Cloud の権限を取得しているのか?」 について具体的イメージを全く持てていなかったため色々と調べてみました!
目次
◆ Google Cloud 上のアプリが SA を使う方法
・ 便利な Metadata Server
◆ GKE 上の Pod はどうやってアクセストークンを取得しているのか?
◆ Workload Identity と gke-metadata-server
◆ GKE で Workload Identity を使ってみる
◆ Pod がアクセストークンを取得する流れ
◆ さいごに
◆ 参考情報
Google Cloud 上のアプリが SA を使う方法

Google Cloud 上のサービスにデプロイしたアプリケーションは、Googleが提供するライブラリの アプリケーションのデフォルト認証情報(ADC) を通してサービスの SA を使えます。
GCE 上にインストールした gcloud コマンドがすぐに使用できるのも、GCE に紐付いている SA の情報を取得しているからです。
では一体 gcloud コマンドはどうやって GCE に紐付いている SA 名や アクセストークンを取得しているのでしょうか?
便利な Metadata Server

アプリや gcloud コマンドは アクセストークンを Metadata Server から取得しています。具体的に見ていきましょう。
Google が提供している x/oauth2 ライブラリのFindDefaultCredentialsWithParams
関数でADC の設定が定義されています。
こちらは実際にコードに書かれているコメントです。
上から順にこのようになっています。
- 環境変数
GOOGLE_APPLICATION_CREDENTIALS
に定義されたJSONファイル - 所定の場所に配置されたJSONファイル
- App Engine Standard 1st は
appengine.AccessToken
関数を使う - GCE、App Engine standard 2nd / Flexible は metadata server からクレデンシャルを取得する
注目すべきは4で GCE で動くアプリケーションは metadata server からクレデンシャルを取得 します。
実際にクレデンシャルを取得しているのがここです。
"instance/service-accounts/" + acct + "/token"
の URL を作成して HTTP リクエストを送った結果をデコードしていますね。
コマンドを GCE 上で叩くと確かにアクセストークンが取得出来ます。
まとめると GCE 上のアプリやコマンドはクライアントライブラリを通じて metadata-server 経由で SA のアクセストークンを取得して Google Cloud リソースを触れる ということでした。
GKE 上の Pod はどうやってアクセストークンを取得しているのか?

GKE の Pod も同様にクライアントライブラリを利用してアクセストークンを取得できます。しかし GKE では様々な Pod が同居しているため GCE の SA を使い回すのはセキュリティ的によろしくありません。
そこで GKE では Workload Identity という仕組みを使って、 Pod ごとに使用する SA を設定する方法が推奨されています。
Workload Identity と gke-metadata-server

Workload Identity を有効にすると gke-metadata-server
という名前の DaemonSet が起動します。(DaemonSet なので 全 Node で起動する)
gke-metadata-server
はその名の通りmetadata を扱うサーバです。
通常はリンクローカルアドレスである 169.254.169.254(metadata.google.internal)
に metadata-server
が Listen しているので GCE の場合はこのURLに HTTP リクエストを投げてアクセストークンを取得します。
しかし Workload Identity を有効にした場合は Pod から 169.254.169.254(metadata.google.internal)
へのアクセスはすべてgke-metadata-server
に転送されます。
これは Workload Identity を有効にした際に追加される Node の iptables ルールによるもので、以下の設定により DNAT が行われ gke-metadata-server
に転送されるようになります。
※つまり Workload Identity を有効化すると元々の metadata-server
は使用できなくなります
iptables の内容を簡略化すると以下の設定になります。
- 169.254.169.254:8080 -> 127.0.0.1:987
- 169.254.169.254:80 -> 127.0.0.1:988
port 987
、988
はいずれもgke-metadata-server
が Node 上で HostPort を使って Listen していますね。以下は gke-metadata-server
の manifest の内容です。
つまり GKE 上の Pod は metadata-server
ではなく、同じ Node 上の gke-metadata-server
経由でアクセストークンを取得します。
GKE で Workload Identity を使ってみる

では実際に Workload Identity を使って見ましょう。(クラスタとノードプールは Workload Identity はあらかじめ有効にしてください)
まずは必要な権限を作成します。
roles/iam.workloadIdentityUser
には以下の4つの権限がついています。
iam.serviceAccounts.get
iam.serviceAccounts.getAccessToken
iam.serviceAccounts.getOpenIdToken
iam.serviceAccounts.list
ロールについて | IAM のドキュメント | Google Cloud
iam.serviceAccounts.getAccessToken
はアクセストークンを取得するための権限なので、この設定によってPROJECT_ID.svc.id.goog[default/gke-workload
が gke-workload@PROJECT_ID.iam.gserviceaccount.com
のアクセストークンを取得できるわけです。
続いて Kubernetes SA と SAを使う Pod と使わない Pod を用意します。(GKE では Kubernetes SA の Annotation に定義した Google SA の権限を Pod が利用します)
manifest の準備ができたらデプロイしてみましょう!
※ kubectl apply -f filename ですね
今回は各 Pod にログインして gke-metadata-server
に経由で利用しているサービスアカウントを確認してみました。
SA なし(pod-without-sa
)の場合
SA あり(pod-with-sa
)の場合
SA ありの場合は Annotation に指定した Google SA のメールアドレスが記載されていますね。つまり Pod から gke-metadata-server
を通じてアクセストークンが取得できているということです。
一方で SA なしの場合は Service Account を設定していないので Workload Identity Pool のデフォルトのアカウントが表示されています。
コマンドを叩いても失敗します。
Pod がアクセストークンを取得する流れ
最後に Pod がアクセストークンを取得する流れを見ていきましょう。

流れを紹介します。
- MyPod がクライアントライブラリ経由で
gke-metadata-server
からクレデンシャルの取得を行う gke-metadata-server
は MyPod が使用している Kubernetes SA を見つけ、Annotation に設定されたiam.gke.io/gcp-service-account: gke-workload@PROJECT_ID.iam.gserviceaccount.com
を取得するgke-metadata-server
は GKE のコントロールプレーン上の OIDC Provider から、OIDC 署名付きの JWT を取得するgke-metadata-server
は OIDC 署名付きの JWT を IAM に渡して Google SA (PROJECT_ID.svc.id.goog[default/gke-workload]
) のアクセストークンを取得する
※IAM は GKE 上の OIDC Provider に問い合わせて JWT の検証を行うgke-metadata-server
は 4 で取得したアクセストークンを使って Google SA(gke-workload@PROJECT_ID.iam.gserviceaccount.com
) のアクセストークンを取得する
※IAM は2つのGoogle SAの binding の確認を行う
※roles/iam.workloadIdentityUser
によって binding していればよいgke-metadata-server
は 4 で取得したアクセストークン を MyPod に渡す- MyPod はアクセストークンを使って Google Cloud リソースを操作する
※この説明は Google Cloud Next 19 で紹介されています
※ 2 は gke-metadata-server
の実装(非公開)によっては 4 と 5 の間に来る可能性があります(2 に書いているのは私の予想です)
このようなフローで GKE 上の Pod は gke-metadata-server
を通じてアクセストークンを取得しています。複雑ですね…。

さいごに
今回は GKE で権限をセキュアに管理する Workload Identity の裏側を見ていきました。便利だな〜と思って使っていましたが、振り返るとあまり知らずに設定をしていたなと痛感しました。
ややこしいですが一歩づつ学んでいきましょう!