Using Service Account Impersonation With Terraform

Posted on Oct 10, 2021
tl;dr: Setup two service accounts, a high privilege, and a low privilege one. Allow low privilege to impersonate the high privilege as the only role. Create a token using a Terraform data source, pass the token to the main provider while you run Terraform using the low privilege account.

Introduction

Account impersonation is an often overlooked or even unknown capability of Terraform that adds a layer of protection and allows for better monitoring and restrictions for the high privilege account that you usually use to deploy infrastructure with Terraform. When you use account impersonation, you only need to expose limited privilege credentials and you can monitor when impersonation is used via your cloud logs. In this article, I would like to give you a quick overview of the capability and one way of using it.

Setup

The following section focuses on the Google Cloud Platform. The idea is to use two accounts, low and high privilege.

  • low privilege account that can impersonate the high privilege account
  • high privilege that has the permissions to deploy the infrastructure

The following script creates the two accounts and the necessary permissions for impersonation.

Note

If you have Terraform runners on GCP, use the low privilege account to run the compute engine resources this way you do not have to pass any additional credentials to Terraform. Make sure that is the low privilege account can write to the GCS bucket if that is you remote backend of choice.

setup-account-chain.sh

#!/bin/bash

PROJECT_ID=$(gcloud config get-value project)

## Create terraform runner account
SERVICE_ACCOUNT_NAME_HIGH_PRIV="terraform-runner"

SERVICE_ACCOUNT_ID_HIGH_PRIV="$SERVICE_ACCOUNT_NAME_HIGH_PRIV@$PROJECT_ID.iam.gserviceaccount.com"

function create_service_account () {
  gcloud iam service-accounts create $1 \
    --description="Account used by Terraform to deploy IaC" \
    --display-name="$1"
}

echo "creating $SERVICE_ACCOUNT_ID_HIGH_PRIV service account"
create_service_account $SERVICE_ACCOUNT_NAME_HIGH_PRIV

## role/editor for testing, this can be adjusted an necessary
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:$SERVICE_ACCOUNT_ID_HIGH_PRIV --role roles/editor

# Enable IAM credentials API for impersonation
gcloud services enable iamcredentials.googleapis.com

# GCE runner account binding permission to allow impersonation of the terraform-runner account
SERVICE_ACCOUNT_NAME_LOW_PRIV="terraform-doorman"

SERVICE_ACCOUNT_ID_LOW_PRIV="$SERVICE_ACCOUNT_NAME_LOW_PRIV@$PROJECT_ID.iam.gserviceaccount.com"

echo "creating $SERVICE_ACCOUNT_ID_LOW_PRIV service account"
create_service_account $SERVICE_ACCOUNT_NAME_LOW_PRIV

echo "Allow $SERVICE_ACCOUNT_ID_LOW_PRIV to generate a token for $SERVICE_ACCOUNT_NAME_HIGH_PRIV account"
gcloud iam service-accounts add-iam-policy-binding \
    $SERVICE_ACCOUNT_ID_HIGH_PRIV \
    --member=serviceAccount:$SERVICE_ACCOUNT_ID_LOW_PRIV \
    --role=roles/iam.serviceAccountTokenCreator

Next is the Terraform file that handles the credential generation. Using two providers and data sources and passing the

For external runners, generate credentials for the terraform-doorman (low privilege) account and pass them to Terraform while having the following configuration for your provider.

provider.tf

provider "google" {
  alias = "tokengen"
}
# get config of the client that runs
data "google_client_config" "default" {
  provider = google.tokengen

}
data "google_service_account_access_token" "sa" {
  provider               = google.tokengen
  target_service_account = "[email protected]${var.project}.iam.gserviceaccount.com"
  lifetime               = "600s"
  scopes = [
    "https://www.googleapis.com/auth/cloud-platform",
  ]
}

/******************************************
  GA Provider configuration
 *****************************************/
provider "google" {
  access_token = data.google_service_account_access_token.sa.access_token
  project      = var.project
}

Bonus

For AWS and using AssumeRole see https://support.hashicorp.com/hc/en-us/articles/360041289933-Using-AWS-AssumeRole-with-the-AWS-Terraform-Provider