y-ohgi's blog

TODO: ここになにかかく

GKEのWorkloadIdentityを使用してモダンなアクセス制御を実現する

概要

GKE からGCP のサービスや機能にPod 単位で権限を割り振るための機能 "Workload Identity" の概要と使い方についてです。

公式ドキュメントは以下になります。
ワークロード ID  |  Kubernetes Engine ドキュメント  |  Google Cloud

Workload Identity とは

WorkloadIdentity がある場合、「Pod 単位でGCP のAPI へのアクセス制御を簡易的に管理する」ことが可能です。

WorkloadIdentity を使用することでPod に対し直接GCP のServiceAccount を紐付けることができ、GCP のAPI へServiceAccount を用いたアクセス制御が可能になります。
(AWS のEKS でいうIAM roles for service accounts やkiam のようなものになります。)

Workload Identity が無い場合

まず、Workload Identity を使用しない場合、どのようにAPI へアクセスするか見てみましょう。

  1. Nodeに付与されている(Google)サービスアカウントを使用する
  2. サービスアカウントのクレデンシャルファイルをPodへマウントする

"1" の場合、Pod が配置されたNode のServiceAccount をそのまま使用することが可能です。
Cloud Monitoring (旧 StackDriver) にログやトレースを流すだけの単純な要件であれば実用する上で問題ないでしょう。
しかし、CloudSQL やPub/Sub など、個々のリソースへアクセスするためにNode のServiceAccount へ権限を追加する必要があり、権限が不要なPod にも過剰な権限を与えてしまうことになります。ノードプールをPod 毎に容易するという手もありますが、リソースプールとしてのコンテナ間でリソースを共有することが可能なメリットを損ねてしまいます。

"2" の場合、Secret(やコンテナイメージの中にファイルを配置したもの)を使用して配置することで、Pod単位でアクセス制限をすることが可能になります。
しかし、クレデンシャルファイルを直接扱うセキュリティリスク(ファイルの漏洩・ローテーションする運用)を気にかけるなど、管理性に問題があります。

Workload Identity がある場合

Workload Identity がある場合、「Pod 単位でGCP のAPI へのアクセス制御を簡易的に管理する」ことが可能です。

まずこの図を見てください。"Pod A" はCloudSQL へ接続し、"Pod B" はBigQuery を呼び出す例です。
前提として、"Pod A" からBigQuery は呼び出せず、"Pod B" はCloudSQL へ接続できないようにする必要があるとします。

f:id:y-ohgi:20200819230400p:plain

Workload Identity を使用することで、Kubernetes のServiceAccount (以下KSA)をGCP の ServiceAccount (以下GSA)と紐付けることが可能になります。
これによりGSA 毎にNodePool を作成したり、Pod 内へ直接GSA のクレデンシャルファイルを配置する必要がなくなります。

いつ使うのか

複数のPod でそれぞれアクセス制限をする必要がある場合はWorkload Identity を使用するのが望ましいでしょう。
また、Workload Identity はGCP が推奨するアクセス制限の方法なので基本的にWorkload Idenitty を選択するのが好ましいと思います。

しかし、Workload Identity は少し複雑です。
GKEの作成時にWorkload Identity を有効化し(あとから有効化は可能)、GSAを作成し、KSAを作成し(、namespaceやRBACやNetworkPolicyも必要であれば考慮し)...この複雑さに要件が見合わない場合は使用しなくて問題ないと思います(個人的に GSA 周りが特に複雑に感じ、その整理と備忘録を目的としてこのブログを執筆しました)。
例えば、GKE 上に1つのDeployment しか配置する必要がなかったり、GCP のAPI を呼び出す必要がなかったりする場合です。


触ってみる

以下はCloudShell で実行可能なはずですが、諸事情(何故か個人のGCPプロジェクトでCloudShellがインターナルエラーを吐き起動できなかったこと)により google/cloud-sdk イメージ上で作業しています。

目標

実際にWorkloadIdentity を使用してPod からCloudSQL へ接続してみます。

f:id:y-ohgi:20200819230414p:plain

1. Cloud SQL の立ち上げ

gcloud コマンドを使用してCloud SQL for MySQL を立ち上げます。
// このタイミングでは必要ありませんが、後々必要になるAdminAPIも有効化します。

$ gcloud services enable sqladmin.googleapis.com
$ gcloud sql instances create instance1 \
    --tier=db-n1-standard-2 \
    --region=asia-northeast1

インスタンスの起動後に接続可能か確認します。
Cloud SQL Proxy のスクリプトを取得し、ローカルからCloud SQL へ接続します。

$ wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
$ chmod +x cloud_sql_proxy
$ ./cloud_sql_proxy -instances=<Cloud SQL 接続名>=tcp:3306

新しくターミナルを立ち上げ mysql コマンドを使用して接続できることを確認をします。

$ mysql -u root --host 127.0.0.1
mysql> \s
 :
