Terraform Security Best Practices

Terraform Security Best Practices

The scariest Terraform mistake I’ve watched someone make wasn’t in the code itself — it was in the state file. A public S3 bucket, used as a Terraform backend, with no encryption and no access restrictions, sitting there with every database password and API key the team had ever provisioned in plaintext. Terraform makes infrastructure reproducible and version-controlled, which is fantastic for velocity, but that same convenience means a single mistake can expose or misconfigure your entire infrastructure at once.

Here’s how I think about Terraform security across the full lifecycle — from state management to the actual resources it provisions.

Where Risk Lives in a Terraform Workflow

flowchart TD
    A[Terraform Code] --> B[Plan]
    B --> C[State File]
    C --> D[Apply]
    D --> E[Provisioned Infrastructure]
    C -.Exposed Secrets Risk.-> F[Attacker Access]
    A -.Hardcoded Credentials.-> F
    style C fill:#f96,stroke:#333
    style F fill:#f66,stroke:#333

1. Secure Your Terraform State

Terraform state files often contain sensitive data in plaintext — database passwords, private keys, and other resource attributes captured at apply time. Treat your state backend with the same seriousness as a secrets manager.

  • Use a remote backend (S3, Azure Storage, GCS, or Terraform Cloud) rather than local state files.
  • Enable encryption at rest on the backend storage.
  • Restrict access with strict IAM policies — state should not be broadly readable.
  • Enable state locking (via DynamoDB for S3 backends, or natively in Terraform Cloud) to prevent concurrent modification and corruption.
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

2. Never Hardcode Secrets in Terraform Code

Credentials committed to .tf files end up in version control history permanently, even if you delete them in a later commit.

  • Use environment variables or a secrets manager (Vault, AWS Secrets Manager) to inject sensitive values.
  • Mark sensitive variables explicitly so Terraform redacts them from plan/apply output.
variable "db_password" {
  type      = string
  sensitive = true
}
  • Add *.tfvars files containing real values to .gitignore, and use a scanning tool like gitleaks or trufflehog in CI to catch accidental commits.

3. Apply Least Privilege to the Terraform Execution Role

The IAM role or service principal Terraform runs as should have exactly the permissions needed to manage the resources in its scope — not broad administrator access “to avoid permission errors.”

  • Scope IAM policies to specific resource types and, where possible, specific resource ARNs.
  • Use separate execution roles per environment (dev, staging, production) so a mistake in one doesn’t cascade into another.
  • Review and prune permissions periodically as your infrastructure evolves.

4. Scan Terraform Code for Misconfigurations

Static analysis tools can catch dangerous patterns before they’re ever applied — a security group open to 0.0.0.0/0, an S3 bucket without encryption, or a database with public accessibility enabled.

tfsec .
checkov -d .

Both tfsec and checkov are widely used, open-source options that plug directly into CI pipelines and fail builds on high-severity findings, the same way image scanners do for container builds — see my related post on Container Image Scanning Explained for a parallel approach in a different part of the stack.

5. Use Modules from Trusted Sources

Public Terraform Registry modules are convenient, but pulling infrastructure code from an unverified or unmaintained source is a supply chain risk, just like an unpinned container base image.

  • Pin module versions explicitly rather than tracking latest or a branch reference.
  • Review the source of third-party modules before adopting them widely.
  • Prefer modules maintained by verified publishers or your own organization for anything security-sensitive.
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.8.1"
}

6. Integrate Terraform Securely into CI/CD

If you’re running Terraform through Jenkins or another CI/CD system, the same daemon and credential-handling principles that apply to Docker apply here too — protect the execution environment, restrict who can trigger applies against production, and require plan review before apply. I’ve covered practical setup patterns in Using Jenkins with Terraform for Infrastructure as Code and the broader deployment context in Setting Up Jenkins for Continuous Deployment on AWS.

  • Require a manual approval step before terraform apply runs against production.
  • Store CI credentials for cloud providers as encrypted secrets, never as plaintext environment variables in job configuration.
  • Log every plan and apply for audit purposes.

7. Review Plans Before Applying

terraform plan exists precisely so you can review exactly what’s about to change before it happens. Never skip this step in production workflows, even when it feels like a formality.

terraform plan -out=tfplan
terraform apply tfplan

Applying a saved plan (rather than re-running apply independently) also guarantees the plan reviewed is exactly the plan executed, with no drift in between.

Common Terraform Security Mistakes

  • Storing state locally or in an unencrypted, publicly accessible bucket.
  • Hardcoding cloud credentials or database passwords directly in .tf files.
  • Granting the Terraform execution role broad administrator permissions.
  • Using unpinned module versions that can change unexpectedly between runs.
  • Skipping terraform plan review and applying directly in production.
  • Never running static analysis, so misconfigurations like open security groups slip straight into production.

Best Practices Checklist

  1. Use a remote, encrypted, access-restricted backend for state.
  2. Mark sensitive variables and never hardcode secrets in code.
  3. Scope the Terraform execution role to least privilege, per environment.
  4. Run tfsec or checkov in CI on every pull request.
  5. Pin module versions and vet third-party sources.
  6. Require plan review and manual approval before production applies.
  7. Enable state locking to prevent concurrent modification corruption.

FAQs

Is Terraform state always sensitive? Often, yes. Many resource types store attributes in state that include sensitive values, even if you didn’t explicitly define them as secrets in your code — so treat all state as sensitive by default.

Can I use Terraform Cloud instead of managing my own backend? Yes, and it handles state encryption, locking, and access control for you, which removes a significant amount of the manual setup described above.

How do static analysis tools like tfsec differ from cloud provider security tools? Static analysis tools check your Terraform code before anything is deployed, catching misconfigurations early, while cloud-native tools (like AWS Security Hub) typically assess resources after they already exist.

Should every Terraform apply require manual approval? For production environments, yes — a manual review of the plan output is a cheap, high-value control that catches unintended changes before they affect live infrastructure.

Conclusion

Terraform’s biggest security risks aren’t usually in some exotic edge case — they’re in how state is stored, how credentials are handled, and how much privilege the execution role is granted. Lock down your backend, keep secrets out of code, scope permissions tightly, and add static analysis to your pipeline. Combined with careful CI/CD practices, this turns Terraform from a potential single point of failure into one of the most auditable, secure parts of how you manage infrastructure.

Total
0
Shares

Leave a Reply

Previous Post
Prevent Cloud Misconfigurations with DevSecOps

Prevent Cloud Misconfigurations with DevSecOps

Next Post
Runtime Container Security vs Image Scanning: What's the Difference?

Runtime Container Security vs Image Scanning: What’s the Difference?

Related Posts