TerraformXI · State Security and LifecycleState security
State Security and Backup
What you'll learn
- Identify what secrets state may contain
- Apply state encryption at rest and in transit
- Apply access control and audit to state
- Configure backups and recovery for state
Prerequisites
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
The state file is the most sensitive artefact in a Terraform estate. It contains the topology of the real-world infrastructure and, often, the secrets used to manage it. This lesson teaches the security and backup requirements for production state.
What secrets may be in state
The state file is a JSON document. Every attribute the provider returned after the last refresh is in the state. The secrets-depending-on-the-attribute:
resource "aws_db_instance" "primary" {
# ...
password = var.db_password
}
The password attribute is in the state. The state shows the
literal password value, regardless of the sensitive = true
marking on the variable.
variable "db_password" {
type = string
sensitive = true
}
The sensitive marking hides the value in the CLI output. The
state has the value. The plan file has the value (the plan
shows (sensitive) instead of the value, but the plan file
itself has the value to be applied).
Common secrets in state:
- Database passwords.
aws_db_instance.password,azurerm_postgresql_server.administrator_password. - TLS private keys.
aws_acm_certificate.private_key(when imported),tls_private_key.private_key_pem. - API tokens. Any custom argument that is a string and represents a credential.
- OAuth tokens.
oauth_tokenarguments. - Connection strings. Any argument that contains a credential.
The recommendation: assume the state contains every secret used in the configuration. Treat the state as a database of secrets.
Encryption at rest
The state must be encrypted at rest by the backend. The recommended setup:
S3 backend
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true # SSE-S3 enabled by default
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/abc-123"
}
}
The encrypt = true enables SSE-S3. The kms_key_id argument
upgrades to SSE-KMS with a customer-managed key. The key is
managed by the operator; the S3 bucket is encrypted with the
key.
OpenTofu state encryption
OpenTofu 1.7+ supports client-side state encryption:
terraform {
backend "s3" {
bucket = "mycompany-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
}
encryption {
key_provider "pbkdf2" "my_passphrase" {
passphrase = var.state_encryption_passphrase
}
method "aes_gcm" "my_method" {
key_length = 32
}
state "my_state" {
method = my_method.my_method
}
}
}
The state is encrypted before it leaves the engineer. The S3 bucket stores the encrypted state. The passphrase is in the operators environment; the provider does not need it.
This is the strongest state-at-rest posture. The state is encrypted with a key the operator controls.
GCS backend
terraform {
backend "gcs" {
bucket = "mycompany-terraform-state"
prefix = "production"
encryption_key {
kms_encryption_key {
kms_key_name = "projects/.../keyRings/.../cryptoKeys/..."
}
}
}
}
The GCS backend supports customer-managed encryption keys.
Encryption in transit
The state is transmitted over TLS:
- The cloud providers API uses TLS by default.
- The local-to-backend hop uses TLS by default.
- The local disk reads/writes are not encrypted at the file level.
For local state, the local disk should be encrypted (LUKS, FileVault, BitLocker). For remote state, the backends TLS is the in-transit encryption.
Access control
The state must be access-controlled. The recommendation:
- Backend access is restricted to the team that needs to operate the configuration. The IAM policy for the S3 bucket is the operative control.
- Read access is granted to the team that needs to view the state (typically the operators).
- Write access is granted to the team that needs to apply changes (typically the operators).
- Delete access is restricted to a small number of people (typically the platform team).
The principle is least privilege: the smallest set of principals that can do the work.
# AWS: example IAM policy for read-only access to the state bucket
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mycompany-terraform-state",
"arn:aws:s3:::mycompany-terraform-state/*"
]
}
]
}
The policy grants read-only access. A separate policy grants write access. The principle is the same: smallest set of permissions.
Audit
The state must be auditable. The recommendation:
- Backend access is logged by the cloud providers audit log (CloudTrail, Cloud Audit Logs, etc.).
- State access is logged at the object level (S3 server access logs).
- Apply operations are logged by the CI/CD pipeline.
A production apply should produce an audit trail that includes:
- The engineer who triggered the apply.
- The credentials used.
- The configuration version.
- The plan output.
- The state version before and after.
- The real-world changes (provider API logs).
The audit trail is the production-grade documentation of the apply.
Backup
The state must be backed up. The recommendation:
- Backend versioning is enabled (S3 versioning, GCS versioning, etc.).
- Daily backups are taken with offsite copies.
- Recovery is tested — a backup that has never been restored is not a backup.
The recovery procedure for a single state:
# 1. Identify the most recent state version
aws s3api list-object-versions \
--bucket mycompany-terraform-state \
--key production/terraform.tfstate
# 2. Restore the latest version
aws s3api copy-object \
--bucket mycompany-terraform-state \
--key production/terraform.tfstate \
--copy-source mycompany-terraform-state/production.terraform.tfstate?versionId=...
# 3. Verify the restored state
terraform plan
The plan should be empty (or, if the configuration has changed, should match the expected diff).
What to keep in state
The state is the source of truth for what Terraform believes exists. The state is not the source of truth for:
- The real-world topology. The providers API is the source of truth.
- The history of changes. Git is the source of truth.
- The inventory. The CMDB or inventory system is the source of truth.
The state records what Terraform knows. The course has a dedicated lesson on observability and auditing for the broader ecosystem.
A state security checklist
A production state should satisfy:
- Encrypted at rest.
- Encrypted in transit.
- Access-controlled (least privilege).
- Audited (access logged).
- Backed up (daily, offsite).
- Not in Git.
- Sensitivity marked in the configuration.
- Recovery tested at least once.
A state that fails any of these is a production risk.
What comes next
The next lesson is state commands — the read-only and state-mutating commands that operate on state directly.
Knowledge check · 7 questions
Q1. What is the role of sensitive = true?
Q2. How should state secrets be managed?
Q3. sensitive = true is sufficient for production secret management.
Q4. How should the state backend be encrypted at rest?
Q5. Which of the following are state security controls? (Select all that apply.)
Q6. What is the role of OpenTofu client-side encryption?
Q7. A credential was accidentally committed to the state. What is the first step?
Passing score: 75%. Answers are checked in this browser.