概要
GKEのPodといったWorkloadからGCPのリソース(GCS、PubSubなど)にアクセスする場合
- GKE NodeのService Accountの権限でアクセスする
- 権限を持ったService AccountのJSONキーを使う
の2通りあります。
それぞれの問題点の理解と、その解決策としてのWorkload Identityの説明・導入方法を紹介します。
環境
- Kubernetes v1.16.10-gke.8
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キーを使う問題点
先程の問題を解決するために
- クラスタ自体には最低限の権限(monitoring.viewerなど)のService Accountを付与
- GCPリソースの機能はService Accountを個別に用意し、鍵を発行
- 発行した鍵を環境変数
GOOGLE_APPLICATION_CREDENTIALS
にセットしたり直接ロードして利用
といった対応方法があります。
しかしこれは
- 鍵管理
- Service Accountの鍵が10年で失効する
といった別の問題が起きます。特に前者は
でも話したように様々な考慮が必要です。
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=app1
、K8S_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リソースへのアクセスをセキュアにすることができるようになりました。