Carpe Diem

備忘録

Workload IdentityでPodからのGCPリソースアクセスをセキュアにする

概要

GKEのPodといったWorkloadからGCPのリソース(GCS、PubSubなど)にアクセスする場合

  • GKE NodeのService Accountの権限でアクセスする
  • 権限を持ったService AccountのJSONキーを使う

の2通りあります。

それぞれの問題点の理解と、その解決策としてのWorkload Identityの説明・導入方法を紹介します。

環境

GKE NodeのService Accountの権限でアクセスするの問題点

GKEのPodが起動するインスタンスには特に指定していない場合はdefaultのService Accountがついています。

ハテナマークをクリックすると分かるように、このノード上の全てのPodがこのService Accountの権限でGCPリソースへアクセス可能です。
※デフォルトではCluster PermissionsやノードプールのSecurity Access Scopesである程度制限はされます

しかしこのdefaultのService Accountは編集者という非常に強い権限を持っています。

なので仮にIAMの権限が小さいエンジニアであっても、Podを経由して編集者としてGCPの様々なリソースにアクセス・操作することが可能になります。
そしてそういったエンジニアが鍵を漏洩したときのリスクが非常に大きくなります。

権限を持ったService AccountのJSONキーを使う問題点

先程の問題を解決するために

といった対応方法があります。

しかしこれは

  • 鍵管理
  • Service Accountの鍵が10年で失効する

といった別の問題が起きます。特に前者は

機密情報の管理で大切なこと - Carpe Diem

でも話したように様々な考慮が必要です。

Workload Identityによってこれらの問題が解決される

以前はこのようにCloud IAMのService Accountを直接扱う必要がありましたが、

Workload IdentityによってKubernetes RBACとCloud IAMのService Accountを紐付け、Cloud IAM側のService Accountの鍵を管理する必要がなくなります。

GSA(Google Service Account) と KSA(Kubernetes Service Account) は 1:N の関係で紐付けることが可能です。

環境構築

最小限の権限のGSA(Google Service Account)を作成する

ノードで使用する最小権限のService Accountを用意します。
こちらはノードがログやメトリクスを保存する際に利用するもので、Workload Identityとは直接は関係ありません

$ gcloud iam service-accounts create $NODE_GSA_NAME \
  --display-name="Service Account for GKE Node Pool"

NODE_GSA_NAME=node-saとします。

モニタリング、ロギング周りの権限を付与していきます。

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:$NODE_GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
  --role roles/logging.logWriter

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:$NODE_GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
  --role roles/monitoring.metricWriter

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:$NODE_GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
  --role roles/monitoring.viewer

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:$NODE_GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
  --role roles/stackdriver.resourceMetadata.writer

ref: 最小限の権限の Google サービス アカウントを使用する

GCRの権限も付けたい場合は以下のようにします。

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member "serviceAccount:$NODE_GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com" \
  --role roles/storage.objectViewer

Webコンソールでは以下のような権限が付いていることを確認できます。

GKEクラスタの作成

Workload Identityを有効にしたクラスタを作成します。

$ gcloud beta container clusters create $NAME \
  --cluster-version=1.16.10-gke.8 \
  --identity-namespace=$PROJECT_ID.svc.id.goog \
  --zone=asia-northeast1-c \
  --enable-ip-alias \
  --service-account=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com

WebコンソールでWorkload Identityが有効になっていることが確認できます。

またノードプールのService Accountは先程作成したものになっています。

GSA(Google Service Account)の作成

次にWorkload Identityで使用するService Accountを作成します。

$ gcloud iam service-accounts create $GSA_NAME \
  --display-name="Test Service Account for Workload Identity"

GSA_NAME=test-gsaとします。

IAMの画面に作成されたことを確認できます。

KSA(Kubernetes Service Account)の作成

まず KSA 側からはiam.gke.io/gcp-service-accountというannotationsを使ってどの GSA と紐づけるかを指定します。

$ kubectl create namespace $K8S_NAMESPACE

namespaceを作っておき、

