Home
Blog
OpenTofu Secrets & State Encryption: How to Secure Sensitive Infrastructure Data

OpenTofu Secrets & State Encryption: How to Secure Sensitive Infrastructure Data

with special guest
Mitchell
Hashimoto
Mitchell Hashimoto headshot

Infrastructure as Code brings enormous benefits — repeatability, version control, team collaboration. But it also creates a risk that many teams underestimate: sensitive data ends up stored in places it should never be.

Database passwords, API keys, TLS certificates, and cloud credentials can all find their way into OpenTofu state files, configuration files, or CI/CD logs — often without anyone realising it.

This guide explains where secrets leak in OpenTofu workflows, how to handle them safely, and how to use OpenTofu's native state file encryption (introduced in OpenTofu 1.7) to protect sensitive data at rest.

 

Why Secrets in IaC Are Dangerous

The problem is not that Terraform or OpenTofu handles secrets badly by design. The problem is that infrastructure provisioning requires secrets to exist at some point in the workflow — and without deliberate controls, they end up persisted in ways that are easy to overlook.

The State File Problem

OpenTofu (like Terraform) stores a complete record of every resource it manages in a state file. This state file includes the full attribute values of every resource — including values marked as sensitive in your configuration.

That means if you provision an RDS database with a master password, or create an IAM access key, or set a secret environment variable on a Lambda function — those values are written to terraform.tfstate in plain text.

In Terraform/OpenTofu, marking a field as sensitive in your configuration does not guarantee it will be hidden in the state file. This is important when dealing with credentials like a database password.

# What you write in your config
resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
password = var.db_password # <-- marked sensitive
}

# What gets written to terraform.tfstate
{
"password": "my-super-secret-password-123" # plain text
}

Even if a variable is marked sensitive, it can still appear in the state file in plain text. The terraform.tfstate file is the source of truth for infrastructure and must be protected carefully using encryption, remote backends, and strict access control.

⚠️  The sensitive = true attribute on a variable only hides the value from plan and apply output in your terminal. It does NOT prevent the value from being written to the state file in plain text.

 

Other Common Secret Leak Points

State files are the biggest risk, but they are not the only one. Secrets also end up in:

•       Git repositories — .tfvars files, terraform.tfstate files, or hardcoded values committed by mistake

•       CI/CD logs — when secrets are passed as environment variables and a tofu plan prints them in error messages

•       Backend storage — unencrypted S3 buckets, Azure Blob containers, or GCS buckets storing state files

•       Provider configurations — credentials hardcoded in provider blocks instead of using environment variables

 

 

OpenTofu Secrets Management Options

There is no single right answer for secrets management in OpenTofu. The best approach depends on your cloud provider, your existing tooling, and how sensitive the data is. Here is a comparison of the main options:

Method Pros Cons Best For
Environment Variables Simple, no extra tooling required Not auditable, risk of log leakage Low-sensitivity CI/CD secrets
AWS Secrets Manager Audited, rotatable, native AWS integration AWS-only, adds latency Production AWS workloads
HashiCorp Vault Multi-cloud support, dynamic secrets Operational overhead Large, multi-cloud teams
OpenTofu State Encryption Native feature, no extra tooling Protects state only, not configuration files All teams — recommended by default
SOPS + Age/PGP Encrypted files stored safely in Git Key management complexity GitOps workflows
Azure Key Vault Native Azure integration, RBAC support Azure-only Production Azure workloads
GCP Secret Manager Native GCP integration, IAM-controlled access GCP-only Production GCP workloads

💡  The best approach is to layer these options: use state encryption as a baseline for all workloads, combine it with a secrets manager (Vault, AWS Secrets Manager, or cloud-native equivalent) for sensitive values, and use environment variables only for low-sensitivity CI/CD configuration.

 

 

State File Encryption (OpenTofu 1.7+)

OpenTofu 1.7 introduced native state file encryption — one of the most significant features that differentiates OpenTofu from Terraform. It lets you encrypt your state file at rest using a key you control, so even if someone gains access to your backend storage, they cannot read sensitive resource attributes.

Terraform does not have this feature. If state encryption is important to your team, this is a compelling reason to choose OpenTofu.

How State Encryption Works

You configure encryption in the terraform block using an encryption stanza. OpenTofu supports multiple key providers and encryption methods. The most straightforward option uses a passphrase-based key for getting started:

This configuration enables state encryption for OpenTofu using a key derived from a passphrase. It defines a key provider and an AES-GCM encryption method to protect state data at rest.

