この記事は GRIPHONE Advent Calendar 2021 16日目の記事です。
SREの徳田です。(2回目)
前回TokenRequestProjectionを使ったAWSへの認証をやったわけですが、同じ機能を使ってGCPのWorkload Identity Federationをやってみよう、という試みです。
TL;DR
- Workload Identity Pool & Providerを設定
- Podを作成
- TokenRequestProjectionを使ってOIDC ID Tokenを作成
- 認証情報構成ファイルをConfigMapなどで作成してマウント
- 環境変数で認証情報構成ファイルを指定
背景
GKEを使ってWorkload Identityやるならこっちの方法でいいじゃん!という話なのですが、hostNetworkを有効にしたPodではWorkload Identityを使うことができません。
ということでTokenRequestで発行されるOIDC ID Tokenを使ったWorkload Identity Federationで認証をしてみようとなった次第です。
Workload Identity Federationの設定
それではまずWorkload Identity Poolを作成します。
gcloud iam workload-identity-pools create wif-test --location global
次にOIDC向けのWorkload Identity Providerを作成します。
gcloud iam workload-identity-pools providers create-oidc wif-test-provider \
--location="global" \
--workload-identity-pool="wif-test" \
--issuer-uri="https://container.googleapis.com/v1/projects/utility-heading-335212/locations/asia-northeast1-a/clusters/cluster-1" \
--allowed-audiences="gcp" \
--attribute-mapping="google.subject=assertion.sub"
--issuer-url
:クラスタのIssuerのURL。こちらの記事も見てみてください--allowed-audiences
:許可するAudience。--attribute-mapping
:属性マップ。複数ある場合はカンマで区切る
--attribute-mapping
では google.subject=assertion.sub
となっていますが、google.subject
はGCP上でのユーザーの一意の識別子で、 assertion.sub
はID Tokenの sub
クレームであり、これらをマップするように指定しています。
ついでにTokenRequestによって発行されるID Tokenの sub
クレームは以下のようになっております。
system:serviceaccount:NAMESPACE:SERVICEACCOUNT
属性のマッピングについて詳しく知りたい方は以下からどうぞ
https://cloud.google.com/iam/docs/workload-identity-federation#mapping
GCPのServiceAccountの設定
Workload Identityを使って成り代わる先のServiceAccountを作成しましょう。
gcloud iam service-accounts create test-account
次にWorkload Identity Poolから先程作成したServiceAccountに成り代われるように権限を設定します。
gcloud iam service-accounts add-iam-policy-binding test-account@utility-heading-335212.iam.gserviceaccount.com \
--role=roles/iam.workloadIdentityUser \
--member="principal://iam.googleapis.com/projects/1051542072111/locations/global/workloadIdentityPools/wif-test/subject/system:serviceaccount:default:default"
--member
の指定は以下のようなフォーマットになっており、今回は sub
クレームが system:serviceaccount:default:default
となっているユーザーを許可するようにしています。要はdefaultネームスペースのdefaultサービスアカウントに権限を渡しています。
memberの指定方法についてはこちらを参考にしていただければと思います。
https://cloud.google.com/iam/docs/workload-identity-federation#impersonation
そして最後に作成したサービスアカウントに適当に権限を付けておきましょう。今回はviewer権限を付与します。
gcloud projects add-iam-policy-binding utility-heading-335212 \
--member "serviceAccount:test-account@utility-heading-335212.iam.gserviceaccount.com" \
--role "roles/viewer"
KubernetesのManifestの設定
それではいよいよPodを作ります。
google-cloud-sdkでは認証情報を構成ファイルとして渡してあげる必要があります。その構成ファイルを作成しておきます。
gcloud iam workload-identity-pools create-cred-config \
projects/1051542072111/locations/global/workloadIdentityPools/wif-test/providers/wif-test-provider \
--service-account="test-account@utility-heading-335212.iam.gserviceaccount.com" \
--output-file=config.json \
--credential-source-file=/secret/token \
--credential-source-type=text
--service-account
:成り代わるサービスアカウントを指定--output-file
:構成ファイルの出力先--credential-source-file
:ID Tokenのパス--credential-source-type
:ファイルのタイプ。text
/json
を指定。今回は直接ID Tokenがおいてあるのでtext
を指定
すると作業フォルダに config.json
というファイルで以下のような内容のものができます。
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/1051542072111/locations/global/workloadIdentityPools/wif-test/providers/wif-test-provider",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/secret/token",
"format": {
"type": "text"
}
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test-account@utility-heading-335212.iam.gserviceaccount.com:generateAccessToken"
}
この作成ファイルを使ってConfigMapとPodを作成します。
---
apiVersion: v1
kind: ConfigMap
metadata:
name: gcloud-config
data:
config.json: |-
{
"type": "external_account",
"audience": "//iam.googleapis.com/projects/1051542072111/locations/global/workloadIdentityPools/wif-test/providers/wif-test-provider",
"subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
"token_url": "https://sts.googleapis.com/v1/token",
"credential_source": {
"file": "/secret/token",
"format": {
"type": "text"
}
},
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test-account@utility-heading-335212.iam.gserviceaccount.com:generateAccessToken"
}
---
apiVersion: v1
kind: Pod
metadata:
name: wif-test
spec:
containers:
- name: main
image: google/cloud-sdk:slim
command:
- bash
- -c
- |
gcloud auth login --cred-file $GOOGLE_APPLICATION_CREDENTIALS
gcloud container clusters list
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /config/config.json
volumeMounts:
- name: gcloud-config
mountPath: /config
readOnly: true
- name: token
mountPath: /secret
readOnly: true
volumes:
- name: gcloud-config
configMap:
name: gcloud-config
- name: token
projected:
sources:
- serviceAccountToken:
path: token
audience: gcp
restartPolicy: Never
terminationGracePeriodSeconds: 0
要点としては
- 構成ファイルを入れたConfigMapのマウント先と
GOOGLE_APPLICATION_CREDENTIALS
のパスを合わせる - TokenRequestで作成したTokenを含むProjected Volumeのマウント先と構成ファイルで指定したID Tokenのパスを合わせる
- audienceにWorkload Identity Providerを作成した際に
--allowed-audience
で指定した値と同じものを指定
また、今回はgcloudコマンドを使うため gcloud auth login
コマンドを実行していますが、クライアントライブラリを活用したツールなどでは環境変数からクレデンシャルを読み込んでくれます。
それでは上記のManifestを適用してログを確認してみます。
$ kubectl apply -f a.yaml
configmap/gcloud-config created
pod/wif-test created
$ kubectl logs -f wif-test
Authenticated with external account credentials for: [test-account@utility-heading-335212.iam.gserviceaccount.com].
Your current project is [utility-heading-335212]. You can change this setting by running:
$ gcloud config set project PROJECT_ID
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
cluster-1 asia-northeast1-a 1.21.5-gke.1302 35.187.223.85 e2-medium 1.21.5-gke.1302 3 RUNNING
ログの通り、サービスアカウントに成り代わってクラスタの情報を取得することができました!!🎉
さいごに
TokenRequestProjectionとWorkload Identity Federationを使ってGCPのサービスアカウントに成り代わってGCPを操作することができました。
hostNetwork
を有効にしているとノードプールのインスタンスに権限を渡すか、サービスアカントを作成して権限を付与し、JSONのSecretを渡して上げる必要がありましたが、TokenRequestProjectionとWorkload Identity Federationを組み合わせることで、外からクレデンシャルを渡すことなく認証処理を動かすことができました。
このケースに当たることはなかなか無いとは思いますが、もしこの手段が活用できるケースが有った場合は参考にしていただければ幸いです。
それでは!