Terraform Security Best Practices

Infrastructure as Code enables consistent deployments, but insecure Terraform configurations can codify vulnerabilities across your entire infrastructure.

Introduction

A growing e-commerce company adopted Terraform to manage their AWS infrastructure, enabling consistent and repeatable deployments. Their infrastructure team created modules for VPCs, EC2 instances, RDS databases, and S3 buckets. A security audit revealed that their RDS module defaulted to publicly_accessible = true, the S3 module allowed public bucket policies, and worst of all, the Terraform state file stored in S3 contained plaintext database passwords—accessible to anyone with bucket read permissions.

Infrastructure as Code transforms how organizations manage cloud resources, but it also changes the security model. Misconfigurations aren't one-time mistakes; they're codified into modules that get deployed repeatedly. Secrets in state files persist indefinitely. This guide covers Terraform security best practices.

Understanding Terraform Security Risks

State File Exposure: Terraform state files contain the complete picture of managed infrastructure, including sensitive values like database passwords, API keys, and certificate private keys. If the backend isn't properly secured, state files can be accessed by unauthorized parties.

Insecure Default Configurations: Terraform resources often have insecure defaults. When teams create reusable modules without explicitly setting secure defaults, those insecure configurations propagate across the organization.

Secrets in Code: Hardcoding secrets in Terraform configurations is common but dangerous. These secrets end up in version control history, CI/CD logs, and state files.

Drift: Manual changes to infrastructure create drift between actual state and Terraform configurations, potentially introducing untracked security misconfigurations.

Securing Terraform State

State security is foundational. Use encrypted remote backends:

terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "production/infrastructure.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-state-lock"
    kms_key_id     = "alias/terraform-state-key"
  }
}

Key configuration elements:

  • encrypt = true enables server-side encryption
  • kms_key_id uses a customer-managed KMS key
  • dynamodb_table prevents concurrent modifications (state locking)

Apply least-privilege access to state storage. Enable versioning to maintain state history. Restrict backend access to specific IAM roles.

Secrets Management

Never hardcode secrets. Use external secret management:

# Using AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
  secret_id = "production/database/master-password"
}
 
resource "aws_db_instance" "main" {
  identifier     = "production-db"
  engine         = "postgres"
  username       = "admin"
  password       = data.aws_secretsmanager_secret_version.db_password.secret_string
}

For CI/CD pipelines, inject secrets as environment variables:

variable "database_password" {
  type        = string
  sensitive   = true
  description = "Database master password"
}

Pass via environment variable: TF_VAR_database_password=<secret> terraform apply

Mark variables as sensitive to prevent console output (state will still contain the secret).

Security Scanning

Integrate security scanning into your pipeline:

# Checkov - open-source static analysis
pip install checkov
checkov -d ./terraform --framework terraform
 
# tfsec - Terraform-specific scanner
brew install tfsec
tfsec ./terraform

Example findings these tools catch:

  • S3 buckets without encryption
  • Security groups allowing 0.0.0.0/0
  • RDS instances without encryption at rest
  • IAM policies with excessive permissions

Writing Secure Modules

Create modules with secure defaults:

resource "aws_s3_bucket" "main" {
  bucket = var.bucket_name
}
 
resource "aws_s3_bucket_public_access_block" "main" {
  bucket                  = aws_s3_bucket.main.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}
 
resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
  bucket = aws_s3_bucket.main.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = var.kms_key_arn != null ? "aws:kms" : "AES256"
      kms_master_key_id = var.kms_key_arn
    }
  }
}
 
resource "aws_s3_bucket_versioning" "main" {
  bucket = aws_s3_bucket.main.id
  versioning_configuration {
    status = "Enabled"
  }
}

CI/CD Integration

# GitHub Actions example
name: Terraform Security Check
on:
  pull_request:
    paths: ['terraform/**']
jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
      - name: Terraform Validate
        run: terraform validate
        working-directory: ./terraform
      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: ./terraform

Best Practices

  • Pin provider versions: Prevent unexpected changes
  • Pin module versions: Use specific versions from registries
  • Plan review process: Never apply without reviewing the plan—generate with terraform plan -out=tfplan, review with terraform show tfplan
  • Separate environments: Use workspaces or separate state files
  • Pre-commit hooks: Catch issues before they're committed

Conclusion

Terraform security requires attention to state protection, secrets management, secure module design, and automated scanning. Unlike manual infrastructure changes, IaC misconfigurations can propagate across all environments and persist in version control history.

Integrating security scanning into CI/CD catches issues before deployment, but it's equally important to test deployed infrastructure. On-demand security testing validates that Terraform configurations result in secure deployments and that no drift has introduced vulnerabilities. RedVeil's AI-powered platform can assess your cloud infrastructure alongside applications, verifying that your IaC practices result in secure environments.

Start testing your infrastructure with RedVeil today.

Ready to run your own test?

Start your first RedVeil pentest in minutes.