Elevate Your Terraform Game: How to Use Google Cloud Service Account Impersonation

TJ. Podobnik, @dorkamotorka
5 min readApr 16, 2023

Terraform is a powerful tool for managing resources in Google Cloud using infrastructure-as-code principles. It’s an open-source tool that simplifies the deployment and management of cloud infrastructure resources, and it’s widely used across the industry. When getting started with Terraform and Google Cloud, having the owner role on the project and running Terraform yourself can be an easy way to start. But once you move past the beginner level, it’s recommended to limit your own permissions and run Terraform code as one or more service accounts with just the right set of IAM roles.

Photo by Michell Luo from Unsplash

In Google Cloud, a service account is a special kind of account that is typically used by applications and virtual machines in a project to access APIs and services. Applications and users can authenticate as a service account using generated service account keys. However, creating resources as a service account comes with a security risk as soon as the key is generated and distributed. Any user with access to a service account key, whether authorized or not, will be able to authenticate as the service account and access all the resources for which the service account has permissions. Fortunately, there’s another way to run Terraform code as a service that’s generally safer — service account impersonation.

Creating resources as a service account

To begin creating resources as a service account, you’ll need a service account in your project that you’ll use to run the Terraform code. This service account will need to have the permissions to create the resources referenced in your code. Second, you’ll need to have the Service Account Token Creator IAM role granted to your own user account. This role enables you to impersonate service accounts to access APIs and resources.

Once you have a service account and the Service Account Token Creator role, you can impersonate service accounts in Terraform in two ways:

  • Set an environment variable to the service account’s email or
  • Add an extra provider block in your Terraform code.

The first method is quick and easy, but you’ll have to remember to set that variable each time you restart your terminal session. You’ll also be limited to using just one service account for all of the resources your Terraform code creates. The second method involves adding a few blocks into your Terraform code that will retrieve the service account credentials. This method enables you to use more than one service account by specifying additional provider blocks with unique aliases.

Method 1: Setting GOOGLE_IMPERSONATE_SERVICE_ACCOUNT Environment Variable

To use a service account for your Terraform code, you can set the GOOGLE_IMPERSONATE_SERVICE_ACCOUNT environment variable to the email of the service account you want to use. For example:

export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=YOUR_SERVICE_ACCOUNT@YOUR_PROJECT.iam.gserviceaccount.com

Once you have set this variable, any Terraform code you run in that terminal session will use the credentials of the specified service account instead of your own. This method is quick and easy, but it has some limitations. You will have to remember to set the variable each time you restart your terminal session, and you will be restricted to using only one service account for all the resources your Terraform code creates.

Method 2: Retrieving Service Account Credentials from Terraform Code

For the second method, you can add a few blocks to your Terraform code (preferably in the provider.tf file) to retrieve the service account credentials. First, set a local variable to the email of the service account:

locals {
terraform_service_account = "YOUR_SERVICE_ACCOUNT@YOUR_PROJECT.iam.gserviceaccount.com"
}

You can also set this variable by writing a variable block and setting the value in the terraform.tfvars file. Next, create a provider that will be used to retrieve an access token for the service account. The provider is google but note the impersonation alias that’s assigned to it:

provider "google" {
alias = "impersonation"
scopes = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email",
]
}

Add a data block to retrieve the access token that will be used to authenticate as the service account. Notice that the block references the impersonation provider and the service account specified above:

data "google_service_account_access_token" "default" {
provider = google.impersonation
target_service_account = local.terraform_service_account
scopes = ["userinfo-email", "cloud-platform"]
lifetime = "1200s"
}

Finally, include a second google provider that will use the access token of your service account. With no alias, it’ll be the default provider used for any Google resources in your Terraform code:

provider "google" {
project = YOUR_PROJECT_ID
access_token = data.google_service_account_access_token.default.access_token
request_timeout = "60s"
}

Now, any Google Cloud resources your Terraform code creates will use the service account instead of your own credentials, and you won’t need to set any environment variables. This method also allows you to use more than one service account by specifying additional provider blocks with unique aliases.

Updating Remote State Files with a Service Account

Once you have set up your Terraform code to use a service account, you can take it a step further by using a service account to manage your state file. Terraform uses the state file to keep track of the resources it manages in Google Cloud, and it is recommended to store it in a Google Cloud Storage (GCS) bucket instead of locally. This way, the state file can be shared with your team and accessed from multiple locations, and it can also be versioned and backed up.

To use a service account to manage your state file, you will need to add the impersonate_service_account argument to your backend block in your terraform.tf file. This service account can be different from the one you use to execute your Terraform code.

Here is an example of how to add the impersonate_service_account argument to your backend block:

terraform {
backend "gcs" {
bucket = "GCS_BUCKET_NAME"
prefix = "OPTIONAL_PREFIX"
impersonate_service_account = "YOUR_SERVICE_ACCOUNT@YOUR_PROJECT.iam.gserviceaccount.com"
}
}

With this argument added, the service account specified will be used to read and update your state file in the specified GCS bucket. Your user account won’t need any access to the bucket, only to the service account.

Using impersonation offers several advantages over using service account keys. With impersonation, you don’t need to generate and distribute service account keys, which can introduce security risks. Instead, the access to the service account is centralized to its corresponding IAM policy. By using impersonation, your code becomes portable and can be easily used by anyone on the project with the Service Account Token Creator role, which can be easily granted and revoked by an administrator.

Conclusion

In conclusion, using a service account to impersonate Terraform in Google Cloud is a secure and efficient way to manage your infrastructure. It allows you to easily grant and revoke access to the service account, and it centralizes access to the service account to its corresponding IAM policy. By following the steps outlined above, you can quickly and easily set up your Terraform code to use a service account for authentication and authorization.

Thanks for reading! 😎 If you enjoyed this article, hit that clap button below 👏

Would mean a lot to me and it helps other people see the story. Say Hello on Linkedin | Twitter

Do you want to start reading exclusive stories on Medium? Use this referral link 🔗

If you liked my post you can buy me a Hot dog 🌭

Are you an enthusiastic Engineer, who lacks the ability to compile compelling and inspiring technical content about it? Hire me on Upwork 🛠️

Checkout the rest of my content on Teodor J. Podobnik, @dorkamotorka and follow me for more, cheers!

--

--

TJ. Podobnik, @dorkamotorka
TJ. Podobnik, @dorkamotorka

Responses (1)