Kubernetes とGCPの世界をつなぐアクセス管理のはなし
この記事は Google Cloud Japan Customer Engineer Advent Calendar 2019 の 1日目の記事です。
TL;DR
Google Cloud Platform (GCP) から Kubernetes(k8s) のリソースにアクセスするための仕組みであるRole-based access control (RBAC)と、Kubernetes から GCP のリソースにアクセスするための仕組みである Workload Identity について解説しています。
はじめに
Google Kubernetes Engine (GKE) 上で Kubernetes (k8s) の運用をご検討頂いている方向けに、K8SとGCPの世界をつなぐアクセス管理について基礎となる点をこの記事ではご紹介したいと思います。
GKE は Google Cloud Platform (GCP) の製品の1つですが、GKE を使っていたとしても、GCP でアクセス管理を行う標準の仕組みである Cloud IAM だけでは k8s で定義されるリソースのアクセス管理が実現できません。
そのために Role-based access controle (RBAC) という仕組みがあるのですが、ドキュメントを読むと、GCPのIAMで使われるサービスアカウントというリソースが k8s 側にもあったりと複雑です。そして最近 Beta リリースされた Workload Identity と呼ばれる k8s から GCPの リソースへアクセスするための仕組みもあり、ここでもGCPとk8sの双方のサービスアカウントを利用して設定を行うので、正しく整理して理解することが必要です。
そこで、どんなユースケースのときに、どちらの機能を使えばいいのか、この記事で整理していきたいと思います。
Kubernetesリソースへのアクセスを管理する
ではまず、Namespace や Pod といった k8s 独自のリソースに対する GCP からのアクセス管理はどのように行えばいいのでしょうか?
例えば、
- セキュリティの監査権限をもつ GCP のサービスアカウント
— すべての Namespace 配下のリソースへの Read only 権限 - 特定のマイクロサービスの 開発用の GCP サービスアカウント
— 対応する Namespace のみへの Read/Write 権限
という設定です。
GCP のサービスアカウントに GCP の IAM を使って権限を付与する場合 k8s のクラスタ全体へのアクセスになってしまうので、必要最低限の権限のみ付与するという原則(最小権限の原則)を考えると好ましくありません。
もちろん k8s の中でも k8s サービスアカウントを定義できるので、それを使って管理は可能なのですが、以下の図のように GCP 側と k8s 側で別々に管理する状態になります。
GCPのサービスアカウントに、より細かいレベルでアクセス権限を付与する仕組みが RBAC です。RBAC では Role と RoleBinding という2つのリソースを使ってアクセス権限を定義します。
(RoleにはRoleとClusterRole、RoleBindingにはRoleBindingとCluster RoleBindingのそれぞれ2種類があります。定義できる権限の範囲の違いです。)
Role では、対象となるリソースに対する権限をrules[].verbsの形で定義します。そしてRoleBidingで、その Role と GCP の IAM で定義されたサービスアカウントを紐付けます。
下図がそのイメージ図ですが、”service-a-ro”というRoleを定義し、RoleBidingの中で
“service-a-developer@[project_id].iam.gserviceaccount.com” のサービスアカウントと紐付けています。
この Role は “service-a” の namespace のみに対してアクセス権をもっているので、“service-a” に対して kubectl get pods コマンドを実施した場合には成功しますが、それ以外の namespace に対しては kubectl get pods コマンドは成功しません。
“service-a”を指定してpodの情報を取得した例
#kubectl get pods -l app=hello-server --namespace=service-a
NAME READY STATUS RESTARTS AGE
hello-server-6c6fd59cc9-h6zg9 1/1 Running 0 13m
“service-a” 以外に対する結果の例
#kubectl get pods -l app=hello-server --namespace=service-b
Error from server (Forbidden): pods is forbidden: User "service-a=developer@[PROJECT_ID].iam.gserviceaccount.com" cannot list pods in the namespace "test": Required "container.pods.list" permission.
実際にご自身で試されたい場合は、こちらの GitHub の Tutorial に詳しい手順が記載されていますのでこちらも参考にしてみてください。
k8s から GCP リソースへのアクセスを管理する
今度は逆に k8s から GCP リソースへのアクセス管理の場合について考えてみましょう。k8s の特定の Pod から GCP のリソース、例えば Spanner にデータを Insert するというは通常考えられるユースケースです。
このようなアクセス管理を行うには、GCP のサービスアカウントに紐づく鍵ファイルを作成して k8s のシークレットに登録しておき、それを Pod が読み込んで使うという方法が1つの方法として考えられます。
(これ以外にも GKE の Node がもつ GCE としてデフォルトのサービスアカウント情報を使う方法などがあります)
これでアクセスの管理は実施できるのですが、GCP のサービスアカウントの鍵ファイルを、k8s 側で登録したり、鍵のローテーションを行う際には再登録が必要になるなど、2重管理が発生してしまいますし、鍵ファイルを適切に管理しないとセキュリティ上のリスクになります。
こうした手間を減らすことができるのが、最近 Beta リリースされた、Workload Identityという仕組みです。Workload Identity では RBAC とは逆にk8s Service Account (KSA)とGCP Service Account (GSA)を結びつける IAM Policy Biding を定義します。これによって、特定の KSA が持つ権限について GCP の IAM 側で一元管理を行うことができますし、鍵ファイルの管理も不要になります。
では実際の設定方法の流れを解説します。Workload identiyを使用するにはGKEのクラスタ単位で機能を有効化する必要があります。
新規のクラスを作る際に有効にするには — identity-namespaceオプションを指定してクラスタを作成します。
gcloud beta container clusters create [CLUSTER_NAME] \
--cluster-version=1.12 \
--identity-namespace=[PROJECT_ID].svc.id.goog
既存のクラスタに対して有効にするには gcloud container clusters update コマンドを使ってクラスタの設定を変更します。
gcloud beta container clusters update [CLUSTER_NAME] \
--identity-namespace=[PROJECT_ID].svc.id.goog
既存クラスタの場合はさらに Workload Identity を使用するPodが動いているノードプールの GKE_METADATA_SERVER も有効にします。(新しいノードプールを作ってそこへ Pod を移動する方法も可能です)
gcloud beta container node-pools update [NODEPOOL_NAME] \
--cluster=[CLUSTER_NAME] \
--workload-metadata-from-node=GKE_METADATA_SERVER
このあとの手順は新規のクラスタの場合も既存のクラスタの場合も同様です。
1. KSAを作成する。
kubectl create serviceaccount \
--namespace [K8S_NAMESPACE] \
[KSA_NAME]
- K8S_NAMESPACE : KSA がアクセス可能なNamespaceを指定。
- KSA_NAME : KSA の名前を指定
(kubectl apply でyamlファイルを指定して作ることもできます)
2. GSAを作成する(既存のGSAを利用することもできます)
gcloud iam service-accounts create [GSA_NAME]
- GSA_NAME : GCPのサービスアカウント名
※1と2の手順は順不同です。
3. IAM policy bidingを作成して、KSAとGSAを紐づける。
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
4. 手順1で作成したKubernetes Service AccountにAnnotationを付与する
kubectl annotate serviceaccount \
--namespace [K8S_NAMESPACE] \
[KSA_NAME] \
iam.gke.io/gcp-service-account=[GSA_NAME]@[PROJECT_ID].iam.gserviceaccount.com
この記事の中では解説しませんが、gcloudとkubectlコマンドを使う代わりに、config connector をつかってyamlで定義する方法もあります。
これで Pod を動かす際に GSA にバインディングされた KSA を使うことで、 GCP のリソースにも鍵ファイルなしでアクセスできるようになります。
動作確認方法
動作確認のために cloud SDK のイメージを使う Pod を一時的に作成します。
kubectl run -it \
--generator=run-pod/v1 \
--image google/cloud-sdk \
--serviceaccount [KSA_NAME] \
--namespace [K8S_NAMESPACE] \
workload-identity-test
この Pod で使っている KSA に紐付けられた GSA の権限に基づいて gcloudコマンドを実行することが可能です。
例えば Google Cloud Storage(GCS)のオブジェクトの閲覧権限のみがあった場合、gsutil ls コマンドでオブジェクトをリストすることは可能ですが、GCS のバケットを作る gsutil コマンドは実行することができません。コマンドを実行すると以下のようなエラーメッセージが返ってきます。
root@workload-identity-test:/# gsutil mb gs://test-wi
Creating gs://test-wi/...
AccessDeniedException: 403 wi-gsa@[PROKECT_ID].iam.gserviceaccount.com does not have storage.buckets.create access to project *********.
終わりに
以上がユースケースに基づく、Kubernetes と GCP の世界をつなぐアクセス管理のはなしでした。 RBAC と Workload Identity を使うことで双方の世界を繋ぐ方法は確立されてきましたが、設定したアクセス管理の状況をモニタリングする方法など、まだまだ課題は残っています。コンテナ化されたシステムを使っていく上でアクセス権限の管理は常に重要なテーマですので、今後も GCP と GKE の進歩にご注目ください。
明日は、Keiji Yoshidaによる「Apache Hadoop のデータを BigQuery で分析するための移行手順」です。お楽しみに!