hashicorp / terraform-provider-google

Terraform Provider for Google Cloud Platform
https://registry.terraform.io/providers/hashicorp/google/latest/docs
Mozilla Public License 2.0
2.28k stars 1.72k forks source link

Support for roles/run.invoker IAM role in google_cloudfunctions2_function_iam_policy for Google Cloud Functions Gen 2 in Terraform Google Provider v4.74.0 #15264

Open iamlohit opened 1 year ago

iamlohit commented 1 year ago

Community Note

Description

Google Cloud recently introduced second generation Cloud Functions, which are built on the same underlying infrastructure as Cloud Run. This change has implications for setting the invoker role, which allows a function to receive HTTP requests. Currently, setting the role roles/cloudfunctions.invoker with the Terraform Google provider doesn't suffice for second generation Cloud Functions, as they require the role roles/run.invoker to be set through Cloud Run. As a workaround, one needs to manually run the following gcloud command, which is not scalable:

gcloud functions add-invoker-policy-binding function-name
--region="region-name"
--member="allUsers"

However, if we try to set roles/run.invoker using the google_cloudfunctions2_function_iam_policy resource, we encounter the following error:

Error: Error setting IAM policy for cloudfunctions2 function "projects/project-id/locations/region-name/functions/function-name": googleapi: Error 400: Invalid argument: 'An invalid argument was specified. Please check the fields and try again.'

This feature request is to enhance the google_cloudfunctions2_function_iam_policy resource to support setting the roles/run.invoker IAM role for second generation Cloud Functions, eliminating the need for the manual gcloud command and resolving the error.

New or Affected Resource(s)

Potential Terraform Configuration

resource "google_cloudfunctions2_function" "function" {
  ...
}

data "google_iam_policy" "invoker" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

resource "google_cloudfunctions2_function_iam_policy" "invoker" {
  location    = google_cloudfunctions2_function.function.location
  cloud_function = google_cloudfunctions2_function.function.name
  policy_data = data.google_iam_policy.invoker.policy_data
}

References

Terraform Documentation: google_cloudfunctions2_function_iam Version 4.74.0 Latest

rileykarson commented 1 year ago

Note: We should use -log-http with gcloud to determine the underlying API call. Is it possible this is done using a Cloud Run IAM resource?

tweakster commented 1 year ago

I can confirm it does use Cloud run.

I believe this should get around the issue.


resource "google_cloud_run_v2_service_iam_policy" "invoker" {
  location    = google_cloudfunctions2_function.function.location
  name        = google_cloudfunctions2_function.function.name
  policy_data = data.google_iam_policy.invoker.policy_data
}
EduardJoy commented 11 months ago

Greetings! This issue renders google_cloudfunctions2_function completely unusable when you need permission management. @tweakster the google_cloud_run_v2_service_iam_policy workaround does not solve the problem anymore as the command throws

Resource 'functionName' of kind 'SERVICE' in region 'us-central1' in project 'projectName' does not exist.

Hopefully someone will pick up this issue as soon as possible, because without being able to set up policies, cloud functions v2 are completely unusable.

tweakster commented 11 months ago

@EduardJoy It still works for me. Are you sure you you have all the details correct?

EduardJoy commented 11 months ago

Attaching the code here. If I am not missing something I think indeed that the issue is still active @tweakster


data "google_iam_policy" "public_policy" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers",
    ]
  }
}

resource "google_cloud_run_v2_service_iam_policy" "public_policy_beforecreate" {
  location    = google_cloudfunctions2_function.beforeCreateFunction.location
  name        = google_cloudfunctions2_function.beforeCreateFunction.name
  policy_data = data.google_iam_policy.public_policy.policy_data

}
EduardJoy commented 11 months ago

Just upgraded to version 5.1.0 and the issue is still active.

davcen commented 10 months ago

I'm run into a similar issue: i have defined a bunch of Cloud Functions 2nd gen invoked by Google Workflow, using the google_cloudfunctions2_function_iam resource i received the same error showed in the first post.

The official documentation is not very clear about how set this type of permission, relying on the statement "from gcloud use the add-invoker-policy-binding command for 2nd gen Cloud Functions".

I've looked a bit into that gcloud command behaviour, in the end it's just a wrapper for this sequence of operations:

  1. describe the function (and extract the name of the relevant Cloud Run Service)
  2. describe the Cloud Run service
  3. set the iam policy on the Cloud Run Service

