TerraformでGoogle CloudのIAMポリシーを作成する際の罠と回避策

Google CloudのIAM管理をTerraformで行う際に、google_project_iam_binding や google_project_iam_policy といったAuthoritativeリソースには思わぬ落とし穴が潜んでいる。この罠を回避し、適切にリソースを管理する方法を解説する。


既に丁寧に解説してる記事が多数あり、枯れたトピックではあるが自分なりに整理してみたので時間のある方はお付き合い頂きたい。


## 1. 前提

Google CloudのIAM制御は、ResourceとPolicyとBindingとRoleとPrincipleという概念で成り立っている。

Resouceは下図でいう右に記載されてる、組織やフォルダ、プロジェクトや各種BigQuery, Cloud Runなどのサービスを指す。いずれかの階層のResourceに対する権限を持った場合はそれ以下の階層への権限を自動でエスカレートして持つことになる。


Roleは権限の束のようなもので、それを特定のPrinciple(member)に紐づけたものをBindingと呼び、複数のroleとprincipleのセット(Binding)をさらに統合したものがPolicyとして特定のResourceに紐づく形である。

![スクリーンショット 2024-12-04 9.18.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252596/7c527041-4cf3-4777-f1b8-76f0ae80e5e9.png)


参考: https://cloud.google.com/iam/docs/overview?hl=ja#cloud-iam-policy



TerraformではこのProjectという階層のリソースに対して権限を設定する際に、以下の３つを使用することができる。

- google_project_iam_member
- google_project_iam_policy
- google_project_iam_binding



roleとprinciple群の組み合わせをどれだけたくさん、特定のプロジェクトレベルで適用したいかという制御要件に基づいて上記３つを使用していくことになるのだが、ここに罠が潜んでいるので以下で解説したい。

## 2. なぜ google_project_iam_binding や google_project_iam_policy が危険か

Terraformのgoogle_project_iam_binding や google_project_iam_policy リソースは、IAMポリシーの管理を一括で行うAuthoritativeなリソースである。

