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.

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.

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

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.”

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.

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.

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

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.

Exit mobile version