Terraform
Use write-only arguments
Write-only arguments let you securely pass temporary values to Terraform's managed resources during an operation without persisting those values to state or plan files. Use write-only arguments to handle sensitive data such as passwords, API tokens, and other secrets.
Background
Write-only arguments complement other ephemeral values in Terraform, letting you securely pass sensitive data throughout your configuration without ever storing it in Terraform's artifacts. For example, you can generate a random password using an ephemeral
resource then pass it to a write-only argument on another resource
block. The provider uses the write-only argument value to configure the resource, then Terraform discards the value without storing it.
Hands-on: Declare a write-only argument in the Upgrade RDS major version tutorial.
Unlike other ephemeral constructs in Terraform, such as ephemeral resources or variables, write-only arguments accept both ephemeral and non-ephemeral values.
Requirements
To use write-only arguments, you must use Terraform v.1.11 or later and use a resource that supports write-only arguments.
Declare a write-only argument
Providers indicate in the Terraform registry whether an argument is write-only. For example, the aws
provider's aws_db_instance
resource has a write-only password_wo
argument. The password_wo
argument accepts a value to use as the database password:
resource "aws_db_instance" "test" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
password_wo = <ephemeral or non-ephemeral value>
password_wo_version = 1
}
Write-only arguments accept both ephemeral and non-ephemeral values. For example, you could also use a string as the value of a write-only argument:
resource "aws_db_instance" "test" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
password_wo = "my-password-here"
password_wo_version = 1
}
However, we recommend using write-only arguments for passing ephemeral values to resources. For example, you can use an ephemeral
resource to generate a random password and pass it to the password_wo
write-only argument:
ephemeral "random_password" "db_password" {
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_db_instance" "example" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
password_wo = ephemeral.random_password.db_password.result
password_wo_version = 1
}
During a Terraform operation, the provider uses the password_wo
value to create the database instance, and then Terraform discards that value without storing it in the plan or state file.
Note that Terraform does not store the generated value for password_wo
, but you can capture it in another resource or output. For an example of generating, storing, retrieving, and using an ephemeral password as a write-only argument, refer to the Examples.
Update write-only arguments with versions
Terraform does not store write-only arguments in state files, so Terraform has no way of knowing if a write-only argument value has changed. Because Terraform cannot track write-only argument values, it sends write-only arguments to the provider during every operation.
Terraform also cannot create plan diffs for write-only arguments because it does not store those values in plan files. However, providers typically include version arguments alongside write-only arguments. Terraform stores version arguments in state, and can track if a version argument changes.
Providers implement version arguments to let practitioners track write-only argument values and control when a provider uses those write-only arguments. The implementation of write-only arguments and their version arguments is provider-specific, so consult the Registry for more details about your specific provider.
For example, the aws_db_instance
resource has an accompanying password_wo_version
argument for the password_wo
write-only argument:
resource "aws_db_instance" "test" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
password_wo = "old-password-here"
password_wo_version = 1
}
The provider uses the write-only argument value when creating the aws_db_instance
resource and Terraform stores the password_wo_version
argument value in state.
To trigger an update of a write-only argument, increment the version argument's value in your configuration:
resource "aws_db_instance" "main" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
password_wo = "new-password-here"
password_wo_version = 2
}
When you increment the password_wo_version
argument, Terraform notices that change in its plan and notifies the aws
provider. The aws
provider then uses the new password_wo
value to update the aws_db_instance
resource.
Examples
The following demonstrates how to use write-only arguments with different cloud providers.
Set and store an ephemeral password in AWS Secrets Manager
You can use an ephemeral
resource to generate a random password, store it in AWS Secrets Manager, and then retrieve it using another ephemeral
resource. Finally, you can pass the password to the password_wo
write-only argument of the aws_db_instance
resource:
ephemeral "random_password" "db_password" {
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
resource "aws_secretsmanager_secret" "db_password" {
name = "db_password"
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string_wo = ephemeral.random_password.db_password.result
secret_string_wo_version = 1
}
ephemeral "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret_version.db_password.secret_id
}
resource "aws_db_instance" "example" {
instance_class = "db.t3.micro"
allocated_storage = "5"
engine = "postgres"
username = "example"
skip_final_snapshot = true
password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string
password_wo_version = aws_secretsmanager_secret_version.db_password.secret_string_wo_version
}
In the above example, the ephemeral resource aws_secretsmanager_secret_version
references an argument that Terraform initially does not know. Terraform defers executing aws_secretsmanager_secret_version
until the apply stage, to ensure that Terraform evaluates the resource after it has the information it needs.
Terraform first creates the secret in AWS Secrets Manager using the ephemeral random_password
, then retrieve it using the ephemeral aws_secretsmanager_secret_version
resource, and finally write the password to the write-only password_wo
argument of the aws_db_instance
resource.
Set and store an ephemeral password in Azure Key Vault
You can use a write-only argument to store a password in Azure's Key Vault, then use that password to create a MySQL database in Azure. In the following example, Terraform generates an password using an ephemeral
resource, stores that password in a azurerm_key_vault_secret
, then retrieves it in the azurerm_mysql_flexible_server
resource:
provider "azurerm" {
features {}
}
ephemeral "random_password" "db_password" {
length = 16
override_special = "!#$%&*()-_=+[]{}<>:?"
}
locals {
db_password_version = 1
}
resource "azurerm_resource_group" "example" {
name = "example-resource-group"
location = "westeurope"
}
data "azurerm_client_config" "current" {}
resource "azurerm_key_vault" "example" {
name = "example-key-vault"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
soft_delete_retention_days = 7
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
key_permissions = [
"Get",
]
secret_permissions = [
"Get",
"Delete",
"List",
"Purge",
"Recover",
"Set",
]
}
}
resource "azurerm_key_vault_secret" "example" {
name = "example-secret"
value_wo = ephemeral.random_password.db_password.result
value_wo_version = local.db_password_version
key_vault_id = azurerm_key_vault.example.id
}
ephemeral "azurerm_key_vault_secret" "db_password" {
name = azurerm_key_vault_secret.example.name
key_vault_id = azurerm_key_vault.example.id
}
resource "azurerm_mysql_flexible_server" "example" {
name = "example-mysql-flexible-server"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
sku_name = "B_Standard_B1s"
administrator_login = "newuser"
administrator_password_wo = ephemeral.azurerm_key_vault_secret.db_password.value
administrator_password_wo_version = local.db_password_version
}
The above configuration stores your password in Azure's Key Vault and uses it to create a database in Azure without ever storing that password in a Terraform artifact.