terraform {
encryption {
key_provider "pbkdf2" "my_key" {
passphrase = var.state_encryption_passphrase
}
method "aes_gcm" "default_method" {
keys = key_provider.pbkdf2.my_key
}
state {
method = method.aes_gcm.default_method
}
}
}

This setup ensures that the Terraform/OpenTofu state file is encrypted using a derived key from a secure passphrase. The pbkdf2 provider strengthens the key generation process, while aes_gcm provides authenticated encryption for the state data. This is a critical security practice for protecting infrastructure secrets in remote or shared environments.

💡  Never hardcode the passphrase value directly in your configuration file. Always reference a variable and supply the value through an environment variable or a secrets manager — never commit it to Git.

 

Using AWS KMS for State Encryption

For production workloads on AWS, using AWS KMS as the key provider is more secure than a passphrase. KMS gives you key rotation, access controls, and an audit trail through CloudTrail:

This configuration enables AWS KMS–backed encryption for both Terraform/OpenTofu state files and plan files. It uses a customer-managed KMS key to securely protect infrastructure data.

terraform {
encryption {
key_provider "aws_kms" "my_key" {
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/mrk-abc123"
key_spec = "AES_256"
region = "us-east-1"
}

method "aes_gcm" "default_method" {
keys = key_provider.aws_kms.my_key
}

state {
method = method.aes_gcm.default_method
}

# Also encrypt plan files
plan {
method = method.aes_gcm.default_method
}
}
}

By using AWS KMS, encryption keys are managed securely outside of your local machine, reducing risk of exposure. Both state and plan outputs are encrypted using AES-GCM, ensuring confidentiality and integrity across your infrastructure lifecycle. This setup is ideal for production-grade environments where sensitive data protection is required.

Using GCP KMS for State Encryption

This configuration enables Google Cloud KMS–based encryption for OpenTofu state files. It securely references a customer-managed encryption key stored in GCP KMS.

terraform {
encryption {
key_provider "gcp_kms" "my_key" {
kms_encryption_key = "projects/my-project/locations/global/keyRings/my-ring/cryptoKeys/my-key"
}

method "aes_gcm" "default_method" {
keys = key_provider.gcp_kms.my_key
}

state {
method = method.aes_gcm.default_method
}
}
}

With GCP KMS integration, encryption keys are centrally managed in Google Cloud, ensuring strong access control and auditability. The state file is protected using AES-GCM encryption, which provides both confidentiality and integrity for infrastructure state data.

Encrypting Plan Files Too

By default, tofu plan -out=plan.tfplan saves a plan file that can also contain sensitive values. Add a plan block inside your encryption configuration to encrypt plan files as well:

This snippet shows how to enable plan encryption inside your OpenTofu encryption block. It ensures that sensitive execution plans are protected before being stored or shared.

# Add this inside your encryption block
plan {
method = method.aes_gcm.default_method
}

By adding the plan block, you extend encryption beyond state files to include plan outputs as well. This helps prevent sensitive infrastructure details from being exposed during review or CI/CD workflows.

✅  State encryption is additive — it does not change your workflow. You still run tofu init, tofu plan, and tofu apply exactly as before. OpenTofu handles encryption and decryption transparently in the background.

 

HashiCorp Vault Integration

HashiCorp Vault is the most comprehensive secrets management solution for multi-cloud teams. It provides dynamic secrets (credentials that are generated on demand and expire automatically), fine-grained access control, and a full audit log of every secret access.

Reading Secrets from Vault in OpenTofu

The Vault provider lets you read secrets directly into your OpenTofu configuration at plan time:

This example shows how to integrate HashiCorp Vault with OpenTofu/Terraform to securely retrieve database credentials at runtime instead of hardcoding them in configuration files.

provider "vault" {
address = "https://vault.mycompany.com"

# Authenticate using a Vault token from an environment variable:
# export VAULT_TOKEN="s.abc123..."
}

# Read a static secret from Vault KV store
data "vault_kv_secret_v2" "db_creds" {
mount = "secret"
name = "production/database"
}

resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"

username = data.vault_kv_secret_v2.db_creds.data["username"]
password = data.vault_kv_secret_v2.db_creds.data["password"]
}

Instead of storing sensitive values directly in code, this setup pulls credentials dynamically from Vault KV v2. This significantly improves security by keeping secrets centralized, rotated, and access-controlled. The database resource then consumes these values at runtime without ever exposing them in configuration files.

Dynamic Database Credentials with Vault

