Terraform(google provider) で Service Account に Role をバインドするときの罠

Cloud Run から Cloud SQL へのコネクションを作るには Service Account によって承認を通す必要がある。Cloud Function と同じアプローチ。  
https://cloud.google.com/sql/docs/mysql/connect-run?hl=ja  

前例の記事などがなかったので雰囲気で手を動かしてたらがっつりハマったので備忘録を残していく。

## Service Account を作る

Terrafrom で Service Account のリソースを作ると以下のような感じ。

```
resource "google_service_account" "sa" {
  account_id = "my-sa"
  display_name = "my-sa"
}
```

Croud Run側のリソース定義での Service Account 参照。今回作成する Cloud Run はフルマネージド。

```
resource "google_cloud_run_service" "cloud-run" {
  name = my-cloud-run
  location = "us-central1"

  template {
    spec {
      containers {
        image = "gcr.io/my-project/image-name"
      }
      // Ref: service account resource
      service_account_name = google_service_account.main.email
    }
    metadata {
      annotations = {
        "autoscaling.knative.dev/maxScale" = "1000"
        // Ref: database instance resource
        "run.googleapis.com/cloudsql-instances" = google_sql_database_instance.main.connection_name
        "run.googleapis.com/client-name" = "cloud-console"
      }
    }
  }
  traffic {
    percent = 100
    latest_revision = true
  }
}

resource "google_sql_database_instance" "my-instance" {
  name             = "my-instance"
  database_version = "MYSQL_5_7"
  region           = "us-central1"
  settings {
    tier = D0
  }
}
```

## Role のバインドを試行錯誤するもハマる

#### 検証1

`data "google_iam_policy"` で作成したポリシーを `resource "google_cloud_run_service_iam_policy"` へ適用する作戦で試みてみる。

Cloud Run サービスを一般公開するポリシーの設定についてドキュメントに記載があったのでそちらを参考に見様見真似してみた。  
https://www.terraform.io/docs/providers/google/r/cloud_run_service.html#example-usage-cloud-run-service-noauth

実際のコードは以下の通り。

```
data "google_iam_policy" "my-policy" {
  binding {
    role = "roles/cloudsql.client"
    members = [
      "serviceAccount:${google_service_account.main.email}",
    ]
  }
}

resource "google_cloud_run_service_iam_policy" "my-policy" {
  location    = google_cloud_run_service.cloud-run.location
  project     = google_cloud_run_service.cloud-run.project
  service     = google_cloud_run_service.cloud-run.name
  policy_data = data.google_iam_policy.my-policy.policy_data
}

resource "google_cloud_run_service_iam_member" "my-policy" {
  location = google_cloud_run_service.meshi-log.location
  service = google_cloud_run_service.meshi-log.name
  role = "roles/cloudsql.client"
  member = "serviceAccount:${google_service_account.main.email}"
}
```

これは エラーになる。

```
Error: Error setting IAM policy for cloudrun service "v1/projects/my-project/locations/us-central1/services/my-sa": googleapi: Error 400: Role roles/cloudsql.client is not supported for this resource.
```

Cloud Run の IAM Policy において `Roles/cloudsql.client` の Role はサポートしていないということである。

当然 Cloud SQL へのコネクションは貼れていないため Stack Driver を確認すると下記のようなエラーログが出力される。

```sh
dial unix /cloudsql/my-project:us-central1:my-instance: connect: connection refused
CloudSQL connection failed. Please see https://cloud.google.com/functions/docs/sql#troubleshooting for additional details: ensure that the account has access to "my-project:us-central1:my-instance" (and make sure there's no typo in that name). Error during createEphemeral for my-project:us-central1:my-instance: googleapi: Error 403: The client is not authorized to make this request., notAuthorized
```

#### 検証2

Terraform のドキュメントを眺めていて `resource "google_service_account_iam_binding"` なるものを見つけた。

https://www.terraform.io/docs/providers/google/r/google_service_account_iam.html

これでは？  
`resource "google_service_account_iam_member"` を使えば 特定の Service Account に対して Policy の定義とバインドを同時にできるみたいなのでそちらを使うことに。

```
resource "google_service_account_iam_member" "sql" {
  service_account_id = google_service_account.sa.name
  role               = "roles/cloudsql.client"
  member             = "serviceAccount:${google_service_account.sa.email}"
}
```

これもダメなのか...。

```
Error: Error applying IAM policy for service account 'projects/my-project/serviceAccounts/my-sa@my-project.iam.gserviceaccount.com': Error setting IAM policy for service account 'projects/my-project/serviceAccounts/my-sa@my-project.iam.gserviceaccount.com': googleapi: Error 400: Role roles/cloudsql.client is not supported for this resource., badRequest
```

## 答え: google_project_iam_binding, google_project_iam_member を使え

terraform-provider-google のリポジトリに同事象のISSUEを発掘した。  
https://github.com/terraform-providers/terraform-provider-google/issues/1225

> In your case, you should be looking at the google_project_iam_binding resource- similar to how your gcloud command was gcloud projects add-iam-policy-binding. The google_service_account_iam_binding resource corresponds to this gcloud command.

特定のプロジェクトに Service Account を追加、および Role のバインドの操作を `gcloud` コマンドから行う場合は `gcloud project` コマンドを使うので、Terraform での操作もそれに沿う必要があるのだと解釈した。

```
resource "google_project_iam_member" "cloud_sql_connection" {
  role = "roles/cloudsql.client"
  member = "serviceAccount:${google_service_account.sa.email}"
}
```

これで Cloud Run が  `roles/cloudsql.client` の Role がバインドされた Service Account を利用して Cloud SQL とコネクションできるようになった。

同じ罠に引っかかる人結構いるのでは？

## 参考にした記事とか

- https://www.sethvargo.com/configuring-cloud-run-with-terraform/
- https://blog.ri52dksla.dev/posts/gcp-terraform-cloud-run-and-gcs/
- https://cloud.google.com/sql/docs/mysql/connect-run?hl=ja
- https://www.terraform.io/
- https://github.com/terraform-providers/terraform-provider-google/issues/1225

## 最後に

Cloud Run いいかんじ。