So it is correct to use the google_cloud_run_service_iam (i've used the google_cloud_run_service_iam_member flavour, in my case, with success)

See also this example: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloudfunctions2_function#example-usage---cloudfunctions2-scheduler-auth

(it also defines the google_cloudfunctions2_function_iam_member resource but i think it isn't necessary)

EduardJoy commented 10 months ago

Sadly, as previously mention this did not work for me. I will try to include both of them to see if there is any change but I think this behaviour should be covered by the GCP provider.

chr-b commented 10 months ago

Same problem here. Trying to use either

resource "google_cloudfunctions2_function_iam_member" "run_invoker" {
  project = google_cloudfunctions2_function.function.project
  location = google_cloudfunctions2_function.function.location
  cloud_function  = google_cloudfunctions2_function.function.name
  role     = "roles/run.invoker"
  member = "allUsers"
}

or

data "google_iam_policy" "admin" {
  binding {
    role = "roles/run.invoker"
    members = [
      "allUsers"
    ]
  }
}

resource "google_cloudfunctions2_function_iam_policy" "policy" {
  project = google_cloudfunctions2_function.function.project
  location = google_cloudfunctions2_function.function.location
  cloud_function = google_cloudfunctions2_function.function.name
  policy_data = data.google_iam_policy.admin.policy_data
}

But both are resulting in "Error setting IAM policy".

EduardJoy commented 10 months ago

Error is still present in the latest version. Is there any update about possible progress in this issue? I believe having this functionality is critical to being able to deploy and manage cloud functions with the help of terraform. Kindest regards

nikhil-g777 commented 9 months ago

There is a way to solve this, when deploying cloud functions gen2 with terraform, the cloud run service is auto-generated with the same name as the cloud function as long the name is in kebab-case.

Have a look at the comment in the code sample here which mentions "name should use kebab-case so generated Cloud Run service name will be the same"

So you can just define a cloud run IAM binding with the same name:

resource "google_cloudfunctions2_function" "test_function" {
  name        = "gcf-function" # name should use kebab-case so generated Cloud Run service name will be the same
  location    = "us-central1"
  description = "a new function"

  build_config {
    runtime     = "nodejs16"
    entry_point = "helloHttp"  # Set the entry point
    source {
      storage_source {
        bucket = google_storage_bucket.bucket.name
        object = google_storage_bucket_object.object.name
      }
    }
  }

  service_config {
    min_instance_count    = 1
    available_memory      = "256M"
    timeout_seconds       = 60
    service_account_email = google_service_account.account.email
  }
}

# IAM Binding for the generated cloud run service
resource "google_cloud_run_service_iam_binding" "binding" {
  location = google_cloudfunctions2_function.test_function.location
  project = google_cloudfunctions2_function.test_function.project
  service = google_cloudfunctions2_function.test_function.name
  role = "roles/run.invoker"
  members = [
    "allUsers",
  ]
}

Worked for me!

EduardJoy commented 9 months ago

@nikhil-g777 I will try your method tomorrow and tell you the result. However I have tried this previously but it still did not work as expected. However the "google_cloudfunctions2_function_iam_policy" resources should have done exactly this. And as such I still consider it a bug.

haizaar commented 9 months ago

Thank you @nikhil-g777! Making sure I follow kebab-case is what fixed it for me. I keep my names snakecase typically, so I just `replace(..., "", "-")inside thegoogle_cloud_run_service_iam_binding` block.

chr-b commented 9 months ago

I can confirm as well: when strictly using kebab case for the Cloud Function name, the google_cloud_run_service_iam_binding will work.

nikhil-g777 commented 9 months ago

@EduardJoy yes this is a workaround but it is mentioned in the example in the docs that the cloud run service name will be the same as long as its in kebab-case. The ideal solution would be to provide the name of the auto-created cloud run service as an attribute (output) for the google_cloudfunctions2_function resource.

Then cloud run service name can be used to configure IAM policy bindings, etc.

EduardJoy commented 5 months ago

Been three months and this issue sadly still persists.

EduardJoy commented 5 months ago

I have managed to make it work by emploting

# IAM Binding for the generated cloud run service
resource "google_cloud_run_service_iam_binding" "public_binding" {
  project  = google_cloudfunctions2_function.link_analytics_log_function.project
  location = google_cloudfunctions2_function.link_analytics_log_function.location
  service  = google_cloudfunctions2_function.link_analytics_log_function.name
  role     = "roles/run.invoker"
  members = [
    "allUsers",
  ]

  depends_on = [ google_cloudfunctions2_function.link_analytics_log_function ]
}

However I do feel like this is a pretty big caveat?

EduardJoy commented 5 months ago

An issue which I discovered is that for update of the cloud functio, you will need to redo the service_iam_binding because the cloud run service gets recreated (and implicitly all IAM bindings are deleted).

to fix this you can do

# IAM Binding for the generated cloud run service
resource "google_cloud_run_service_iam_binding" "public_binding" {
  project  = google_cloudfunctions2_function.link_analytics_log_function.project
  location = google_cloudfunctions2_function.link_analytics_log_function.location
  service  = google_cloudfunctions2_function.link_analytics_log_function.name
  role     = "roles/run.invoker"
  members = [
    "allUsers",
  ]

  depends_on = [ google_cloudfunctions2_function.link_analytics_log_function ]

   lifecycle {
    replace_triggered_by = [ google_cloudfunctions2_function.link_analytics_log_function ]
  }
}

Lifecycle should handle it.

drussov commented 2 months ago

I have managed to get it work without any manipulations with function name - the "google_cloudfunctions2_function" resource outputs the service name after creation. So the policy looks like this:

resource "google_cloud_run_service_iam_member" "member" {
  location = google_cloudfunctions2_function.default.location
  service  = google_cloudfunctions2_function.default.service_config[0].service
  project  = var.project_id

  for_each = toset(setunion(var.admins, var.public ? ["allUsers"] : []))
  role     = "roles/run.invoker"
  member   = each.key
}