Terraform stores sensitive variables directly in your state file, making it a security risk.

Let’s see this in action. Imagine you’re provisioning a database with a root password.

resource "aws_db_instance" "default" {
  allocated_storage    = 20
  engine               = "mysql"
  engine_version       = "5.7.11"
  instance_class       = "db.t2.micro"
  name                 = "mydb"
  username             = "admin"
  password             = var.db_password # This is the sensitive part
  parameter_group_name = "default.mysql5.7"
  skip_final_snapshot  = true
}

variable "db_password" {
  description = "The root password for the database."
  type        = string
  sensitive   = true # This tells Terraform to treat it as sensitive
}

When you run terraform apply, Terraform will prompt you for var.db_password if you haven’t set it. If you provide it, or if it’s set via an environment variable or tfvars file, Terraform will not display it on the console. However, it will be written to the terraform.tfstate file in plain text unless you take specific action.

The sensitive = true flag is your first hint. It tells Terraform that this variable should be treated specially. When you run terraform state show or terraform state pull, you’ll see that the value for db_password is masked with (known after apply) or (sensitive value). This masking is a display feature. It doesn’t encrypt the data in the state file.

The real problem arises when you inspect the state file directly. If you’re using a local backend, your terraform.tfstate file is just a JSON file on disk. If you’re using a remote backend like an S3 bucket, the state file is stored there. Anyone with read access to that state file, whether it’s an attacker who compromised your S3 bucket or a curious colleague who can access your development machine, can see your secrets.

This is where the sensitive attribute on a variable comes into play. It’s a declarative way to mark data that should not be exposed. Terraform uses this flag internally for several things:

  1. Display Masking: As mentioned, it prevents the value from being printed to the console during apply or plan.
  2. State File Masking: It marks the attribute within the state file as sensitive. This is crucial for how Terraform interacts with its state.

The core issue is that Terraform’s state is designed to be a source of truth about your infrastructure, not a secure vault. Therefore, any sensitive data you directly pass into Terraform resources or variables, even if marked sensitive = true, can end up in the state file.

To truly protect secrets, you need to avoid putting them directly into the state file in the first place. The recommended way to handle this is by using a dedicated secrets management system.

Here’s the proper workflow:

  1. Store secrets externally: Use a service like AWS Secrets Manager, HashiCorp Vault, or even environment variables if you have a secure deployment pipeline.
  2. Reference secrets in Terraform: Instead of defining the secret directly in your .tfvars file or passing it interactively, fetch it at runtime.

For example, using AWS Secrets Manager:

data "aws_secretsmanager_secret" "db_password_secret" {
  name = "my-app/database/password"
}

data "aws_secretsmanager_secret_version" "db_password_version" {
  secret_id = data.aws_secretsmanager_secret.db_password_secret.id
}

resource "aws_db_instance" "default" {
  # ... other configurations ...
  password = jsondecode(data.aws_secretsmanager_secret_version.db_password_version.secret_string)["password"]
}

In this scenario, the actual database password is never directly entered into Terraform or stored in the state file. The state file will contain references to the secret, but not the secret itself. The jsondecode(data.aws_secretsmanager_secret_version.db_password_version.secret_string)["password"] part tells Terraform to retrieve the secret string from Secrets Manager, parse it as JSON, and extract the value associated with the "password" key.

The most surprising thing is that even when you mark a variable as sensitive = true, Terraform still writes its presence and its type into the state file. It’s a pointer that says "this value is sensitive," but the value itself is still there if the state file is compromised. The sensitive attribute is primarily for controlling Terraform’s output and how it displays information, not for encrypting the data at rest within the state file itself.

The next logical step after securing your secrets is to manage your Terraform state securely. This involves using remote backends with proper access controls and encryption at rest.

Want structured learning?

Take the full Terraform course →