terraform-google-modules / terraform-google-iam

Manages multiple IAM roles for resources on Google Cloud

Home Page:https://registry.terraform.io/modules/terraform-google-modules/iam/google

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Invalid for_each argument when trying to create additive bindings

joe-a-t opened this issue · comments

commented

Overview

If you try to create a project, service account, and service account binding at the same time, terraform errors out if the binding mode is additive but not if the binding mode is authoritative.

My suspicion is that this is because authoritative uses known values for the keys in the for_each but additive uses the member in the keys, which includes the service account's email and is not known until the service account has been created. If the helper could be refactored so that an unknown value (member) is not in the keys, I suspect additive would no longer cause this error.

Sample Configuration

resource "google_project" "main" {
  name                = var.project_id
  project_id          = var.project_id
  org_id              = var.org_id
  folder_id           = var.folder_id
  billing_account     = var.billing_account
  auto_create_network = false
}

module "google_test_sa" {
  source       = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
  project_id   = google_project.main.project_id
  names        = ["google-test-sa"]
  display_name = "google-test-sa"
}

module "impersonate_service_account_iam_bindings" {
  source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
  service_accounts = [
    module.google_test_sa.service_account.email,
  ]
  project = google_project.main.project_id
  mode    = "additive"
  bindings = {
    "roles/storage.objectAdmin" = [
      "serviceAccount:${module.google_test_sa.service_account.email}",
    ]
  }
}

Error message

Error: Invalid for_each argument

  on .terraform/modules/impersonate_service_account_iam_bindings/modules/service_accounts_iam/main.tf line 50, in resource "google_service_account_iam_member" "service_account_iam_additive":
  50:   for_each           = module.helper.set_additive

The "for_each" value depends on resource attributes that cannot be determined
until apply, so Terraform cannot predict how many instances will be created.
To work around this, use the -target argument to first apply only the
resources that the for_each depends on.

Unfortunately there's no way to make the keys deterministic with this set up, I recommend using the google_service_account_iam_member resource directly or using authoritative mode.

commented

@morgante what about changing the schema of bindings so that it can take both a name for the entity (which could be a known value) and the email? Then https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L85 could use that name instead of the member when creating keys_additive.

@joe-a-t We might be able to update the conditional bindings in #109 to be called advanced_bindings and let them use the title as the key.

commented

@morgante are you talking about https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L96? It already has both member and title so I'm not sure how it would work if you want to do a conditional with multiple members but if you're able to refactor that to solve this issue and can make description and expression optional, that would be much appreciated.

It already has both member and title so I'm not sure how it would work if you want to do a conditional with multiple members

Good point, but do you need multiple members for your use case?

It might be possible for us to special case the scenario where there's only 1 member, but otherwise I still think the best recommendation is to just use google_service_account_iam_member directly.

commented

Yes, we need multiple members. What about a change like:

  advanced_bindings = [
    {
      role        = "roles/storage.admin"
      title       = "expires_after_2019_12_31"
      description = "Expiring at midnight of 2019-12-31"
      expression  = "request.time < timestamp(\"2020-01-01T00:00:00Z\")"
      members = {
        service_account = "serviceAccount:${var.sa_email}",
        group = "group:${var.group_email}",
        user = "user:${var.user_email}",
      }
    }
  ]

then the key in members (a known value) is used as ${member} in https://github.com/terraform-google-modules/terraform-google-iam/blob/master/modules/helper/main.tf#L96. It does add more info you need to put in the advanced bindings but it would solve this issue.

And then also making title/description/expression optional

It seems like what you're trying to accomplish would best be done using the project_roles parameter on the service accounts module.

commented

project_roles is possible. However, there is some awkwardness seeing what permissions are if you are creating a resource and a service account then trying to add permissions to both the new service account and existing group/service accounts/etc. Instead of having a single bindings location to determine what the permissions that are added in this directory are like this would provide,

module "google_test_sa" {
  source       = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
  project_id   = google_project.main.project_id
  names        = ["google-test-sa"]
  display_name = "google-test-sa"
}

module "impersonate_service_account_iam_bindings" {
  source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
  service_accounts = [
    module.google_test_sa.service_account.email,
  ]
  project = google_project.main.project_id
  mode    = "additive"
  bindings = {
    "roles/storage.objectAdmin" = merge(
      {
        google-test-sa = "serviceAccount:${module.google_test_sa.service_account.email}"
      },
      var.storage_object_admins_map
    )
  }
}

you have to do this where the permission is added both in the service account creation and separately for the other objects.

module "google_test_sa" {
  source       = "git@github.com:terraform-google-modules/terraform-google-service-accounts"
  project_id   = google_project.main.project_id
  names        = ["google-test-sa"]
  display_name = "google-test-sa"
  project_roles = ["${google_project.main.project_id}=>roles/storage.objectAdmin"]
}

module "impersonate_service_account_iam_bindings" {
  source = "git@github.com:terraform-google-modules/terraform-google-iam//modules/service_accounts_iam"
  service_accounts = [
    module.google_test_sa.service_account.email,
  ]
  project = google_project.main.project_id
  mode    = "additive"
  bindings = {
    "roles/storage.objectAdmin" = var.storage_object_admins_list
  }
}

That dynamic of having multiple scattershot places where the same permissions are added within a single directory is harder for users to follow. I know the ability to add the same permission in multiple places is the benefit of additive, but we would like to only have to have separate bindings if we are trying to add permissions in a different directory/statefile

Another option is targeted applies (terraform apply -target=module. google_test_sa).

We're probably not going to change this in the module though as there are multiple different alternatives that can accommodate your use case without complicating the module interface unnecessarily.

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days