apiVersion: v1
kind: ServiceAccount
metadata:
  name: $KSA_NAME
  namespace: $K8S_NAMESPACE
  annotations:
    iam.gke.io/gcp-service-account: "$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com"

このようにKSA用のyamlをapplyします。

今回はKSA_NAME=app1K8S_NAMESPACE=exampleとして作成しました。

サービス アカウントの権限借用(Impersonation)

Policy Bindingを作成して KSA が GSA になりすまし、GSAの権限を使えるようにします。
AWSでいうAssumeRole(権限引き渡し)のイメージです。

$ gcloud iam service-accounts add-iam-policy-binding \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[$K8S_NAMESPACE/$KSA_NAME]" \
    "$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com"

紐付いているか確認してみます。
namespace 内に gcloud の入った pod を立て、

kubectl run \
  --namespace $K8S_NAMESPACE \
  --overrides='{ "spec": { "serviceAccount": "$KSA_NAME" }  }' \
  --image google/cloud-sdk:alpine \
  --rm -it wi-test

ref: kubectl run --generator run-pod/v1 が使えなくなっていた - Carpe Diem

Binding した GSA が正しく認識されているか見てみます。

# gcloud auth list
                     Credentialed Accounts
ACTIVE  ACCOUNT
*       test-gsa@$PROJECT_ID.iam.gserviceaccount.com

大丈夫ですね。これで準備が整いました。

PodからGCPリソースへアクセスしてみる

GCSのバケットを用意して、そこのオブジェクトにアクセスできるかどうかを確認してみましょう。

$ gsutil mb gs://workload-identity-test
$ echo "Hello World" > foo.txt
$ gsutil cp foo.txt gs://workload-identity-test/foo.txt

BindしたGSAに権限がない状態

まずはKSAとBindしているGSAにRoleを付けていない状態で動作確認してみます。

先程のgcloudのpodで試してみましょう。

kubectl run \
  --namespace $K8S_NAMESPACE \
  --overrides='{ "spec": { "serviceAccount": "$KSA_NAME" }  }' \
  --image google/cloud-sdk:alpine \
  --rm -it wi-test
# gsutil cat gs://workload-identity-test/foo.txt
AccessDeniedException: 403 test-gsa@$PROJECT_ID.iam.gserviceaccount.com does not have storage.objects.list access to the Google Cloud Storage bucket.

怒られました。

BindしたGSAに権限を付けた状態

次にGSAにRoleを付けた状態で動作確認してみます。

反映のため少し待ってからアクセスすると、

# gsutil cat gs://workload-identity-test/foo.txt
Hello World

ちゃんとアクセスできました。

その他気になった点

Workload Identityを有効化すると、PodはノードのGSAを使えない

Workload Identity無効&GSA未指定なノードプールでPodを立ち上げると

/ # gcloud auth list
                  Credentialed Accounts
ACTIVE  ACCOUNT
*       533275368484-compute@developer.gserviceaccount.com

と、default のGSAが使われます。なので先程の検証バケットにもアクセスできます。

# gsutil cat gs://workload-identity-test/foo.txt
Hello World

しかしWorkload Identity有効なノードプールでPodを立ち上げると、ノードプール自体にはdefault GSAが設定されている&特にBinding設定していなくても

# gcloud auth list
          Credentialed Accounts
ACTIVE  ACCOUNT
*      $PROJECT_ID.svc.id.goog

というアカウントで認証され、

# gsutil cat gs://workload-identity-test/foo.txt
AccessDeniedException: 403 Primary: /namespaces/$PROJECT_ID.svc.id.goog with additional claims does not have

このようにアクセスできませんでした。

ノードプールで GKE メタデータ サーバーが有効にされると、Pod は Compute Engine のメタデータ サーバーにアクセスできなくなります。

ref: Use Workload Identity  |  Google Kubernetes Engine (GKE)  |  Google Cloud

この制限事項の影響ですね。

まとめ

Workload Identityを利用することで、PodからGCPリソースへのアクセスをセキュアにすることができるようになりました。

参考