Authentication with Terraform

When following tutorials out there on Terraform, a consistent approach seems to be passing in authentication secrets through variables:

provider "azurerm" {
  features {}

  subscription_id = var.subscription_id
  client_id       = var.client_id
  client_secret   = var.client_secret
  tenant_id       = var.tenant_id
}

This is a very low friction way to handle authentication when first beginning with Terraform because the overall environment is not front of mind. This means we must also write additional lines of code to declare the variables:

variable "subscription_id" {
  type = string
}

variable "client_id" {
  type = string
}

variable "client_secret" {
  type      = string
  sensitive = true
}

variable "tenant_id" {
  type = string
}

Maintaining secrets is a challenge that we want to avoid. The less code that we have, the better.

Preferred Solution

Federating identity is the preferred solution. This is enabled by using OpenID Connect (OIDC) tokens. A working platform for this is GitHub Actions.

Benefits:

  • No credentials to rotate
  • No Terraform code required
  • No need to store credentials
  • Supported in major platforms (AWS, Azure, and GCP)

Backup Solution

As a fallback for local execution and otherwise, using environment variables is an option for most providers. For instance, considering our previous Azure examples, the following could establish environment variables in Bash:

export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_SECRET="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"

This automatically is picked up by Terraform and eliminates the need to handle this via code. In addition, it accomodates other changes that would beed to be handled in code that may be unanticipated, such as supporting a special use case, like a sovereign or government tenant:

export ARM_ENVIRONMENT="german"

Without relying on such a mechanism, a variable would need to be passed with a default that is already handled otherwise:

provider "azurerm" {
  features {}

  subscription_id = var.subscription_id
  client_id       = var.client_id
  client_secret   = var.client_secret
  tenant_id       = var.tenant_id
  environment     = var.environment
}

...

variable "environment" {
  type    = string
  default = "public"
}

Supports Testing

As we continue to see maturity in the testing space with Terraform, it will be more commonplace that we write tests and achieve higher levels of code coverage. Applying Test Driven Development (TDD) to our code will suggest that we should write code that is testable by nature. Removing the need for code gives us fewer lines of code to test while improving code coverage without writing more tests.

Final Thoughts

Review the documentation for the providers used to understand the nuances around authentication:

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s