👮

Google CloudのIAM基本概要とTerraform管理時の注意点

2025/01/19に公開

IAMの基本概要

IAMの主要概念としては、プリンシパル, ロール, 権限 の3つがあります。
さらに、それらの紐づけを表す概念として、バインディング と ポリシー(許可ポリシーとも呼ばれる)があります。

プリンシパル

「プリンシパル」はアクションを起こす主体(またはその集合体)です。具体的には、Googleアカウント、サービスアカウント、Googleグループなどが該当します。
AWSでは同じくプリンシパルと呼ばれる概念(IAMユーザ、グループ、ロールなど)にあたります。

権限

「権限」はどのような操作を行えるかを示すものです。多くの場合、Google CloudのREST APIの各メソッドと1対1で対応しています。
AWSにおけるIAMポリシーに記述する「アクション」に近い概念です。

ロール

「ロール」は複数の「権限」をまとめた集合体です。Google Cloudがあらかじめ定義している事前定義ロールと、ユーザが独自に作成できるカスタムロールがあります。
また、基本ロール(Owner/Editor/Viewerなど)も存在しますが、これは古い形式であり権限の範囲が大まかです。Google Cloudではユースケースごとに詳細な権限が付与された事前定義ロールが多数用意されているため、基本ロールではなく事前定義ロールの使用を推奨します。
ロールはAWSでいうIAMポリシーに近い概念です。

バインディング

ここまでをまとめると、「権限」の集合体で「ロール」を作成し、ロールを「プリンシパル」に紐づけることによって、プリンシパルは権限内容を実行できるようになります。
この、ロールとプリンシパルの紐づけを示すのが「バインディング」です。特に注意したいのは、「1つのロールとN個のプリンシパルとの紐づけ」が1つのバインディング である、という点です。別の言い方をするのであれば、「Aロールをアタッチしているプリンシパルたち」という関係こそがバインディングの概念です。

  • 例1: プリンシパルCが2つのロールに紐づけられている場合、プリンシパルCに対するバインディングは2つ存在します。(先述の図を参照)
  • 例2: プリンシパルA,B,Cがそれぞれ roles/bigquery.jobUser に紐づけられている場合は、バインディングは1つです。Aとroles/bigquery.jobUser、Bとroles/bigquery.jobUser、Cとroles/bigquery.jobUserの計3バインディングという状態はにはなりません。(先述の図を参照)

バインディング:ロール:プリンシパルは必ず1:1:Nです。

ポリシー

ポリシーは、すべてのバインディングの集合体です。つまり、どのプリンシパルがどのロールに紐づいているかを一覧できるのがポリシーです。
例えばプロジェクトのポリシーを確認するコマンド gcloud projects get-iam-policy my-project-name --format=json を実行すると下記のような結果を取得でき、ポリシーはバインディングの集合体であることがわかります。
また、1つのバインディングが1つのロールと複数のユーザの紐づきを表現していることがわかります。

{
  "bindings": [
    {
      "role": "roles/storage.objectAdmin",
      "members": [
        "user:my-user@example.com",
        "serviceAccount:my-other-app@appspot.gserviceaccount.com",
        "group:my-group@example.com"
      ]
    },
    {
      "role": "roles/storage.objectViewer",
      "members": [
        "user:my-user@example.com"
      ]
    }
  ]
}

なお、Google Cloudの権限(IAM)を適用できる階層には、組織単位、フォルダ単位、プロジェクト単位、リソース単位などがあります。しかし、この記事の後半ではこれらのスコープ設定に深入りしないため、詳細は別の記事に譲ります。

Terraform管理の注意点

さて、本題です。プリンシパルにロールをアタッチする、つまりバインディングを作成/変更をするTerraformリソースは google_project_iam_policy, google_project_iam_binding, google_project_iam_member です。

このうち、google_project_iam_policygoogle_project_iam_bindingは既存でアタッチされている権限を剥奪してしまう可能性があります。ポリシー、バインディングの概念を確認しつつ、どのような動作を行うか確認していきましょう。

google_project_iam_policy

google_project_iam_policyはポリシーを定義するリソースです。ポリシーはすべてのバインディングの集合のため、Applyするとgoogle_project_iam_policyに記述されていないバインディングはすべて消えます
つまり、自動的にアタッチされていたロールを含め、Applyを行ったTerraformワークスベース外で付与していたロールがすべて解除されてしまいます。

resource "google_project_iam_policy" "project" {
  project     = "your-project-id"
  policy_data = data.google_iam_policy.costs_manager.policy_data
}

# プリンシパルD(alice@example.com)にロールC(roles/billing.costsManager)を紐づける
data "google_iam_policy" "costs_manager" {
  binding {
    role = "roles/billing.costsManager"

    members = [
      "user:alice@example.com",
    ]
  }
}

API有効化などで自動的にバインディングされていたものも消えてしまうため、扱いが難しいリソースです。

また、google_project_iam_policyを利用する場合も、Apply前にimportを行い、既存のバインディングをすべて取り込んだポリシーをTerraform上で作成しておくことが必要です。

google_project_iam_binding

google_project_iam_bindingはバインディングを定義するリソースです。バインディングは1つのロールにアタッチするプリンシパルたちをリストアップしています。このため、google_project_iam_policyとは異なり、新規ロールとプリンシパルをバインディングする際は他の既存バインディングへの影響はありません。

一方、既存バインディングに新たにプリンシパルを追加したい場合には、既存プリンシパルもコードに追加しなくてはいけません。

# 既存ロールB(roles/bigquerydatapolicy.viewer)に新規にプリンシパルDを紐づける
resource "google_project_iam_binding" "project" {
  project = "your-project-id"
  role    = "roles/bigquerydatapolicy.viewer"

  members = [
    "serviceAccount:app@my-project.iam.gserviceaccount.com", # 既存プリンシパル
    "user:alice@example.com", # 新規プリンシパル
  ]
}

既存プリンシパルを記述しない場合、バインディングが上書きされるため、既存プリンシパルとロールのバインディングが外れてしまいます。

google_project_iam_member

最後のgoogle_project_iam_memberは指定したプリンシパルを、バインディングのプリンシパルリストに追加するリソースです。既存バインディングが存在しない場合は新たに作成します。
副作用が発生しないため、一番保守的な形態です。

# 既存ロールB(roles/bigquerydatapolicy.viewer)に新規にプリンシパルEを紐づける
resource "google_project_iam_member" "project" {
  project = "your-project-id"
  role    = "roles/bigquerydatapolicy.viewer"
  member  = "user:yamada@example.com" # 新規プリンシパルのみ.既存プリンシパルは記述しなくて問題ない
}

どう使い分けるか

google_project_iam_policyは記載のないバインディングを削除するため、ユーザ権限を厳格に一元管理することは可能です。。
一方、実運用ではサービスやプロジェクトごとにTerraformワークスペースを分け、それぞれのスペースで権限付与を行うことが少なくありません。柔軟性の担保を含め、筆者はgoogle_project_iam_binding, google_project_iam_memberを使うことが多いです。
google_project_iam_binding, google_project_iam_memberについてはワークスペースをまたがない権限は基本的にgoogle_project_iam_binding、またぐ場合はgoogle_project_iam_memberを使用しています。この両者の切り替えはあまり手間がかからないため、カジュアルに変えています。

Appendix

https://cloud.google.com/iam/docs/overview?hl=ja

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam

https://blog.g-gen.co.jp/entry/how-to-use-iam-resources-of-terraform

Discussion