DEV Community

drewmullen
drewmullen

Posted on

Adopting Terraform Ephemeral Resources

In version 1.11, HashiCorp introduced Terraform Ephemeral resources and write-only attributes to allow for root configs that do not store secrets in the Terraform statefile. But many users ask about how they can adopt ephemerals. This blog attempts to lay out the ways secrets can be stored in state and how you should update your configurations to remove those secrets.

Note: For a primer on ephemerals (see this blog post).

Scenarios to consider:

  1. Data sources that fetch a static secret
  2. Resources that receive a secret
  3. Resources that generate a dynamic secret
  4. Resources that fetch generated secrets to store in another 3rd party system

Scenario 1: Data sources with static secrets

Ephemeral resources can often be a drop-in replacement for data sources pulling static values:

data "vault_kv_secret_v2" "static_kv" {
  mount = "kvv2"
  name  = "my_password"
}
Enter fullscreen mode Exit fullscreen mode
ephemeral "vault_kv_secret_v2" "static_kv" {
  mount = "kvv2"
  name = "my_password"
}
Enter fullscreen mode Exit fullscreen mode

However, using these values has 1 specific difference. The attributes on a ephemeral resource are considered ephemeral and can only be used as ephemeral arguments. That means 2 places:

Provider blocks

Provider blocks are considered ephemeral, so ephemeral resources may populate arguments:

provider "example" {
  password = tostring(ephemeral.vault_kv_secret_v2.static_kv.data.password)
}
Enter fullscreen mode Exit fullscreen mode

Write-only arguments

Write-only arguments are special arguments that require the ephemeral taint for values:

resource "aws_db_instance" "example" {
  ...
  password_wo = tostring(ephemeral.vault_kv_secret_v2.static_kv.data.password)
}
Enter fullscreen mode Exit fullscreen mode

If the resource you wish to pass a value to does not have an available ephemeral, open an issue with that provider. You can reference:

Scenario 2: Resources that receive a static secret

Without duplicating to the section above, write-only arguments are a way to get secrets out of state. Above has guidance if the secret value comes from a data source, but what if its from a variable? In that case, we just need to add the ephemeral taint to the variable declaration:

variable "my_password" {
  type       = string
  ephemeral  = true
  senstitive = true
}

resource "aws_db_instance" "example" {
  ...
  password_wo = var.my_password
}
Enter fullscreen mode Exit fullscreen mode

Scenario 3: Resources that generate a dynamic secret

Now is when things get slightly trickier. There are some resources that generate dynamic secret content:

# generates a random password
resource "random_password" "pw" {
  length           = 16
  special          = true
  override_special = "!#$%&*()-_=+[]{}<>:?"
}
Enter fullscreen mode Exit fullscreen mode

These can also be replaced with an ephemeral resource but theres 1 difference. Ephemeral resources have no state and run every plan & apply. This means that every time plan or apply is run, the random_password will be regenerated.

To account for this, users should use the wo_version arguments:

variable "password_keeper" {
  type        = number
  description = "Bump this number to update the password associated with `ephemeral.random_password.pw`."
  default     = 1
}

ephemeral "random_password" "pw" {
  length           = 16
  special          = true
  override_special = "!#$%&*()-_=+[]{}<>:?"
}

resource "aws_db_instance" "example" {
  ...
  password_wo         = ephemeral.random_password.pw.result
  password_wo_version = var.password_keeper
}
Enter fullscreen mode Exit fullscreen mode

If you would like to know about how to implement this pattern in modules, see this blog post.

Scenario 4: Resources or data sources that fetch generated secrets to store in another 3rd party system

There are many situations when a user would like to use Terraform to generate and hand off a secret between 2 3rd party systems. A typical workflow is asking Terraform to generate a API key that will be given to another 3rd party system to give it access. This is currently a feature gap for ephemerals.

Take the example of giving a HCP Terraform API token to give to another tool (GitHub Pipeline, auditing tool, etc):

resource "tfe_team_token" "test" {
  team_id     = tfe_team.test.id
  description = "my team token"
}
Enter fullscreen mode Exit fullscreen mode

Although you may think an ephemeral resource for this could fix the issue, it sneakily does not. Heres why:

ephemeral "tfe_team_token" "example" {
  team_id = tfe_team.example.id
}
Enter fullscreen mode Exit fullscreen mode

The above code would execute every plan & apply and delete the token at the end of the run. This means that for normal terraform run workflow, 2 team tokens would be generated and deleted. Even if you gave the token to a 3rd party system (AWS secrets manager), the value would be stored but the key deleted.

As stated above, these ephemerals can be used to authenticate providers. This is commonly done with the value dynamic credentials backends:

ephemeral "vault_terraform_token" "tf_token" {
  role_name = "tfe_admin"
}

provider "tfe" {
  token = ephemeral.vault_terraform_token.tf_token.token
}
Enter fullscreen mode Exit fullscreen mode

Closing

This blog walked you through the scenarios and solutions you will encounter when trying to remove secrets from your Terraform statefile.

If you would like a way to detect when your workspaces are using resources that store secrets in state, check out this Sentinel Ephemerality Policy.

Top comments (0)