Terraform-provider-google: Can't update cloud function that uses a non-default service account

Created on 13 Mar 2020  路  10Comments  路  Source: hashicorp/terraform-provider-google

UPDATE
Actual issue described here:
https://github.com/terraform-providers/terraform-provider-google/issues/5889#issuecomment-611714012


Community Note

  • Please vote on this issue by adding a 馃憤 reaction to the original issue to help the community and maintainers prioritize this request.
  • Please do not leave _+1_ or _me too_ comments, they generate extra noise for issue followers and do not help prioritize the request.
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment.
  • If an issue is assigned to the modular-magician user, it is either in the process of being autogenerated, or is planned to be autogenerated soon. If an issue is assigned to a user, that user is claiming responsibility for the issue. If an issue is assigned to hashibot, a community member has claimed the issue already.

Terraform Version

0.12.13
provider version 3.16

Affected Resource(s)

  • google_cloudfunctions_function

Terraform Configuration Files

resource "google_cloudfunctions_function" "function" {
  project               = "${var.project}"
  name                  = "${var.name}"
  description           = "${var.description}"
  runtime               = "${var.runtime}"
  timeout               = "${var.timeout}"
  available_memory_mb   = "${var.memory}"
  entry_point           = "${var.entry_point}"
  source_archive_bucket = "${var.source_bucket}"
  source_archive_object = "${var.source_object}"
  trigger_http          = "${var.function_trigger_http}"
  event_trigger {
      event_type = "${var.event_trigger.event_type}"
      resource   = "${var.event_trigger.resource}"
  }
  environment_variables = "${var.env}"
  service_account_email = "${var.service_account}"
}

Expected Behavior

When updating an existing terraform managed cloud function, service account email should not be affected (unless that is the argument being updated).

Actual Behavior

When updating any argument of the cloud function, the service account email assigned when the cloud function was created is removed (and the default app engine service account is assigned).

Steps to Reproduce

  1. Create a cloud function using the google_cloudfunctions_function resource, making sure to assign it a custom service_account_email
  2. Update the cloud function (for example, add an environment variable)
  3. Observe that the service account is changed (or attempted to be changed) to the default app engine service account

I believe this is because the service_account_email property is simply omitted from the update function for the cloudfunction_function resource

bug upstream

Most helpful comment

Terraform is sending a correct request to the API, it's getting misinterpreted upstream. I filed a bug against Cloud Functions, I'll update this issue when I get a response. In the meantime, I'm marking it upstream and unassigning myself to mark that there's no action to be taken.

Internal Reference: b/154019086

All 10 comments

@chriswacker I have followed the steps you provided and saw NO change in the field of the custom service account email. Could you please post the full debug logs of the executions before and after you apply the change in the environment field? Thank you

I'm sorry I haven't had time to come up with more detailed steps or the information you asked for. I looked at the code, and although I am very inexperienced with this code base, I spotted what might be the issue (although I am not sure how to fix it):

"service_account_email" shows up in:
https://github.com/terraform-providers/terraform-provider-google/blob/e820eaf01aafd5e3a55315372037b3f424b6732e/google/resource_cloudfunctions_function.go#L295
and
https://github.com/terraform-providers/terraform-provider-google/blob/e820eaf01aafd5e3a55315372037b3f424b6732e/google/resource_cloudfunctions_function.go#L402

but is no where in
https://github.com/terraform-providers/terraform-provider-google/blob/e820eaf01aafd5e3a55315372037b3f424b6732e/google/resource_cloudfunctions_function.go#L459'

@edwardmedia does that seem odd to you?

@chriswacker I am using below HCL to test your issue. But I see the service_account_email remain unchange after I updated value for MY_ENV_VAR. Please provide the your details so I can see your issue. Thanks

resource "google_cloudfunctions_function" "function" {
  name        = "function-test"
  description = "My function"
  runtime     = "nodejs10"
  available_memory_mb   = 128
  source_archive_bucket = "cloud-function-5889"
  source_archive_object = "helloworld.zip"
  trigger_http          = true
  timeout               = 60
  entry_point           = "helloGET"
  labels = {
    my-label = "my-label-value"
  }
  environment_variables = {
    MY_ENV_VAR = "my-env-var-value-2"  # changed from "my-env-var-value"
  }
  service_account_email = "[email protected]"
}

After looking more closely, it turns out the problem only occurs when the service account that terraform is using does not have roles/iam.serviceAccountUser on the default service account: [email protected], even when that shouldn't be required.

