最近の砂場活動その19: Workload Identityを利用してGKEクラスタの権限管理を行なう

前回の続編です。GKEクラスタの設定をterraformで行なうように準備ができたので、肝心のWorkload Idenfityの設定をしていきます。

なぜ

Workload Identityの実基盤への導入に丁寧に書いてありますが、素朴なやり方では以下のような問題があるからです。

  • 権限が強すぎる
    • 方法: GKEの裏側のGCEに丸ごと権限を付与する
    • 楽チンだが、本来付与すべきでない権限も一律で付与してしまうことになる
    • GKEの上で様々なアプリケーションが動いているときには危険
  • 権限管理が面倒
    • 方法: 専用のサービスアカウントをのキーをダウンロードして使う
    • 鍵をどうやって安全に配置するか
      • 使い回されるとカオスになっていく
    • 10年経つとダウンロードしたキーが失効する
      • 仕事だと突然サービスが動かないことになるので、まずい

今回の私のケースは趣味プロジェクトであり、私しか使わないのでぶっちゃげGKEに丸ごと権限付与で問題ないのですが、仕事でもGKEを使う機会が出てきたので、素振りをしようという感じです。

どうやって

上記の問題を解決する方法として、Workload Identityというものがあります。

Workload Identityの理解にはあれこれ知識が必要なので、整理していきます。Kubernetes初心者なので...。

前提知識: Kubernetesのサービスアカウント

Kubernetesの操作(例: kubectl apply)を行なう代表的な登場人物としては人間(ユーザー)がいるが、プログラムからアクセスするときは共通ユーザーなどは作りたくない。なので、GCPと同じく、Kubernetesにもサービスアカウントという概念が登場する。

Kubernetesで「どういうことができる~」という権限はRoleで表現される。クラスタ単位の権限はClusterRoleとして定義され、namespace単位の権限はRoleで定義される。「この{ユーザー, グループ, サービスアカウント}にこの権限を付与する」というのはRoleをbindするという形で表わされ、具体的にはRoleBindingClusterRoleBindingがある。kind: ClusterRoleBindingといった形でyamlファイルで定義していくようだ。

というわけで、Kubernetes上で動くpodは

  • Kubernetesに対する操作をする時は、Kubernetesのサービスアカウントを経由してKubernetesのAPIを触る
  • GCPのリソースに対して操作をするときは、GCPのサービスアカウントを利用してGCPのリソースを触る

という風にして動いていた(Workload IdentityでPodからのGCPリソースアクセスをセキュアにする - Carpe Diemより図を引用)。podがGCPのサービスアカウントを知る必要があり、ダウンロードしたキーをどう管理する問題が起きてしまう。

Workload IdentityによるGCPのサービスアカウントとKubernetesのサービスアカウントの紐付け

前提知識が揃ったので、Workload Identityの説明。Workload Identityでは、KubernetesのサービスアカウントとGCPのサービスアカウントを紐付けることによって、GCPのサービスアカウントのキーをダウンロードせずGCP側のリソースを使えるようにする。Workload IdentityでPodからのGCPリソースアクセスをセキュアにする - Carpe Diemより、再び図を引用。

具体的な設定方法

GCPのサービスアカウントを作成

今回はBigQueryからデータを取得して、推薦した結果をGCSに置くバッチジョブを対象にします。BigQueryとGCSの権限に絞ったサービスアカウントをまず作成しましょう。

resource "google_service_account" "recommend_related_examples_service_account" {
  account_id   = "recommend-related-examples"
  display_name = "Service account for recommend-related-examples"
}

resource "google_project_iam_member" "recommend_related_examples_roles_binding" {
  for_each = toset(local.recommend_related_examples_roles)
  role     = each.value
  member   = "serviceAccount:${google_service_account.recommend_related_examples_service_account.email}"
}

locals {
  recommend_related_examples_roles = [
    "roles/bigquery.dataViewer",
    "roles/bigquery.jobUser",
    "roles/storage.admin",
    "roles/storage.objectCreator",
    "roles/storage.objectViewer",
  ]
}

Kubernetesのnamespaceの作成

GKEのクラスタ全体にbindをしたくないので、namespaceを事前に作っておきます。

kind: Namespace
apiVersion: v1
metadata:
  name: my-project-batch

Kubernetesのサービスアカウントの作成

GCPのサービスアカウントを紐付ける用のKubernetesのサービスアカウントを作成します。annotationsを使って、どのGCP側のサービスアカウントと紐付けるかを記述します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-job
  namespace: my-project-batch
  annotations:
    iam.gke.io/gcp-service-account: recommend-related-examples@my-project.iam.gserviceaccount.com

annotationsとは何ぞや...という気持ちになるけど、labelsと同じようにkey/value形式でオブジェクトに対してメタデータを付与する方法であるが、labelsと違ってキーの長さなどに制約があまりない、というのが特徴のようだ(labelsはselectorなどで検索する用途で使われる想定っぽい)。

Workload Identityを有効にする

GCPのサービスアカウントに対してWorkload Identityの権限を有効にします。

resource "google_service_account_iam_member" "recommend_related_examples_wi_iam_binding" {
  service_account_id = google_service_account.recommend_related_examples_service_account.name
  member             = "serviceAccount:my-project.svc.id.goog[ml-news-batch/recommend-related-examples]"
  role               = "roles/iam.workloadIdentityUser"
}

これでクラスタには最小限の権限、個別のworkloadには適切な権限を付与しつつ、管理方法も面倒なことにならずにいけるようになりました。