π This week I’ve been goofing around with Google Cloud Platform and Terraform to manage it. I’ve learned quite a few things, that might be of help to others, so here goes β¦
~
IAM for SA vs. IAM for Projects
There’s a difference between IAM policy for service account (google_service_account_iam
) and IAM policy for projects (google_project_iam_member
).
The former is used to allow other users to impersonate/control a certain Service Account, the second is used to define what a Service Account can access and do.
Should’ve RTFM‘d
~
The Project IAM Admin role
To apply IAM Policies onto a Service Account the invoker needs the “Project IAM Admin” (roles/resourcemanager.projectIamAdmin
) role. If not you’ll get back a 403:
returned error: Error applying IAM policy for project "my-project": Error setting IAM policy for project "my-project":
googleapi: Error 403: The caller does not have permission, forbidden
~
Terraform doesn’t like Casing
There’s a bug in Terraform which prevents terraform apply
from setting IAM Roles when you have a Service Account or User with an uppercase character in their e-mail address (e.g. Firstname.Lastname@domain.tld
).
Terraform will automatically convert all e-mail address to all-lowercase variants (e.g. firstname.lastname@domain.tld
). Therefore GCP’s Resource Manager won’t be able to apply the policies as the Rresource does not exist. Accounts that inherit access to the project are not affected.
The error you get back is quite obscure:
returned error: Error applying IAM policy for project "my-project": Error setting IAM policy for project "my-project":
googleapi: Error 400: Request contains an invalid argument., badRequest
To see whether you have such a conflicting user, check the output of gcloud projects get-iam-policy PROJECT-ID
~
You can partially apply Terraform Changes
You can partially apply a Terraform State by using Resource Targets. You need this, for example, when working with secrets which you can store onto Google Cloud Platform. To work with secrets you rely on a keyring+keycode, but you need that keyring/keycode to be available before you can generate encrypted secrets from plaintext strings.
Here’s how I did it, using Resource Targets:
-
Run Terraform to only target the
google_kms_key_ring
andgoogle_kms_crypto_key
resources:terraform apply -target=google_kms_key_ring.my_key_ring -target=google_kms_crypto_key.my_crypto_key
- Generate your secrets and store them in the keyring using helper scripts such as
gcloud-kms-scripts
which I created just for that. - Use the encrypted secrets in your
.tf
files - Run
terraform apply
as you’d normally do
π‘ If you want to target a module, add a module.
to its name, e.g. terraform apply -target=module.my_module
~
Everyone needs some sleep, even GCP
Applying IAM Policies to a newly created Service Account won’t always work, as it takes some time before the SA is available for use. There’s a workaround in which you trigger a sleep
command using local-exec
, yet I’m hoping that this will be solved in a future release of Terraform (perhaps with a delay
Terraform Resource?).
resource "null_resource" "before" {
}
resource "null_resource" "delay" {
provisioner "local-exec" {
command = "sleep 10"
}
triggers = {
"before" = "${null_resource.before.id}"
}
}
resource "null_resource" "after" {
depends_on = ["null_resource.delay"]
}
~
Service Account Impersonation
It’s possible to impersonate a Service Account from within your Terraform code. First you connect using your main account, and then generate a short lived token for the SA. From the Terraform docs I got this snippet:
provider "google" {
scopes = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email",
]
}
data "google_service_account_access_token" "default" {
provider = "google"
target_service_account = "impersonated-account@projectB.iam.gserviceaccount.com"
scopes = ["userinfo-email", "cloud-platform"]
lifetime = "300s"
}
data "google_client_openid_userinfo" "me" { }
output "source-email" {
value = "${data.google_client_openid_userinfo.me.email}"
}
provider "google" {
alias = "impersonated"
access_token = "${data.google_service_account_access_token.default.access_token}"
}
data "google_project" "project" {
provider = "google.impersonated"
project_id = "target-project"
}
Due to the provider = "google.impersonated"
part, the google_project
will run as impersonated-account@projectB.iam.gserviceaccount.com
~
That’s it! I hope these might have been of help to you, saving you some lookup work β¦
Thank me with a coffee.
I don\'t do this for profit but a small one-time donation would surely put a smile on my face. Thanks!
To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.