slide

Ephemeral Values in Terraform

Ned Bellavance
6 min read

Cover

Terraform 1.10 has introduced the concept of ephemeral values for outputs and input variables, and as a new object type. Before I dig into the details of how they work, first I think it’s important to understand the problem ephemeral values are trying to solve.

We all know that Terraform state can contain sensitive information. After all, it contains all the attributes of each resource and data source and any output values defined in the configuration. Terraform uses these values to calculate an execution plan when updates are made to the configuration.

An additional concern is sensitive information found in saved Terraform plan files. Plan files not only contain the entirety of the configuration, but also the planned changes and variable values used to create the plan.

How can you remove sensitive information from state data and saved plans? That is what ephemeral values are meant to do. You can mark input variables and output values as ephemeral, and there is a brand new object type: ephemeral resources.

Unlike data sources or managed resources, an ephemeral resource and its attributes are never written to state or an execution plan. Additionally, ephemeral resources and their properties cannot be used in the context of a non-ephemeral object. If they could, then the values of the ephemeral resource would end up in state or a plan, and would defeat the purpose behind them.

Input variables and outputs marked as ephemeral have similar restrictions. You can only use an ephemeral value for an argument that will not be recorded in state.

Given that you can’t use an ephemeral value inside of a typical managed resource or data source, what good are they? Well there are a few use cases:

  • Inside a provider block
  • Inside a provisioner block
  • With write-only arguments (new in Terraform 1.11!)

We will explore write-only arguments in their own blog post, so for now let’s focus on the first two use cases.

Ephemeral Values Syntax

Input variables and output values can be marked as ephemeral simply by adding the ephemeral argument and setting it to true:

variable "identity_token" {
    type = string
    description = "Token used for Azure authentication."
    sensitive = true
    ephemeral = true
}

The syntax for an ephemeral resource is basically the same as a managed resource:

ephemeral "<ephemeral_resource_type>" "<name_label>" {
    <identifier> = <expression>
}

Ephemeral resources are a new object type in Terraform 1.10, and they appear as a separate category in the provider documentation. Often they mirror existing data source types, but the implementation is different. Currently, the AzureRM, AWS,GCP, and Vault providers have all added support for ephemeral resources, with more coming in the future.

The current version (4.35.0) of the azurerm provider supports the following ephemeral resource types:

  • azurerm_key_vault_secret
  • azurerm_key_vault_certificate

If you have a secret stored in Key Vault that you want to use, the syntax for the ephemeral resource would be:

ephemeral "azurerm_key_vault_secret" "example" {
  name         = var.key_vault_secret_name
  key_vault_id = var.key_vault_id
}

Now what can you do with this secret? Can you use it in a regular resource?:

resource "azurerm_container_group" "example" {
  name                = "ephemeral-continst"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  #...

  container {
    name   = "hello-world"
    image  = "mcr.microsoft.com/azuredocs/aci-helloworld:latest"
    cpu    = "0.5"
    memory = "1.5"

    secure_environment_variables = {
      KEY_VAULT_SECRET = ephemeral.azurerm_key_vault_secret.example.value
    }

  }
}

Nope! When you run terraform validate you’ll get the error:

Error: Invalid use of ephemeral value
│   with azurerm_container_group.example,
│   on main.tf line 36, in resource "azurerm_container_group" "example":
│   36:       KEY_VAULT_SECRET = ephemeral.azurerm_key_vault_secret.example.value
│ Ephemeral values are not valid in resource arguments, because resource instances must persist between Terraform phases.

To generate a proper execution plan, Terraform would have to persist the secret value beyond the planning phase. Since we’re trying to avoid that, Terraform throws an error.

So if we can’t use ephemeral values with regular resource or data source arguments, where can we use them? You can use them with the follow object types:

  • Local values: The local value will inherit the ephemeral marker.
  • Provider blocks: You can use ephemeral values to configure the properties of a provider.
  • Ephemeral resources: You can reference the attribute of one ephemeral resource in another since neither persists.
  • Ephemeral variables: You can pass an ephemeral value to a child module if the input variable is marked as ephemeral.
  • Ephemeral outputs: You can pass an ephemeral value to a parent module if the output is marked as ephemeral.
  • Provisioner and connection blocks: You can use an ephemeral value to configure a provisioner.
  • Write-only arguments: You can use ephemeral values with resource and data source arguments that have been marked write-only.

Note that ephemeral outputs cannot be used in the root module, because then they would be written to state.

Also, since ephemeral values are not written to the plan file, they are calculated on each Terraform run. So you might get a different value for plan than you do for apply. That’s a pretty important thing to note. If your ephemeral value is non-deterministic, the value may be different between the plan and apply.

Using with Providers

Honestly, the biggest use case with the release of Terraform 1.10 was provider configuration. Say for instance, you are using the Kubernetes provider to deploy a manifest and you have the Kubernetes credentials stored in Azure Key Vault secret. You could grab the secret with an ephemeral resource and pass it to the Kubernetes provider block:

ephemeral "azurerm_key_vault_secret" "k8s" {
  for_each = toset(["client-certificate", "client-key", "cluster-ca-certificate"])

  name         = each.key
  key_vault_id = var.key_vault_id
}

provider "kubernetes" {
  host = "https://${var.aks_cluster_host}"

  client_certificate     = base64decode(ephemeral.azurerm_key_vault_secret.k8s["client-certificate"].value)
  client_key             = base64decode(ephemeral.azurerm_key_vault_secret.k8s["client-key"].value)
  cluster_ca_certificate = base64decode(ephemeral.azurerm_key_vault_secret.k8s["cluster-ca-certificate"].value)
}

Because the provider configuration is never stored in state or the plan, this is a valid use of an ephemeral resource. You can also use meta-arguments with an ephemeral resource, as shown above with the for_each argument. Ephemeral resources also support:

  • depends_on
  • count
  • for_each
  • provider
  • lifecycle

The other potential use case is for provisioners, but here I would remind you that provisioners are considered an anti-pattern and not recommended. If you must though, you could run a remote-exec every time a member is added to a cluster like this:

ephemeral "azurerm_key_vault_secret" "example" {
  name         = var.key_vault_secret_name
  key_vault_id = var.key_vault_id
}

resource "terraform_data" "cluster" {
    triggers_replace = azurerm_linux_virtual_machine.cluster[*].id

    connection {
        type        = "ssh"
        user        = "clusteradmin"
        private_key = ephemeral.azurerm_key_vault_secret.example.value
        host        = azurerm_linux_virtual_machine.cluster[0].private_ip_address

    }
    provisioner "remote-exec" {
        inline = [#...]
    }
}

Provisioners also do not write their connection information to state or plan.

The introduction of ephemeral values in Terraform 1.10 is a huge step towards getting secrets out of state and plan, but there is one key thing missing that closes the loop with resources and data sources. Write-only arguments. I’ll address those in my next post.