Server version:         5.7.25-google (Google)
 :

接続できたことを確認できたらターミナルを終了して問題ありません。

2. GKEの作成

WorkloadIdentity を有効化したGKE を作成します。

作成時に --workload-pool オプションを使用してWorkloadIdentity を有効化します。
--workload-pool には <PROJECT ID>.svc.id.goog のようにプロジェクトIDを指定します。ここで定義した名前空間を使用し GSA と KSA の紐付けを行います。

$ gcloud services enable container.googleapis.com
$ gcloud beta container clusters create wi-test \
    --release-channel regular \
    --zone asia-northeast1-a \
    --workload-pool=<PROJECT ID>.svc.id.goog

3. namespaceの作成

(GKEのクレデンシャルを取得し、)CloudSQL への接続検証用 namespace を作成します。

$ gcloud container clusters get-credentials wi-test
$ kubectl create namespace cloudsql-ns

4. KSAの作成

KSA(Kubernetes ServiceAccount)を作成します。

$ kubectl create serviceaccount cloudsql-ksa --namespace cloudsql-ns

cloudsql-ns namespace 内に cloudsql-ksa という命名の KSA が作成できていれば以下のように表示されます。

$ kubectl get serviceaccount --namespace cloudsql-ns
NAME           SECRETS   AGE
cloudsql-ksa   1         11s
default        1         56s

5. GSAの作成

WorkloadIdentity で使用するための GSA(Google ServiceAccount)を作成します。

$ gcloud iam service-accounts create cloudsql-gsa

作成した GSA へCloudSQL への接続権限を付与します。

$ gcloud projects add-iam-policy-binding <PROJECT ID> \
    --member serviceAccount:cloudsql-gsa@<PROJECT ID>.iam.gserviceaccount.com \
    --role roles/cloudsql.client

GSA とKSA のバインディングを行います。

$ gcloud iam service-accounts add-iam-policy-binding \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:<PROJECT ID>.svc.id.goog[cloudsql-ns/cloudsql-ksa]" \
    cloudsql-gsa@<PROJECT ID>.iam.gserviceaccount.com

KSA のアノテーションに紐付ける対象の GSA を追加します。

$ kubectl annotate serviceaccount \
    cloudsql-ksa \
    --namespace cloudsql-ns \
    iam.gke.io/gcp-service-account=cloudsql-gsa@<PROJECT ID>.iam.gserviceaccount.com

6. Podの実行

接続検証用に使い捨ての Pod を立ち上げ、疎通できるか確認します。
この際に GSA と紐付けた KSA を付与することを忘れずに行いましょう。

$ kubectl run \
    -it \
    --rm true \
    --restart Never \
    --serviceaccount cloudsql-ksa \
    --namespace cloudsql-ns \
    --image google/cloud-sdk \
    cloudsql-pod \
    -- bash
# cd /tmp
# apt-get install -y mysql-client wget
# wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
# chmod +x cloud_sql_proxy
# ./cloud_sql_proxy -instances=<Cloud SQL 接続名>=tcp:3306 &
# mysql -u root --host 127.0.0.1
mysql> \s
 :
Server version:         5.7.25-google (Google)
 :

ここまでで WorkloadIdentity の動作確認ができたと思います。

7. お片付け

GKEの削除

$ gcloud projects remove-iam-policy-binding <PROJECT ID> \
    --member serviceAccount:cloudsql-gsa@<PROJECT ID>.iam.gserviceaccount.com \
    --role roles/cloudsql.client
$ gcloud container clusters delete wi-test --zone asia-northeast1-a

GSAの削除

$ gcloud 
$ gcloud iam service-accounts delete cloudsql-gsa@<PROJECT ID>.iam.gserviceaccount.com

CloudSQLの削除

$ gcloud sql instances delete instance1

最後に

まず、WorkloadIdentity は将来的に GKE 作成時にデフォルトで enable になる機能なので今のうちにキャッチアップして損はない機能かなと思います。

また、WorkloadIdentity は複数プロジェクト間のアクセス制御も対応している点も魅力的です。
例えば、複数のサービス用のプロジェクトから、全社で統合的に管理している分析用の BigQuery プロジェクトへデータを集積するような使い方をしているようなケースには特にマッチするのではないでしょうか。
逆に(個人的に)、 GSA の権限設定周りが複雑に感じており、GCP(のIAM)自体にこなれていないうちは下手に手を出すと迷宮入りしてしまいそうな気はしました。

今回は CloudSQL への接続を題材にしましたがそもそも CloudSQL はプライベートIPで接続するのが良い気がします。
それより SecretManager に配置した秘匿情報へのアクセス制御や別プロジェクトにあるサービスに対してIAP 経由で接続するなど、GCP の API はサービスの成長とともに叩くようなアーキテクチャになりやすいと思うので、 WorkloadIdentity は積極的に採用していきたいですね。