Creating google_project_iam_binding deletes google_project_iam_member

817 Views Asked by At

I'm creating a Cloud Run service to act upon PubSub trigger using Terraform.

I've added the relevant terraform code, and besides that I also have the following portion already defined:

# bind token creations permission to the default app engine service account
# to allow gcp cloud functions to create firebase custom tokens
resource "google_project_iam_member" "serviceAccountTokenCreator" {
  project = var.PROJECT_ID
  role    = "roles/iam.serviceAccountTokenCreator"
  member  = "serviceAccount:${data.google_app_engine_default_service_account.default.email}"
}

I'm getting this weird behavior where after terraform applying and provisioning the Cloud Run service, it deletes the previously defined google_project_iam_member. This is what terraform plan shows:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_project_iam_member.serviceAccountTokenCreator will be created
  + resource "google_project_iam_member" "serviceAccountTokenCreator" {
      + etag    = (known after apply)
      + id      = (known after apply)
      + member  = "serviceAccount:[email protected]"
      + project = "myproject"
      + role    = "roles/iam.serviceAccountTokenCreator"
    }

When I hit terraform apply again, it mutates the new google_project_iam_binding resource that was created. This is what I get for terraform plan:

Terraform will perform the following actions:

  # google_project_iam_binding.dsp-records will be updated in-place
  ~ resource "google_project_iam_binding" "dsp-records" {
        id      = "myproject/roles/iam.serviceAccountTokenCreator"
      ~ members = [
          - "serviceAccount:[email protected]",
            # (1 unchanged element hidden)
        ]
        # (3 unchanged attributes hidden)
    }

And so I'm getting this weird circle every time I hit terraform apply.

This is how I defined google_project_iam_binding:

resource "google_service_account" "dsp-records" {
  account_id   = "dsp-records-invoker"
  display_name = "dsp-records Cloud Run Pub/Sub Invoker"
}

resource "google_cloud_run_service_iam_binding" "dsp-records" {
  location = google_cloud_run_service.dsp-records.location
  service  = google_cloud_run_service.dsp-records.name
  role     = "roles/run.invoker"
  members  = ["serviceAccount:${google_service_account.dsp-records.email}"]
}

resource "google_project_iam_binding" "dsp-records" {
  project = google_cloud_run_service.dsp-records.project
  role    = "roles/iam.serviceAccountTokenCreator"
  members = ["serviceAccount:${google_service_account.dsp-records.email}"]
}

Can someone explain this?

1

There are 1 best solutions below

0
On

google_project_iam_member adds a single member to a role. If you use this resource, Terraform will ensure that the specified member has the specified role, without affecting other members who might also have that role. It's useful for adding individual permissions without altering the entire set of entities that have access to a resource.

google_project_iam_binding manages the entire set of members for a role. This means that Terraform expects to have exclusive control over who has the specified role. If Terraform's state doesn't match the actual members with that role, it will update the role to match its state, potentially adding or removing members.

The Issue When you apply your Terraform configuration, the google_project_iam_member resource tries to add a specific service account to the roles/iam.serviceAccountTokenCreator role. This is fine on its own. However, when you use google_project_iam_binding with the same role (roles/iam.serviceAccountTokenCreator), Terraform sees it as taking over complete control of that role's membership.

If the set of members in your google_project_iam_binding doesn't include every member you've added via google_project_iam_member, Terraform will try to reconcile this by modifying the google_project_iam_binding to match its state. This leads to the "weird circle" of Terraform applying changes every time you run terraform apply, as it oscillates between adding the member through google_project_iam_member and then trying to reconcile that with the state managed by google_project_iam_binding.

Resolution To resolve this, you should choose one method (either google_project_iam_member or google_project_iam_binding) for managing IAM roles and stick with it across your Terraform configuration for a given role. Which method you choose depends on your needs:

If you need to ensure that specific accounts have specific roles without managing the entire set of accounts for those roles, use google_project_iam_member. If you want Terraform to manage the entire set of accounts for a role, ensuring that only the accounts defined in your Terraform configuration have that role, use google_project_iam_binding. If you decide to use google_project_iam_binding, make sure to include all the service accounts that should have the role as members of that resource. This way, you avoid the oscillation and ensure your Terraform configuration accurately reflects your intended state in GCP.