Terraform on GoogleCloud - impersonating with short-lived AccessTokens & ServiceAccounts
Tags: terraform, gcloud, serviceaccount, accesstoken
Published: 2020-04-20 22:38:53

# Your Current Setup
1. you have a Google Cloud Platform (GCP) project
2. you have a Terraform script
3. you have the JSON Key of a ServiceAccount in your Terraform script
4. your ServiceAccount has full (owner) access to your GCP - to be able to create & destroy anything & everything in GCP as & when needed.

# The Problem Statement
**Point number 4** above is the problem statement. You have a JSON key outside in the world that has FULL access to do anything with your GCP. If anyhow that JSON key is obtained by someone(despite all sorts of encryption / protection / etc etc), you run the risk of a lot of damage. That's a big risk in security perspective and we can do better than that.

# Enter Impersonation

The idea is simple.

The executor ServiceAccount (for which you have a JSON key that is literally floating out there in the wild jungle called "the internet") will only have super-limited / super-controlled / super-tight access to your GCP. That's all it is allowed to do. Nothing more than that. Kinda secure that way.

That TF executor ServiceAccount will "_impersonate_" another super ServiceAccount - the mighty one who will have all the privileges and permissions to do anything & everything with your GCP as required by Terraform to create/modify/destroy resources.

However, this super-mighty ServiceAccount will not have any JSON key (so nothing about it is floating out there on the internet - kinda secure that way) and it will allow only very specific ServiceAccounts (for example, the executor ServiceAccount in this case) to "_impersonate_" it.

# How does it work in practice?

### Let's assign some names first:

* let's call the ServiceAccount with limited permissions our `tf-executor`
* let's call the super-mighty ServiceAccount our `tf-owner`

### Let's write our Terraform providers

```terraform
provider "google" {
  credentials = file(var.path_to_tf_executor_service_account_json_file)
  alias       = "tf_executor"
}

data "google_service_account_access_token" "impersonated" {
  provider                = google.tf_executor
  target_service_account  = var.tf_owner_service_account_email
  scopes                  = [ "cloud-platform" ]
  lifetime                = "1800s" # 30 minutes - max can be set up to 60 minutes
}

provider "google" {
  access_token = data.google_service_account_access_token.impersonated.access_token
  project      = var.gcp_project_id
  region       = var.region
}

provider "google-beta" {
  access_token = data.google_service_account_access_token.impersonated.access_token
  project      = var.gcp_project_id
  region       = var.region
}

# Terraform v0.12.24
# provider.google v3.13.0
# provider.google-beta v3.13.0
```

### Some things to note in the script above

1. there are 2 google providers and 1 google-beta provider. Ignore the importance of google-beta provider for this discussion. It is here just to show that we can have multiple providers "_impersonating_" the same ServiceAccount

2. there is a google provider with an alias
there is a google provider without alias

3. the aliased google provider uses the `tf-executor` ServiceAccount via its JSON key file

4. the data block uses the aliased google provider to call google APIs to request for a new access token on behalf of `tf-owner` - this new access token will last for 30 minutes - max can be set up to 60 minutes

5. this new access_token from the data block has cloud-platform scope. This means the access token has full access across all of GCP - as long as the IAM roles assigned to the `tf-owner` ServiceAccount allow it - more on this inside the "Roles for tf-owner" section below.

6. this new access_token from the data block is then used by the non-aliased google provider and the non-aliased google-beta provider - thus "_impersonating_" the `tf-owner` ServiceAccount

7. now, we can use these non-aliased providers in our Terraform resources and modules:
  * mentioning `provider = google.tf_executor` inside the resource will refer to the aliased google provider - which really has only very limited permissions by itself
  * mentioning `provider = google` (or by not even mentioning it because it is implied by default) inside the resource will refer to the non-aliased google provider - which now has all the necessary permissions to perform everything

This way, throughout the rest of our Terraform script, our "_impersonated_" google provider (aka our non-aliased google provider) will have all the necessary permissions (on behalf of `tf-owner`) to perform all terraform operations like create/modify/destroy as needed.

# Roles & Permissions

## Roles for `tf-executor`:

* `roles/storage.admin` - to be able to query GCS bucket if that is what you are using to store our TFStates. This is required even before the `tf-executor` gets to "_impersonate_" the `tf-owner`. Hence, we need to provide this bit explicitly. You may further tighten this permission by adding a condition to this role so that it can access only the specific GCS bucket that is dealing with the TFStates

* `roles/iam.serviceAccountTokenCreator` - to be able to perform the work of the data block - requesting access token on behalf of another ServiceAccount

* that's all - nothing more than that

## Roles for `tf-owner`:

* `roles/owner` - to be able to create/modify/destroy anything & everything inside your GCP project.

* or you may consider not giving the owner role at all but instead just the specific admin roles of specific GCloud resources if you want to further tighten the permissions.

# Benefits

This actually helps tighten the access and makes sure that the JSON key file that is out there sitting in the internet cannot do anything much by itself - It also has to know additional things like the `tf-owner` ServiceAccount email address etc to be able to fully exploit its potentials

# Watch Out

This certainly doesn't mean it's now OKAY to pay less attention to the security / encryption / storage of the `tf-executor` ServiceAccount JSON key.

You still gotta do all that. But the risks associated with it being compromised is measurably reduced now.


---

With inspirations from https://medium.com/wescale/how-to-generate-and-use-temporary-credentials-on-google-cloud-platform-b425ef95a00d
This article originally appeared in https://medium.com/@syedrakib/terraform-on-gcp-impersonating-with-limited-access-on-serviceaccount-9dae6e2be11c