Terraform Secrets Management: Keep Passwords Out of Code and State
Secrets and the Terraform state #
The main issue with secrets in Terraform is that anything a resource needs ends up in the state file in plain text: database passwords, generated keys, certificate private keys. Hiding the value in the console output does not remove it from the state. A good strategy has four parts.
1. Never put secrets in the code or in Git #
Do not write passwords in .tf or .tfvars files that are committed. Scanners such as Trivy and Checkov detect many of them. Add *.tfvars with secrets and .terraform/ to .gitignore.
2. Mark variables and outputs as sensitive #
variable "db_password" {
type = string
sensitive = true
}
output "db_connection" {
value = "postgres://admin:${var.db_password}@${aws_db_instance.main.endpoint}/app"
sensitive = true
}This only redacts the value in the plan and output. Provide it at runtime: TF_VAR_db_password from the CI/CD secret store, never from a file in the repository.
3. Let AWS generate and store the secret #
For RDS, the best option is to ask AWS to manage the master password in Secrets Manager: Terraform never sees it.
resource "aws_db_instance" "main" {
identifier = "ditwl-pro-db"
engine = "postgres"
instance_class = "db.t4g.micro"
allocated_storage = 20
username = "app"
manage_master_user_password = true
storage_encrypted = true
}The secret ARN is in aws_db_instance.main.master_user_secret[0].secret_arn, and the application reads it at runtime with its IAM role.
When you must create a secret yourself, create the container with Terraform and set the value outside of it:
resource "aws_secretsmanager_secret" "api_key" {
name = "pro/app/api-key"
kms_key_id = aws_kms_key.secrets.arn
}Then run aws secretsmanager put-secret-value from a secure process. That way, the value never enters the state.
4. Ephemeral values and write-only arguments #
Recent versions of Terraform (1.10 and later) and OpenTofu add ephemeral resources and write-only arguments: values that are used during the run but are never saved in the plan or state. This is the direction to follow for generated passwords and data read from secret stores. Support depends on the provider resource, so check the documentation for your versions before relying on it.
5. Protect the state itself #
Whatever you do, assume the state contains sensitive data:
- Use a remote backend with encryption at rest (S3 with KMS), versioning and strict IAM access.
- In OpenTofu, enable state encryption, which encrypts the state and the plan before they are written.
- Restrict who can run
terraform state pullor read the bucket. Read access to the state is read access to every secret in it. - Avoid terraform_remote_state to share values: it requires reading the whole state.
Avoid long-lived credentials for Terraform itself #
Do not store AWS access keys in the pipeline. Use short-lived credentials from OIDC or an instance role.
Checklist #
- [ ] No secrets in Git
- [ ] Sensitive variables and outputs marked
- [ ] Database passwords managed by AWS (
manage_master_user_password) or Secrets Manager - [ ] State in an encrypted, private, versioned backend
- [ ] State encryption enabled in OpenTofu when possible
- [ ] Short-lived credentials in CI/CD