For the highest level of security, use Vault's database secrets engine to generate short-lived credentials on demand. The credentials expire automatically after a configurable TTL — so even if they are captured, they become useless quickly:

This configuration demonstrates how to use Vault dynamic database credentials. Instead of static passwords, credentials are generated on-demand and automatically expire after a defined TTL.

# Generate dynamic, short-lived database credentials
data "vault_database_secret_backend_creds" "db" {
backend = "database"
role = "production-app-role"
}

# Use the dynamic credentials — they expire after the TTL
resource "aws_db_instance" "main" {
username = data.vault_database_secret_backend_creds.db.username
password = data.vault_database_secret_backend_creds.db.password
}

This approach improves security by ensuring credentials are ephemeral and automatically rotated. Applications receive fresh database access each time, reducing the risk of long-lived secret exposure and eliminating manual password management.

⚠️  When using dynamic credentials, the generated values will be written to your OpenTofu state file. This is exactly why combining Vault with state encryption is the recommended approach for production workloads.

 

 

AWS Secrets Manager Integration

For AWS-focused teams, AWS Secrets Manager is a simpler alternative to Vault. It integrates natively with IAM for access control and supports automatic secret rotation.

This example shows how to securely retrieve a database password from AWS Secrets Manager and inject it into a database resource without hardcoding sensitive values.

# Read a secret from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "production/app/db-password"
}

