How do I secure Terraform and infrastructure as code?

Direct answer

Two distinct problems. The state file contains secrets in plaintext, so it needs a remote encrypted backend and must never be committed. And the configuration itself can describe insecure infrastructure. Open security groups, wildcard IAM, public buckets, which static analysis catches before it's applied.

Muhammad HasanUpdated

The state file

Terraform state records the values of the resources it manages, and that includes sensitive ones. RDS passwords, generated keys, secret values. Stored in plaintext regardless of whether the variable was marked sensitive.

Marking a variable sensitive = true hides it from plan output. It does not encrypt it in state.

So: remote backend with encryption at rest and access control, state locking to prevent concurrent writes, and *.tfstate in .gitignore. If state has ever been committed, treat every secret in it as compromised and rotate.

terraform {
  backend "s3" {
    bucket         = "tf-state"
    key            = "prod/terraform.tfstate"
    encrypt        = true
    dynamodb_table = "tf-locks"
  }
}

Credentials in configuration

Hardcoded access keys in .tf files get committed. Reference secret managers or use provider credential chains instead of embedding values.

data "aws_secretsmanager_secret_version" "db" {
  secret_id = "prod/db"
}

Same for .tfvars, those get committed constantly. Keep them out of version control and template an example version instead.

Insecure configuration

The other half. Terraform will happily create infrastructure that's wide open, and the mistakes are consistent:

  • Security groups with 0.0.0.0/0 on anything that isn't a load balancer

  • IAM policies with Action = "*" and Resource = "*"

  • S3 buckets without public access blocks

  • Unencrypted volumes, databases and queues

  • Databases with publicly_accessible = true

  • No logging or flow logs

tfsec, checkov and terrascan all catch these. Run one in CI on every pull request. They're free, fast, and this is a class of problem where pattern matching works well. The vulnerable configuration has a recognisable shape.

Review the plan, not just the code

terraform plan output shows what will actually change, including things a module chose on your behalf. In CI, post the plan to the pull request and require approval before apply.

Pin module and provider versions. A module pulled from a registry at a floating version can change what it creates between runs.

Drift

Infrastructure changed by hand in the console won't appear in your code, and the next apply may revert it, or preserve a manual change nobody reviewed. Run drift detection on a schedule and treat console access to production as an exception rather than a habit.

Go deeper

Kolega for DevOps

Related answers

See what your own repository returns

Connect a repo and run a scan. No credit card, no pipeline changes.

Get started for free