Terraform security focuses on state protection, provider/module supply-chain control, least-privilege cloud credentials, and secure CI/CD integration. Since Terraform manages infrastructure directly, compromising Terraform can lead to full cloud environment compromise.
# backend.tf - Secure remote backend configuration
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/uuid"
dynamodb_table = "terraform-locks"
}
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/terraform-ci"
},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::terraform-state-prod/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "true"
}
}
}
]
}
# Generate and store plans securely
terraform plan -out=tfplan
aws s3 cp tfplan s3://terraform-plans/ \
--sse-kms-key-id arn:aws:kms:...
# Never commit plans to version control
echo "*.tfplan" >> .gitignore
echo "terraform.tfstate*" >> .gitignore
# versions.tf - Pin provider versions
terraform {
required_version = ">= 1.14.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.30"
}
google = {
source = "hashicorp/google"
version = "~> 5.10"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.27"
}
}
}
required_providers with version constraintsterraform.lock.hcl in version control# Use versioned module sources
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
# Pin to specific commit for maximum security
# source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git?ref=v5.5.0"
}
# Avoid unversioned or arbitrary sources
# source = "git::https://random-github-user.com/repo.git" # DANGEROUS
main branch# Generate provider lock file
terraform providers lock -platform=linux_amd64 -platform=darwin_amd64
# Verify provider checksums
terraform providers -json | jq '.[] | {name, version, provider}'
# Check provider signatures
terraform version -json | jq '.provider_selections'
terraform providers lock for reproducible builds# NEVER hardcode credentials in Terraform files
# Bad example - DO NOT DO THIS:
# resource "aws_instance" "bad" {
# ami = "ami-12345"
# user_data = <<-EOF
# export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
# export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# EOF
# }
# Good example - Use IAM roles or external secrets:
data "aws_iam_instance_profile" "app" {
name = "app-instance-profile"
}
resource "aws_instance" "good" {
ami = "ami-12345"
iam_instance_profile = data.aws_iam_instance_profile.app.name
}
.tf files# GitHub Actions - OIDC authentication
jobs:
terraform:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-ci
aws-region: us-east-1
- run: terraform init
- run: terraform plan
# Run Terraform in isolated environment
docker run --rm -it \
-e AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/token \
-v $(pwd):/workspace \
hashicorp/terraform:1.14.6 \
terraform apply
# .github/CODEOWNERS
# Require review for infrastructure changes
*.tf @infrastructure-team
modules/ @infrastructure-team
terraform.tfvars @infrastructure-team
terraform plan output in PR comments# Sentinel policy example (Terraform Cloud/Enterprise)
# Require encryption for S3 buckets
main = rule {
all S3_BUCKET_RESOURCES as _, bucket {
bucket.server_side_encryption_configuration is not null
}
}
validation blocks to modules# Require manual approval for production
deploy_production:
needs: plan
environment: production # GitHub Environments with reviewers
runs-on: ubuntu-latest
steps:
- run: terraform apply -auto-approve
# Enable Terraform Cloud/Enterprise audit logging
# Or self-hosted logging with telemetry
# CloudTrail for AWS
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=ConsoleLogin \
--start-time $(date -d '7 days ago' -Iseconds)
# Run security scanning before apply
tflint --config .tflint.hcl
checkov -d . --framework terraform
tfsec . --format sarif --out tfsec.sarif
# Integrate in CI pipeline
if ! tflint && ! checkov; then
echo "Security scan failed"
exit 1
fi
# Detect infrastructure drift
terraform plan -detailed-exitcode
if [ $? -eq 1 ]; then
echo "Drift detected!"
# Alert operations team
fi
# Scheduled drift detection
# Run terraform plan periodically via CI
terraform plan runs# Check Terraform version
terraform version
# Verify provider lock file exists
ls -l terraform.lock.hcl
# Review backend configuration
grep -A10 "backend" ./*.tf
# Check for hardcoded secrets
grep -r "password\|secret\|key\|token" ./*.tf --exclude="*.tfvars" | \
grep -v "variable\|description\|#" | head -20
# Validate configuration
terraform validate
# Format check
terraform fmt -check -recursive
# List providers
terraform providers
# Check state location
terraform workspace show
cat backend.tf | grep -A5 "backend"
# Run security scans
tflint
checkov -d . --quiet
tfsec . --format compact
.tf files