Terraform : Is there a better way to impersonate service accounts? Is it possible to use and output a provider in modules?

Viewed 39

The current way I am able to impersonate service accounts via terraform is by using lengthy declarations like these with multiple provider blocks. Not to mention I have to copy/paste this every time for each user/s.a/project...

provider "google" {
  alias  = "network_admin_impersonation"
  scopes = var.impersonation_info.of_network_admin.tier_1_scopes
}
data "google_service_account_access_token" "network-admin" {
  provider               = google.network_admin_impersonation
  target_service_account = google_service_account.network-admin.email
  scopes                 = var.impersonation_info.of_network_admin.tier_2_scopes
  lifetime               = "1200s"
}

provider "google" {
  alias        = "as_network_admin"
  access_token = data.google_service_account_access_token.network-admin.access_token
  region       = var.region
  zone         = var.zone
}

And to grant users the right to use this service account :

resource "google_service_account_iam_member" "network-admin-impersonators" {
  for_each = toset([
    for account in var.user_accs_impersonators_info.as_network_admin :
    "${account.acc_type}:${account.acc_details.email}"
  ])

  service_account_id = google_service_account.network-admin.name
  role               = "roles/iam.serviceAccountTokenCreator"
  member             = each.value
}

There must be a better way to do this that I am not seeing. Maybe via a module? But I read somewhere that it is generally a bad idea to use provider within modules... I would appreciate some guidance on this.

1 Answers

You need to look at the provider initialization syntax, which allows you to embed a provider's configuration block within another provider's configuration block. So your configuration could look like this instead:

provider "google" {
    alias = "network_admin_impersonation"

    scopes = var.impersonation_info.of_network_admin.tier_1_scopes

    provider {
    access_token = data.google_service_account_access_token.network-admin.access_token
    region       = var.region
    zone         = var.zone
    }
}

Note that the provider block is nested within the provider block, and that it does not have an alias attribute.

edit:

I don't think you can. What you can do is create a provider configuration block with the token and pass it to the child provider:

provider "google" {
alias = "network_admin_impersonation"

scopes = var.impersonation_info.of_network_admin.tier_1_scopes

provider {
    access_token = data.google_service_account_access_token.network-admin.access_token
    region       = var.region
    zone         = var.zone
}

provider "google" {
    alias = "as_network_admin"
}
}

Or you can just declare it all in the google provider:

provider "google" {
alias = "network_admin_impersonation"

scopes = var.impersonation_info.of_network_admin.tier_1_scopes
}

provider "google" {
alias = "as_network_admin"

access_token = data.google_service_account_access_token.network-admin.access_token
region       = var.region
zone         = var.zone
}

edit 2

You could use the count of the service account list to create the multiple provider blocks. Pass the service account name as a variable to the module.

Example module

variable "service_accounts" {
type = list(string)
}

resource "google_service_account" "service_account" {
count = length(var.service_accounts)
name  = var.service_accounts[count.index]
}

data "google_service_account_access_token" "service_account" {
count = length(var.service_accounts)
provider = google.service_account
service_account = element(var.service_accounts, count.index)
scopes = var.impersonation_info.of_network_admin.tier_2_scopes
lifetime = "1200s"
}

provider "google" {
count = length(var.service_accounts)
alias = element(var.service_accounts, count.index)
scopes = var.impersonation_info.of_network_admin.tier_1_scopes

provider {
    access_token = element(data.google_service_account_access_token.service_account.*.access_token, count.index)
    region       = var.region
    zone         = var.zone
}
}

Example usage
module "impersonation" {
source = "./impersonation"
service_accounts = [
    "network_admin_service_account",
    "project_admin_service_account",
]
}

And you can use the following resources to grant permissions to the service accounts.

resource "google_iam_member" "service_account" {
count = length(var.service_accounts)
service_account_id = element(var.service_accounts, count.index)
role               = "roles/iam.serviceAccountTokenCreator"
member             = var.member
}
Related