Terraform Security
Learn how to scan Terraform code with tfsec and Checkov, protect secrets in state, secure backends, and integrate IaC security checks into CI/CD.
Why Terraform Security Scanning Matters
Terraform makes infrastructure repeatable, but repeatable does not automatically mean secure.
If you write an insecure S3 bucket policy, a security group that opens SSH to the world, or an unencrypted EBS volume, Terraform will happily create it consistently every time. That is why Infrastructure as Code security matters so much.
Misconfigured cloud infrastructure is one of the most common causes of cloud incidents. In many breaches, the root problem is not a sophisticated zero-day. It is something simpler:
- a public S3 bucket
- an overly broad IAM policy
- unrestricted inbound access like
0.0.0.0/0 - missing encryption on data storage
- secrets exposed in state or logs
Terraform security is therefore about two related goals:
- catch insecure patterns before they reach the cloud
- protect the Terraform workflow itself, especially state and credentials
This lesson covers both.
Shift Left: Scan Before Apply
The biggest win in Terraform security is catching issues before terraform apply ever runs.
That is the idea behind IaC scanners such as:
- tfsec
- Checkov
These tools parse your Terraform code and look for risky patterns. They do not replace cloud-native security controls, but they dramatically improve early feedback.
tfsec
tfsec is a popular Terraform-focused static analysis tool. It looks for insecure Terraform patterns and reports findings with severity information.
Installing tfsec
Common installation options include:
brew install tfsec
or with Go:
go install github.com/aquasecurity/tfsec/cmd/tfsec@latest
In CI, many teams use a prebuilt container or download the binary during the job.
Running tfsec
From the Terraform project directory:
tfsec .
You can also target a specific directory:
tfsec terraform/env/prod
Understanding tfsec findings
A finding usually includes:
- the rule ID
- the severity
- the affected file and line
- an explanation of the risk
- remediation guidance
That makes tfsec useful not just as a blocker, but also as a teaching tool.
Severity levels
Tools vary slightly, but the common severity language is:
- LOW
- MEDIUM
- HIGH
- CRITICAL
Not every project treats severities the same way. A common policy is:
- fail the pipeline on HIGH and CRITICAL
- review MEDIUM findings carefully
- triage LOW findings based on context
Checkov
Checkov is another widely used IaC security scanner. It supports Terraform and many other IaC formats, which makes it attractive for teams with mixed tooling.
Installing Checkov
A common local install method is:
pip install checkov
You can also run it in containers or dedicated CI images.
Running Checkov
checkov -d .
Or for a specific folder:
checkov -d terraform/env/prod
Checkov produces rule-based findings similar to tfsec and is especially useful when you want broader platform coverage beyond Terraform alone.
Suppressing False Positives Carefully
Security scanners are helpful, but no scanner understands every business decision automatically. Sometimes you intentionally choose a pattern that looks risky in isolation.
Example: a tutorial bucket that is intentionally public for static website hosting.
In those cases, you may need to suppress a specific finding.
Example Checkov suppression
resource "aws_s3_bucket_policy" "assets" {
#checkov:skip=CKV_AWS_70:Intentional public-read bucket for tutorial static website example
bucket = aws_s3_bucket.assets.id
policy = data.aws_iam_policy_document.assets_public_read.json
}
Good suppression hygiene
If you suppress a finding:
- suppress the smallest possible scope
- include a clear reason
- review suppressions periodically
- never use suppression as an excuse to skip understanding the finding
False positives happen. Lazy suppressions also happen. Mature teams distinguish between the two.
Common IaC Security Problems
Some Terraform mistakes appear constantly in real projects. Learn these early because scanners will find them often.
1. Public S3 Buckets
A public bucket is not always wrong, but it is dangerous by default.
Example risk:
resource "aws_s3_bucket_policy" "bad" {
bucket = aws_s3_bucket.data.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.data.arn}/*"
}]
})
}
If the bucket contains internal data, logs, backups, or application artifacts, public read access is a serious issue.
Safer mindset
Ask first:
- should this bucket be private?
- can CloudFront serve it instead?
- do I need a narrow policy or origin access control?
2. Unrestricted Security Groups
One of the most common scanner findings is this:
cidr_blocks = ["0.0.0.0/0"]
For HTTP or HTTPS on a public service, that may be correct. For SSH, RDP, database ports, or internal application ports, it is usually a red flag.
Example of risky SSH
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
Better approach
Restrict admin access to:
- a single trusted IP
- a VPN range
- a bastion host security group
- private connectivity instead of public exposure
3. Unencrypted EBS Volumes
Storage encryption is another common check.
Example of safer explicit encryption:
resource "aws_ebs_volume" "data" {
availability_zone = "us-east-1a"
size = 20
encrypted = true
}
Many AWS accounts now enable encryption by default, but being explicit in Terraform is still valuable for clarity and policy enforcement.
4. IAM Over-Permissions
IAM misconfiguration is often harder to spot than public networking problems because the syntax looks harmless while the impact is large.
Examples of risky patterns:
Action = "*"Resource = "*"- administrator-style permissions for app roles
- broad wildcard permissions where specific ARNs should be used
Least privilege is slower to design, but much safer to operate.
Integrating tfsec and Checkov into GitLab CI
Static scanning becomes truly valuable when it is automatic.
Here is a simple GitLab CI example that adds IaC security checks to a Terraform pipeline.
stages:
- validate
- security
- plan
- apply
terraform-validate:
stage: validate
image: hashicorp/terraform:1.9.8
script:
- terraform init -backend=false
- terraform fmt -check -recursive
- terraform validate
terraform-tfsec:
stage: security
image: aquasec/tfsec:latest
script:
- tfsec .
terraform-checkov:
stage: security
image: bridgecrew/checkov:latest
script:
- checkov -d .
terraform-plan:
stage: plan
image: hashicorp/terraform:1.9.8
script:
- terraform init
- terraform plan -out=tfplan
Why a separate security stage?
Because it keeps the pipeline readable and makes the policy explicit:
- syntax and formatting first
- security checks next
- planning after the code passes safety gates
You can decide whether security findings should block all plans or only fail on higher severities.
Secrets in the Terraform State File
This is one of the most important Terraform security topics.
Terraform state can contain sensitive data, including:
- generated passwords
- connection strings
- rendered templates
- private IPs and infrastructure metadata
- resource arguments you might not realize are sensitive
- output values that expose secrets
Even when Terraform marks some values as sensitive in CLI output, the state file often still stores the real value in plaintext or recoverable form, depending on provider behavior.
That means state is sensitive data and must be protected accordingly.
How to Protect State
At minimum:
- use a remote backend
- encrypt the storage backend
- restrict IAM access tightly
- enable versioning where appropriate
- audit who can read state
- never commit
terraform.tfstateto Git
If an attacker can read your state, they may learn far more than you intended.
Sensitive Variables
Terraform lets you mark variables as sensitive.
variable "db_password" {
description = "Database password"
type = string
sensitive = true
}
This mainly affects display behavior. Terraform will try not to print the value in normal CLI output.
Important limitation
Marking a variable as sensitive = true does not guarantee it never appears anywhere. It helps reduce accidental exposure in output, but you still must protect state, logs, and downstream systems.
Sensitive Outputs
Outputs can also be marked sensitive.
output "db_password" {
value = var.db_password
sensitive = true
}
Again, this hides values in many CLI displays, but it does not magically remove them from state handling concerns.
The right lesson is:
- use
sensitiveto reduce accidental exposure - do not mistake it for full secret management
Backend Security: Encrypting S3 State with KMS
If you use an S3 backend, secure the bucket aggressively.
A good pattern includes:
- private S3 bucket
- bucket versioning enabled
- restricted IAM access
- DynamoDB locking
- server-side encryption with KMS
Example backend configuration:
terraform {
backend "s3" {
bucket = "devopslesson-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-state-locks"
encrypt = true
kms_key_id = "alias/terraform-state"
}
}
Why KMS matters
KMS gives you tighter control over who can decrypt the state object and provides richer auditability than relying only on default storage behavior.
You should also secure the backend resources themselves with:
- bucket public access blocks
- bucket policies that deny insecure access
- logging if your compliance model requires it
HashiCorp Vault Integration
Terraform is not a secrets manager. If your workflow needs dynamic secrets, short-lived credentials, or centrally managed secret access, HashiCorp Vault is often the better tool.
Terraform can integrate with Vault in a few ways:
- reading secrets from Vault data sources
- using Vault-generated cloud credentials
- allowing CI pipelines to obtain short-lived secrets instead of storing long-lived static ones
That improves security because the pipeline does not need permanent credentials sitting in repository settings forever.
Sentinel Policies
Terraform Sentinel is a policy-as-code feature associated with Terraform Enterprise and some higher-end workflow setups.
You do not need Sentinel to use Terraform safely, but it is worth knowing what it is.
Sentinel lets organizations enforce rules such as:
- no public S3 buckets unless specifically approved
- no security groups with unrestricted SSH
- all production EBS volumes must be encrypted
- certain tags must exist on every resource
Think of Sentinel as an organizational guardrail layer on top of normal Terraform execution.
Security Review Checklist for Terraform Projects
When reviewing a Terraform repo, ask these questions:
- Are there any public S3 buckets or broad bucket policies?
- Are security groups exposing admin or database ports to the world?
- Are storage resources encrypted explicitly?
- Are IAM roles and policies least-privilege?
- Is remote state protected and encrypted?
- Are secrets being passed through variables, outputs, or state carelessly?
- Are tfsec and/or Checkov part of CI?
- Are suppressions documented and justified?
This checklist catches a large percentage of common IaC problems.
A Practical Security Workflow
A strong but realistic Terraform security workflow looks like this:
- write Terraform code
- run
terraform fmtandterraform validate - run
tfsecandcheckov - review findings and fix or justify them
- generate a plan in CI
- protect remote state and credentials
- apply only through controlled branches or approvals
This is not security perfection. It is operational discipline, which is what prevents most avoidable mistakes.
Final Thoughts
Terraform security is not only about finding bad code patterns. It is equally about protecting the Terraform workflow itself.
You need both:
- secure infrastructure definitions
- secure handling of state, secrets, and pipeline credentials
If you remember only one thing from this lesson, make it this: a Terraform repo can be dangerous even before anything is applied. The code, the state, the CI variables, and the backend are all part of the security boundary.
Practice Questions
Question 1: State File Risk
Why is the Terraform state file considered sensitive?
Question 2: Scanner Purpose
What is the main benefit of tools like tfsec and Checkov?
Question 3: Sensitive Variables
What does `sensitive = true` on a Terraform variable primarily do?