参考: [Terraform公式ドキュメント](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam#google_project_iam_policy-1 )

Authoritativeとは詰まるところ、Single Source of Truthであり、Terraformのリソースに定義された事実のみが実態として残り、定義されていない既存リソースなどは暗黙的に削除されるというもの。

**つまり既存のIAMリソースを破壊してしまう可能性がある。**

google_project_iam_binding は指定したロールに対する１つ以上のメンバーをTerraform定義した内容のみで置き換える。これにより、以下のような事態が発生することがある。

Terraform管理外で設定されたIAMメンバー（例えば手動で追加されたユーザーやサービスアカウント）へのロールのbindingであったり、他のTerraformモジュールなどで設定した内容も削除される。

```
resource "google_project_iam_binding" "example" {
  project = "my-project"
  role    = "roles/editor"
  members = [
    "user:example1@example.com",
  ]
}
```


このコードを適用すると、my-project というGoogleCloudリソースに対して `roles/editor` を持つメンバーが `user:example1@example.com` のみに置き換えられる。Planを実行しても既存メンバーのリソースはTerraform管理下に入っていないのでStateファイルとの差分は表示されない。すなわち削除される事実は教えてくれないので、結果として意図しない権限剥奪が発生する。



google_project_iam_policyも同様だ。　bindingが指定したロールに対するPrincipleのセットの定義だったのに対して、プロジェクトという単位のリソースに対して、複数のbindingを定義した内容のみで置き換える。これにより、プロジェクトというリソースに紐づけられており下の定義に含まれないBindingsは全て消え去ることになり、甚大な障害を引き起こす。

```
resource "google_project_iam_policy" "project" {
  project     = "my-project"
  policy_data = data.google_iam_policy.editor.policy_data
}

data "google_iam_policy" "editor" {
  binding {
    role = "roles/editor"

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

## 3. どういう時にAuthoritativeリソースを使っても良いのか？

google_project_iam_binding や google_project_iam_policy を使っても良い場面は、既存リソースとの整合性を気にする必要がない以下のような場合に限定される。

### 3-1. プロジェクトを新規作成する場合

新規プロジェクトでIAM設定を完全にTerraformで管理する場合は、Authoritativeリソースが有効である。プロジェクト作成時点でポリシーが何も設定されていないため、全てをTerraformで制御できる。

```
resource "google_project_iam_binding" "example" {
  project = "my-new-project"
  role    = "roles/viewer"
  members = [
    "user:example@example.com",
    "serviceAccount:example-sa@my-new-project.iam.gserviceaccount.com"
  ]
}
```


既存のIAM設定がないため、terraform apply を安全に実行でき、google_project_iam_memberのようにroleとmemberを1:1でしか紐付けることができず可視性が悪くなるデメリットもなく、見通しが良くなる。

### 3-2. テスト環境や短期プロジェクト

削除されても問題のないリソースや一時的な環境の場合、Authoritativeリソースを使用しても大きなリスクは伴わない。例えば、テスト環境やPOC（概念実証）プロジェクトなど。しかし、検証中のメンバーがいた場合権限剥奪により作業を中断させてしまうので、やはり慎重にIAMの既存リソースへの影響範囲の特定は行わなければならない。

### 3-3. 定義すべき権限周りの設定が膨大にあり、Terraform管理外のIAMリソースを全て管理下に置ける場合

後述の4.2でも述べるが、google_project_iam_memberはNon-Authoritativeなリソースなので、Terraform定義外のリソースに対しては一切影響を及ぼさない。



なのでTerraform管理外の既存リソースがある場合はgoogle_project_iam_memberを使用するのが安牌というわけだが、roleとprincipleを1:1でしか定義できないので、プロジェクト内のIAM制御を全てこのリソースで行おうとすると、Terraformリソースが大量に生成されstate管理が重くなったりfor_each文などを組み合わせることで可視性が落ちたりする。



なので後述にある通りTerraform Importで既存リソースをすべてTF管轄にするオペレーションを担保できる場合に限り、IAM制御を一つのリソースにまとめて記述できるgoogle_project_iam_policyや1つのRoleに対してまとめてPrincipleを定義できるgoogle_project_iam_bindingの使用が有効な選択肢となってくる。

## 4. 既存リソースがある場合の回避策

既存のIAMリソースを誤って破壊しないためには、以下の2つの方法がある。それぞれメリット・デメリットがあるため、用途に応じて使い分けるべきである。

### 4-1. Terraform Importで全てtf化する

既存リソースをTerraformで管理したい場合は、terraform import を使って現在のIAM設定を状態管理に取り込む方法がある。

手順

1. 現在のIAM設定をインポート
    ```
    terraform import google_project_iam_binding.example "my-project roles/viewer"
    ```

    
2. インポート後に状態を確認し、tfファイルを記述
    ```
    resource "google_project_iam_binding" "example" {
      project = "my-project"
      role    = "roles/viewer"
      members = [
        "user:example1@example.com",
        "user:example2@example.com"
      ]
    }
    ```

3. コードと実際の設定が一致していることを確認

**メリット**
・全ての設定をTerraformでの管理に移行できる。
・IAMポリシーの意図しない変更を防止できる。

**デメリット**
・インポート作業が手間。
・非効率なリソース管理となる可能性がある。



※ terraform importコマンドを使用したが、import blockを使用することでも既存リソースのimportは可能。

### 4-2. google_project_iam_member を使用する

google_project_iam_member は、特定のメンバーとロールの紐付けのみを管理するNon-Authoritativeリソースである。これを利用することで、既存のIAMポリシーに影響を与えずに管理を追加できる。

例: google_project_iam_member の使用
```
resource "google_project_iam_member" "example" {
  project = "my-project"
  role    = "roles/viewer"
  member  = "user:example@example.com"
}
```


ただし、roleとmemberを1:1でしか紐づけられないので、複数のプロジェクトで複数のロールにそれぞれのPrincipleを紐づけたいとなった場合は以下のようなハックが必要。(あくまで書き方の一例)

```
locals {
  iam_bindings = {
    "my-project" = {
      "roles/bigquery.dataViewer" = [
        "user:example6@example.com",
        "user:example6@example.com2",
      ]
      "roles/bigquery.dataOwner" = [
        "group:example-group@example.com",
      ]
    }
  }
}


resource "google_project_iam_member" "iam_bindings" {
  for_each = { for f in flatten([
    for project_id, roles in local.iam_bindings : [
      for role, members in roles : [
        for member in members : {
          project_id = project_id
          role       = role
          member     = member
        }
      ]
    ]
    ]) : "${f.project_id}_${f.role}_${f.member}" => f
  }

  project = each.value.project_id
  role    = each.value.role
  member  = each.value.member
}
```

とはいえだいぶ可視性が悪くなるので、既存リソースの破壊の心配がないのであればgoogle_project_iam_bindingと併用するケースもある。

> google_project_iam_binding resources can be used in conjunction with google_project_iam_member resources only if they do not grant privilege to the same role.



**メリット**
・既存のIAM設定を壊さない。
・特定のメンバーやロールの追加管理が容易。

**デメリット**
・大規模なIAM管理には向かない。
・上述のようにfor_eachのハックで定義自体はシンプルに保てるが、管理するメンバーが増えると管理リソースが肥大化するのでterraformの処理が重くなる。

## まとめ

Google CloudのIAM管理をTerraformで行う際は、google_project_iam_binding や google_project_iam_policy の使用に注意が必要である。新規プロジェクトではAuthoritativeリソースが有効だが、既存リソースがある場合は次のいずれかを選択するのが良い。

- 既存リソースを terraform import して完全管理下に置く。
- google_project_iam_member を使い、既存設定を確実に壊さないように管理する。


チームやプロジェクトの状況に応じて適切な方法を選び、その方法によるリスク要因は許容可能であるかどうかと向き合っていかなければならない。