mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-22 11:17:35 -05:00
Co-authored-by: James Humphries <James@james-humphries.co.uk> Co-authored-by: Ilia Gogotchuri <ilia.gogotchuri0@gmail.com> Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
240 lines
9.5 KiB
Plaintext
240 lines
9.5 KiB
Plaintext
---
|
|
description: >-
|
|
An overview on the ephemeral concept and general usability
|
|
---
|
|
|
|
# Ephemerality
|
|
|
|
Ephemerality refers to the nature of a value to be "ephemeral", meaning that it will not be stored in the state or the plan. With this concept, transient and/or confidental information can be safely handled within OpenTofu.
|
|
|
|
Related concepts:
|
|
* [Sensitive](../state/sensitive-data/) resource fields, outputs and variables
|
|
* Marking values as sensitive ensures the contents are sanitized from the user interface.
|
|
* Sensitive values will still be stored in plaintext in the state and plan.
|
|
* [State Encryption](../state/encryption/)
|
|
* Encryption of state and plan data can be configured to prevent unauthorized access and tampering.
|
|
* Security of this data is determined by the encryption methods used and access to the encryption keys.
|
|
|
|
|
|
In contrast, Ephemeral values only exist for the duration of single execution of the `tofu` command and are never stored in state or plan data. This enables representation of transient concepts, such as temporary network tunnels
|
|
or confidential keys managed by an external system.
|
|
|
|
It is recommended that all three concepts are considered when working with confidential information. If possible use ephemeral values/resources to prevent storing the data at all. If confidential data must be stored, ensure that it is marked as
|
|
sensitive and that state and plan encryption are enabled and properly configured.
|
|
|
|
|
|
As part of this concept, the following constructs can be used:
|
|
* [Ephemeral Resources](./ephemeral-resources.mdx)
|
|
* [Ephemeral Variables](../values/variables.mdx#ephemerality)
|
|
* [Ephemeral Outputs](../values/outputs.mdx#ephemerality)
|
|
* [Locals](../values/locals.mdx#ephemerality)
|
|
* [Providers](../providers/configuration.mdx)
|
|
* [Provisioners](../resources/provisioners/syntax.mdx#suppressing-provisioner-logs-in-cli-output)
|
|
* [Resource `connection` blocks](../resources/provisioners/connection.mdx#ephemeral-usage)
|
|
* [Resource `write-only` attributes](./write-only-attributes.mdx)
|
|
|
|
## Compatibility
|
|
:::warning
|
|
This concept is inspired by and aims for compatibility with the equivalent "Ephemeral" concept in Terraform v1.12.
|
|
If incompatibilities are discovered, the OpenTofu team will consider accepting breaking changes in subsequent versions
|
|
of OpenTofu to ensure compatibility.
|
|
:::
|
|
|
|
## Additional Topics
|
|
* [Providing values for root module ephemeral variables](../../cli/commands/apply.mdx#ephemeral-variables)
|
|
* [Validations interaction with ephemeral values](../expressions/custom-conditions.mdx#ephemeral-values-usage)
|
|
* [Sanitizing values using `ephemeralasnull()`](../functions/ephemeralasnull.mdx)
|
|
* [Differentiating plan/apply with `tofu.applying`](../expressions/references.mdx#filesystem-and-workspace-info)
|
|
* [How configuration generation handles write-only attributes](../import/generating-configuration.mdx#generated-hints)
|
|
|
|
## Usage example
|
|
:::note
|
|
The following example is meant to showcase how ephemeral concepts listed above work together and is not intended to be used directly in real environments
|
|
:::
|
|
|
|
### A module to handle various usage patterns of secrets
|
|
In the following example you can see a module that can handle secret creation and retrieval by using ephemeral variables, ephemeral resources, write-only attributes, ephemeral outputs and validations using ephemeral values in their `condition`.
|
|
```hcl
|
|
terraform {
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = ">=6.0.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
variable "secret_map" {
|
|
type = map(string)
|
|
default = null # default because in some cases this module can be used only for reading the secret and not storing it
|
|
ephemeral = true # defines an ephemeral variable since it holds the secrets to be processed
|
|
description = "The map of secrets to be used to create the new secret entry. Omit this when using this module to read the secret"
|
|
}
|
|
|
|
variable "secret_version" {
|
|
# needed to update a write-only attribute
|
|
type = number
|
|
default = 0
|
|
description = "The version used to update the secret. You need to bump this from the previous version in order for the secret_map content to be persisted. Omit this when using this module to read the secret"
|
|
}
|
|
|
|
variable "secret_manager_arn" {
|
|
type = string
|
|
default = ""
|
|
description = "The map of secrets to be used to create the new secret entry"
|
|
validation {
|
|
condition = (var.secret_manager_arn == "" && var.secret_map != null) || (var.secret_manager_arn != "" && var.secret_map == null)
|
|
error_message = "var.secret_manager_arn should not be used in the same time with var.secret_map. Use the module only with var.secret_manager_arn to read the secret or use it with var.secret_map and var.secret_version to create a new secret"
|
|
}
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret" "manager" {
|
|
count = var.secret_version > 0 ? 1 : 0 # used when we create a new secret manager
|
|
name = "testin-secret-manager"
|
|
}
|
|
|
|
resource "aws_secretsmanager_secret_version" "secret_creation" {
|
|
count = var.secret_version > 0 ? 1 : 0 # used when we want to create a new secret
|
|
secret_id = aws_secretsmanager_secret.manager[0].arn
|
|
secret_string_wo = jsonencode(var.secret_map) # here we pass in the ephemeral variable into a write-only attribute
|
|
secret_string_wo_version = var.secret_version
|
|
# and when we want to update it, we can provide a different value for var.secret_map and an incremented secret_version
|
|
}
|
|
|
|
ephemeral "aws_secretsmanager_secret_version" "secret_retrieval" {
|
|
# used to read the secret from the secret manager after creating it
|
|
count = var.secret_version > 0 ? 1 : 0
|
|
secret_id = aws_secretsmanager_secret.manager[0].arn
|
|
depends_on = [
|
|
aws_secretsmanager_secret_version.secret_creation # ensure that we want for the secret creation before reading it
|
|
]
|
|
}
|
|
|
|
ephemeral "aws_secretsmanager_secret_version" "secret_retrieval_direct" {
|
|
# used to read the secret from the secret manager when the module is used only for reading without creating a new secret
|
|
count = var.secret_version > 0 ? 0 : 1
|
|
secret_id = var.secret_manager_arn
|
|
}
|
|
|
|
output "secrets" {
|
|
value = "${var.secret_version > 0 ?
|
|
jsondecode(ephemeral.aws_secretsmanager_secret_version.secret_retrieval[0].secret_string) :
|
|
jsondecode(ephemeral.aws_secretsmanager_secret_version.secret_retrieval_direct[0].secret_string)}"
|
|
ephemeral = true # marking an output as ephemeral is mandatory when the value points to an ephemeral value
|
|
}
|
|
|
|
output "secret_manager_arn" {
|
|
value = var.secret_version > 0 ? aws_secretsmanager_secret.manager[0].arn : null
|
|
# available only when the module is used to create a secret
|
|
}
|
|
```
|
|
|
|
### Using the module in a configuration to store a secret
|
|
|
|
The following configuration uses the module above to create a new secret where it stores the given aws credentials and
|
|
outputs the ARN of the created secret manager:
|
|
```hcl
|
|
terraform {
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = "6.0.0-beta1"
|
|
}
|
|
}
|
|
}
|
|
|
|
provider "aws" {
|
|
alias = "secrets-read-write"
|
|
}
|
|
|
|
variable "access_key" {
|
|
type = string
|
|
ephemeral = true
|
|
}
|
|
|
|
variable "secret_key" {
|
|
type = string
|
|
ephemeral = true
|
|
}
|
|
|
|
locals {
|
|
secrets = {
|
|
"access_key" : var.access_key,
|
|
"secret_key" : var.secret_key
|
|
}
|
|
}
|
|
|
|
module "secret_management" {
|
|
providers = {
|
|
aws = aws.secrets-read-write
|
|
}
|
|
source = "../mod"
|
|
secret_map = local.secrets
|
|
secret_version = 1 # first version of the secret. If want to update the secret inside, bump this version
|
|
}
|
|
|
|
output "secret_manager_arn" {
|
|
value = module.secret_management.secret_manager_arn
|
|
}
|
|
```
|
|
|
|
### Using the module in a configuration to retrieve a secret and configure a provider
|
|
|
|
The following configuration uses the module above to read the secret, configure a provider with
|
|
the credentials retrieved and store the same credentials in a write-only attribute `value_wo`
|
|
of the `aws_ssm_parameter` resource.
|
|
Additionally, it adds two `local-exec` provisioners. The execution of the first one will print the `command` content but the
|
|
second one will print `(output suppressed due to ephemeral value in config)`:
|
|
```hcl
|
|
terraform {
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = "6.0.0-beta1"
|
|
}
|
|
}
|
|
}
|
|
provider "aws" {
|
|
alias = "read-secrets"
|
|
}
|
|
|
|
variable "secret_manager_arn" {
|
|
type = string
|
|
}
|
|
|
|
module "secret_management" {
|
|
providers = {
|
|
aws = aws.read-secrets
|
|
}
|
|
source = "../mod"
|
|
secret_manager_arn = var.secret_manager_arn
|
|
}
|
|
|
|
# Provider can be configured with the credentials returned by the ephemeral resource inside the module.
|
|
provider "aws" {
|
|
alias = "dev-access"
|
|
access_key = module.secret_management.secrets["access_key"]
|
|
secret_key = module.secret_management.secrets["secret_key"]
|
|
}
|
|
|
|
resource "aws_ssm_parameter" "store_ephemeral_in_write_only" {
|
|
provider = aws.dev-access
|
|
name = "parameter_from_ephemeral_value"
|
|
type = "SecureString"
|
|
value_wo = jsonencode(module.secret_management.secrets) # Using the secrets again in a write-only attribute
|
|
value_wo_version = 1 # bump this if `value_wo` needs to be updated
|
|
|
|
# Because this provisioner uses only regular attributes, it will print the output of the command
|
|
provisioner "local-exec" {
|
|
when = create
|
|
command = "echo non-ephemeral value: ${aws_ssm_parameter.store_ephemeral_in_write_only.arn}"
|
|
}
|
|
|
|
# Because this provisioner uses ephemeral values, its output will be suppressed
|
|
provisioner "local-exec" {
|
|
when = create
|
|
command = "echo ephemeral value from module: #${jsonencode(module.secret_management.secrets)}#"
|
|
}
|
|
}
|
|
```
|