Steps to reproduce:

  1. Create service account 'function-agent' to be the service account assigned to the function.
  2. Create service account 'terraform-agent' to be the service account terraform uses.
  3. Give 'terraform-agent' only two roles: roles/cloudfunctions.developer on the project level, and
    roles/iam.serviceAccountUser on 'function-agent' created in step 1 (not on project level).
  4. Create a bucket and object for function source code.
  5. Create a function with this HCL, using terraform plan -out plan && terraform apply plan, providing the tfvars:
provider "google" {
  project     = "${var.project}"
  region      = "${var.region}"
  credentials = "${file(var.terraform_agent_key_file)}" # JSON key for terraform-agent
}

resource "google_cloudfunctions_function" "function" {
  name        = "function-test"
  description = "My function"
  runtime     = "python37"

  available_memory_mb   = 128
  source_archive_bucket = "${var.source_bucket}"
  source_archive_object = "${var.source_object}"
  trigger_http          = true
  entry_point           = "hello"
  environment_variables = {
    TEST = "ENV"
  }
  service_account_email = "function-agent@${var.project}.iam.gserviceaccount.com"
}
  1. Update the TEST environment variable value, and try to update the function with terraform plan -out plan && terraform apply plan.

The output I receive from step 6 is:

Error: Error while updating cloudfunction configuration: googleapi: Error 403: Missing necessary permission iam.serviceAccounts.actAs for  on the service account [email protected]. 
Ensure that service account [email protected] is a member of the project myproject, and then grant  the roles/iam.serviceAccountUser role. 
You can do that by running 'gcloud iam service-accounts add-iam-policy-binding [email protected] --member= --role=roles/iam.serviceAccountUser' 
In case the member is a service account please use the prefix 'serviceAccount:' instead of 'user:'., forbidden

I believe this is erroneous because iam.serviceAccounts.actAs should only be required for 'function-agent' since that is the service account we've assigned to the function. After all, I was able to create it without that permission, I just can't update it.

In our use case, this is an issue because we are practicing least privilege, and do not what to grant roles/iam.serviceAccountUser on the default service account when it is not necessary.

@chriswacker It looks like the account the Terraform uses must have the iam.serviceAccounts.actAs. It is by design. The error you received is from API and there is nothing to do with Terraform provider. Below is what the cloud function doc says https://cloud.google.com/functions/docs/securing/function-identity#permissions_required_to_use_non-default_identities. Please let me know if this makes sense to you.

Permissions required to use non-default identities
In order to deploy a function with a non-default service account, the deployer must have the iam.serviceAccounts.actAs permission on the service account being deployed.

@edwardmedia the documentation you linked states:

In order to deploy a function with a non-default service account, the deployer must have the iam.serviceAccounts.actAs permission on the service account being deployed.

In my case, the service account being deployed is function-agent which terraform-agent _does_ have the iam.serviceAccounts.actAs permission for. This is why I was able to create the function. When trying to update, the error says it needs iam.serviceAccounts.actAs on the default app engine service account, which is not the service account being deployed. I'd like to stress that _I was able to create the function_ and the error only occurs on update. Why would I have permission to create but not update? This is definitely a bug, are you saying it's a bug with Google's API?

@chriswacker I can repro it now. We will take a closer look. Thank you for filing this issue.

Terraform is sending a correct request to the API, it's getting misinterpreted upstream. I filed a bug against Cloud Functions, I'll update this issue when I get a response. In the meantime, I'm marking it upstream and unassigning myself to mark that there's no action to be taken.

Internal Reference: b/154019086

Hi all, I was taking a look at this https://cloud.google.com/functions/docs/reference/rest/v1/projects.locations.functions#CloudFunction It looks like that is expected behavior from the CFs perspective because is documented, but anyway that involves active AppEngine API that a bit tricky, not always is necessary it. Using gcloud command to deploy a CF, as part of the parameters is the SA to use, that works because is a parameter passed in the REST API and there is con confusion there. My question, it is possible to enable a parameter in the terraform CF resource to enforce always the usage of the SA passed to deploy? Avoiding the possible usage of the default account provided by the GCP project

Is there a reasonable workaround? (I don't consider terraform taint before apply a really good one and giving the deployer SA iam.serviceAccount.actAs to the appspot account did not work for me)

Was this page helpful?
0 / 5 - 0 ratings