GKE Workload Identity について詳しく調べてみた(DeepDive)

riddle
MIXI DEVELOPERS
Published in
11 min readFeb 15, 2022

--

こんにちはミクシィの 開発本部 SREグループ の riddle です

GKE の Workload Identity は Kubernetes Service Account(SA) と Google Service Account(SA) を紐付けることで、Pod に対して GCP リソースを操作させる仕組みです。

Workload Identity がよくわからない人

非常に便利ですが 「GKE 上の Pod がどのように Google Cloud の権限を取得しているのか?」 について具体的イメージを全く持てていなかったため色々と調べてみました!

目次

Google Cloud 上のアプリが SA を使う方法
便利な Metadata Server
GKE 上の Pod はどうやってアクセストークンを取得しているのか?
Workload Identity と gke-metadata-server
GKE で Workload Identity を使ってみる
Pod がアクセストークンを取得する流れ
さいごに
参考情報

Google Cloud 上のアプリが SA を使う方法

Google Service Account くん

Google Cloud 上のサービスにデプロイしたアプリケーションは、Googleが提供するライブラリの アプリケーションのデフォルト認証情報(ADC) を通してサービスの SA を使えます。

GCE 上にインストールした gcloud コマンドがすぐに使用できるのも、GCE に紐付いている SA の情報を取得しているからです。

では一体 gcloud コマンドはどうやって GCE に紐付いている SA 名や アクセストークンを取得しているのでしょうか?

便利な Metadata Server

アプリや gcloud コマンドは アクセストークンを Metadata Server から取得しています。具体的に見ていきましょう。

Google が提供している x/oauth2 ライブラリのFindDefaultCredentialsWithParams 関数でADC の設定が定義されています。

こちらは実際にコードに書かれているコメントです。

default.go — Go

上から順にこのようになっています。

  1. 環境変数GOOGLE_APPLICATION_CREDENTIALSに定義されたJSONファイル
  2. 所定の場所に配置されたJSONファイル
  3. App Engine Standard 1st は appengine.AccessToken関数を使う
  4. GCE、App Engine standard 2nd / Flexible は metadata server からクレデンシャルを取得する

注目すべきは4で GCE で動くアプリケーションは metadata server からクレデンシャルを取得 します。

実際にクレデンシャルを取得しているのがここです。

google.go — Go

"instance/service-accounts/" + acct + "/token" の URL を作成して HTTP リクエストを送った結果をデコードしていますね。

コマンドを GCE 上で叩くと確かにアクセストークンが取得出来ます。

まとめると GCE 上のアプリやコマンドはクライアントライブラリを通じて metadata-server 経由で SA のアクセストークンを取得して Google Cloud リソースを触れる ということでした。

GKE 上の Pod はどうやってアクセストークンを取得しているのか?

Google Kubernetes Engine

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 の内容を簡略化すると以下の設定になります。

port 987988 はいずれも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-workloadgke-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 がアクセストークンを取得する流れを見ていきましょう。

流れを紹介します。

  1. MyPod がクライアントライブラリ経由で gke-metadata-server からクレデンシャルの取得を行う
  2. gke-metadata-server は MyPod が使用している Kubernetes SA を見つけ、Annotation に設定された iam.gke.io/gcp-service-account: gke-workload@PROJECT_ID.iam.gserviceaccount.com を取得する
  3. gke-metadata-server は GKE のコントロールプレーン上の OIDC Provider から、OIDC 署名付きの JWT を取得する
  4. gke-metadata-serverは OIDC 署名付きの JWT を IAM に渡して Google SA (PROJECT_ID.svc.id.goog[default/gke-workload]) のアクセストークンを取得する
    ※IAM は GKE 上の OIDC Provider に問い合わせて JWT の検証を行う
  5. gke-metadata-serverは 4 で取得したアクセストークンを使って Google SA(gke-workload@PROJECT_ID.iam.gserviceaccount.com) のアクセストークンを取得する
    ※IAM は2つのGoogle SAの binding の確認を行う
    roles/iam.workloadIdentityUser によって binding していればよい
  6. gke-metadata-serverは 4 で取得したアクセストークン を MyPod に渡す
  7. MyPod はアクセストークンを使って Google Cloud リソースを操作する

※この説明は Google Cloud Next 19 で紹介されています
※ 2 は gke-metadata-server の実装(非公開)によっては 4 と 5 の間に来る可能性があります(2 に書いているのは私の予想です)

このようなフローで GKE 上の Pod は gke-metadata-server を通じてアクセストークンを取得しています。複雑ですね…。

さいごに

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

ややこしいですが一歩づつ学んでいきましょう!

参考情報

--

--