Terraform 39: Key Vault + secrets done right
easy⏱ 5 mincourseterraform
Look up the vault, don't create it here
A Key Vault is usually a shared, long-lived resource owned by a platform team. Your service reads it with a data source (by name + resource group) so you get its id without managing its lifecycle. Then you write secrets into it.
data "azurerm_key_vault" "app" {
name = "orders-kv-dev"
resource_group_name = "myorg-orders-dev-rg"
}
# data.azurerm_key_vault.app.id is now usable everywhere below
One secret per map entry with for_each
Drive secret creation from a local map so adding a secret is a one-line change, not a copy-pasted block. Each each.key becomes the secret name and each.value its source — ideally another resource's output, not a literal.
locals {
secrets = {
"orders-db-connection" = azurerm_postgresql_flexible_server.orders.connection_string
"orders-storage-endpoint" = azurerm_storage_account.orders.primary_blob_endpoint
}
}
resource "azurerm_key_vault_secret" "app" {
for_each = local.secrets
name = each.key
value = each.value
key_vault_id = data.azurerm_key_vault.app.id
expiration_date = "2099-12-30T00:00:00Z"
}
Never put secret values in state or vars
Do not hardcode a secret, pass it as a variable default, or commit it in terraform.tfvars. Source values from other resources' attributes (connection strings, keys) or set them out-of-band. Also treat the state file as sensitive: store it in an encrypted remote backend with locking and restricted access — never commit terraform.tfstate.
# ANTI-PATTERN — never do this
variable "db_password" {
default = "S3cr3t!" # committed in VCS, printed in plans, saved in state
}
# DO THIS — value comes from a resource attribute, not a literal
# value = azurerm_postgresql_flexible_server.orders.administrator_password
Set an expiration and lean on soft-delete
Many compliance controls require expiration_date on every secret. Key Vault soft-delete + purge protection also mean a deleted secret can be recovered — configure the key_vault provider feature block accordingly rather than force-purging on destroy.
List the secrets for_each would create
Given secretSources (a map of secret-name → source description), write secretNames(sources) that returns the sorted array of keys — exactly the names azurerm_key_vault_secret would produce. Log secretNames(secretSources).join(", "). It must NOT log any source value (those are the sensitive part).