resource "aws_db_instance" "main" {
identifier = "production-db"
engine = "postgres"
username = "admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

Instead of storing credentials in code, this approach pulls them directly from AWS Secrets Manager at runtime. This improves security, enables centralized secret rotation, and reduces the risk of credential leakage in infrastructure configuration files.

The key advantage of this approach is that the actual secret value never appears in your .tf files or version control. OpenTofu fetches it at plan time using your AWS credentials. The value will still appear in state — which is why state encryption should always be used alongside this pattern.

 

Environment Variables Best Practices

Environment variables are the simplest way to pass secrets to OpenTofu without hardcoding them. They are the right tool for low-to-medium sensitivity values like provider credentials and API tokens in CI/CD pipelines.

Using Environment Variables for Provider Credentials

All major providers support environment variable authentication. Always use these instead of hardcoding credentials in provider blocks:

This snippet shows how to configure cloud provider credentials and OpenTofu/Terraform variables using environment variables. These values are typically injected through CI/CD pipelines or local shell sessions.

# AWS — set these in your CI/CD pipeline or local shell
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="abc123..."
export AWS_REGION="us-east-1"

# Azure
export ARM_CLIENT_ID="..."
export ARM_CLIENT_SECRET="..."
export ARM_SUBSCRIPTION_ID="..."
export ARM_TENANT_ID="..."

# GCP
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/sa-key.json"

# OpenTofu variables — prefix with TF_VAR_
export TF_VAR_db_password="my-secret-password"

Using environment variables keeps sensitive credentials out of source code. Cloud providers automatically detect these values at runtime, while OpenTofu/Terraform reads TF_VAR_* variables as input parameters for your infrastructure configuration.

Rules for Using Environment Variables Safely

•       Never print environment variables in scripts — avoid env, printenv, or set commands in CI/CD pipelines that could expose them in logs

•       Use CI/CD secret stores — GitHub Actions Secrets, GitLab CI Variables (masked), or AWS Parameter Store for storing and injecting secrets into pipelines

•       Rotate credentials regularly — especially for long-lived CI/CD credentials that have broad permissions

•       Scope permissions tightly — the AWS role or service principal used by your pipeline should only have the permissions it needs for the specific workloads it manages

•       Never use environment variables for highly sensitive data — database master passwords, TLS private keys, and encryption keys should always go through a secrets manager

 

OpenTofu Secrets Security: Best Practices at a Glance

Best Practice How to Implement It
Enable state file encryption Use OpenTofu 1.7+ encryption with AWS KMS, GCP KMS, Azure Key Vault, or a passphrase for all production workloads.
Never commit secrets to Git Add *.tfvars, terraform.tfstate, and *.tfstate.backup to your .gitignore immediately.
Mark sensitive variables Use sensitive = true on all variable and output blocks that handle secret values.
Use a secrets manager Retrieve secrets from Vault, AWS Secrets Manager, Azure Key Vault, or GCP Secret Manager during plan or apply operations.
Use environment variables for credentials Supply provider credentials via environment variables instead of hardcoding them in configuration files.
Encrypt backend storage Enable server-side encryption on the S3 bucket, Azure Storage container, or GCS bucket used for storing state files.
Enable state versioning Turn on versioning for your state backend to recover from accidental corruption, overwrites, or deletions.
Audit secret access Use CloudTrail (AWS), Azure Monitor, GCP Audit Logs, or Vault audit logs to track access to secrets and state files.
Rotate credentials regularly Configure automatic rotation for database passwords, API keys, and cloud credentials whenever possible.
Restrict state file access Use IAM roles, bucket policies, and least-privilege permissions to limit who can read or modify the state backend.

How env zero Handles Secret Management

Managing secrets safely in OpenTofu requires discipline across every team, every pipeline, and every environment. In practice, that discipline is hard to maintain manually — especially as teams grow and the number of workloads increases.

env zero provides the governance layer that makes secure secret handling the default, not the exception:

•       No local credentials — developers never need cloud credentials on their laptops. All OpenTofu runs happen through env zero's platform, which injects credentials securely at runtime using short-lived tokens.

•       Centralised state management with encryption — env zero manages state storage with encryption enabled by default across all workspaces. Teams do not need to configure backends or encryption manually.

•       Secret scanning in plans — env zero scans plan output for patterns that look like secrets (API keys, tokens, passwords) and flags them before they can be accidentally logged or exposed.

•       Policy enforcement — env zero can enforce policies that require sensitive variables to be marked correctly, prevent secrets from appearing in outputs, and mandate state encryption for specific workspace types.

•       Audit trail — every run, every plan, every apply is logged with full attribution. If a secret is accessed or a sensitive resource is changed, you know exactly who did it and when.

 

Secrets management is one of the hardest operational challenges in IaC at scale. env zero is designed to make it significantly easier.

 

Frequently Asked Questions

Does sensitive = true prevent secrets from being stored in state?

No. The sensitive = true attribute on a variable or output only suppresses the value from being shown in terminal output during tofu plan and tofu apply. The value is still written to the state file in plain text. To protect secrets in state, you need to use state file encryption (OpenTofu 1.7+) and restrict access to the backend.

Is OpenTofu state encryption available in Terraform?

No. Native state file encryption is one of the features unique to OpenTofu. Terraform does not currently support it. If state encryption is a requirement for your team — for compliance reasons or general security posture — this is a meaningful reason to choose OpenTofu over Terraform.

Can I encrypt state for existing workloads without breaking anything?

Yes. OpenTofu's state encryption is designed to be enabled incrementally. You can add the encryption configuration to an existing workspace and run tofu apply — OpenTofu will encrypt the state on the next write. The existing state is not lost. It is recommended to take a manual backup before enabling encryption on a production workload, as an extra precaution.

What is the safest way to pass a database password in OpenTofu?

The safest pattern is: store the password in a secrets manager (Vault, AWS Secrets Manager, or equivalent), read it using a data source at plan time, reference it in the resource block, and enable state encryption so the value is protected at rest. Never hardcode passwords in .tf files or .tfvars files that could be committed to version control.

How do I stop secrets appearing in terraform plan output?

Mark the variable as sensitive = true. OpenTofu will redact the value in plan and apply output, replacing it with (sensitive value). Be aware that this does not protect the value in the state file — for that, you need state encryption.

This example defines a sensitive input variable in OpenTofu/Terraform. It is commonly used for secrets such as database passwords and API keys.

variable "db_password" {
type = string
sensitive = true
}

Marking a variable as sensitive = true ensures it is hidden in CLI output and logs. However, it does not encrypt or secure the value in state by itself—additional measures like state encryption or external secret managers are still required.

 

Summary

Secrets security in OpenTofu is a layered problem that requires multiple controls working together. Here is the short version:

•       The state file is your biggest risk — it stores sensitive resource attributes in plain text by default

•       Enable state encryption — OpenTofu 1.7+ makes this native and straightforward, especially with AWS KMS or GCP KMS

•       Use a secrets manager — pull secrets at plan time from Vault, AWS Secrets Manager, or your cloud provider's equivalent

•       Environment variables are fine for provider credentials and low-sensitivity CI/CD config — not for highly sensitive data

•       Mark all sensitive variables with sensitive = true to prevent them from appearing in terminal output

•       Never commit secrets to Git — add state files and tfvars files to .gitignore from day one

 

Security is not a one-time configuration. It is an ongoing practice — and the teams that get it right are the ones that make it the default, not an afterthought.

 

Want to see how env zero makes secure OpenTofu workflows the default for your entire platform team? [Talk to our team →](#)

Schedule a technical demo
See env zero in action
Schedule demo