Skip to content

TFSec Cheat Sheet

Overview

TFSec is a static analysis security scanner for Terraform code that identifies potential security issues in Infrastructure as Code before deployment. It provides comprehensive security checks across multiple cloud providers with detailed remediation guidance and integrates seamlessly into CI/CD pipelines.

⚠️ Note: Free and open-source tool. Now part of Trivy as of 2023.

Installation

Binary Installation

# Linux/macOS
curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash

# macOS with Homebrew
brew install tfsec

# Windows with Chocolatey
choco install tfsec

# Windows with Scoop
scoop install tfsec

# Direct download
# Visit: https://github.com/aquasecurity/tfsec/releases
# Download appropriate binary for your platform

Docker Installation

# Pull Docker image
docker pull aquasec/tfsec:latest

# Run with Docker
docker run --rm -it \
  -v $(pwd):/src \
  aquasec/tfsec:latest /src

# Docker alias for convenience
alias tfsec='docker run --rm -it -v $(pwd):/src aquasec/tfsec:latest'

Package Managers

# Alpine Linux
apk add tfsec

# Arch Linux
yay -S tfsec

# Ubuntu/Debian (via snap)
sudo snap install tfsec

# Go install
go install github.com/aquasecurity/tfsec/cmd/tfsec@latest

Basic Usage

Simple Scan

# Scan current directory
tfsec

# Scan specific directory
tfsec /path/to/terraform/code

# Scan with verbose output
tfsec --verbose

# Scan and show passed checks
tfsec --include-passed

Output Formats

# JSON output
tfsec --format json

# SARIF output
tfsec --format sarif

# JUnit XML output
tfsec --format junit

# CSV output
tfsec --format csv

# Checkstyle XML output
tfsec --format checkstyle

# GitHub output
tfsec --format github

# Save output to file
tfsec --format json --out results.json

Filtering Results

# Show only high severity issues
tfsec --minimum-severity HIGH

# Show critical and high severity
tfsec --minimum-severity CRITICAL

# Include specific checks
tfsec --include-checks AWS001,AWS002

# Exclude specific checks
tfsec --exclude-checks AWS001,AWS002

# Exclude by severity
tfsec --exclude-severity LOW,MEDIUM

Configuration

Configuration File

# .tfsec.yml or tfsec.yml
severity_overrides:
  AWS001: ERROR
  AWS002: WARNING

exclude_checks:
  - AWS001
  - AWS002

include_checks:
  - AWS003
  - AWS004

minimum_severity: MEDIUM

exclude_paths:
  - "**/.terraform/**"
  - "**/node_modules/**"
  - "examples/**"

include_paths:
  - "modules/**"
  - "environments/**"

Environment Variables

# Set configuration via environment variables
export TFSEC_MINIMUM_SEVERITY=HIGH
export TFSEC_EXCLUDE_CHECKS=AWS001,AWS002
export TFSEC_FORMAT=json
export TFSEC_OUT=results.json

# Run with environment configuration
tfsec

Custom Checks Directory

# Specify custom checks directory
tfsec --custom-check-dir ./custom-checks

# Multiple custom check directories
tfsec --custom-check-dir ./custom-checks --custom-check-dir ./team-checks

Cloud Provider Coverage

AWS Security Checks

# AWS-specific scan
tfsec --include-checks AWS*

# Common AWS security issues:
# - S3 bucket encryption
# - IAM policy issues
# - Security group misconfigurations
# - RDS encryption
# - EBS encryption
# - CloudTrail configuration
# - VPC security

AWS Examples

# ❌ Insecure S3 bucket
resource "aws_s3_bucket" "bad_example" {
  bucket = "my-bucket"
  # Missing encryption configuration
}

# ✅ Secure S3 bucket
resource "aws_s3_bucket" "good_example" {
  bucket = "my-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "example" {
  bucket = aws_s3_bucket.good_example.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# ❌ Overly permissive security group
resource "aws_security_group" "bad_example" {
  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# ✅ Restrictive security group
resource "aws_security_group" "good_example" {
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }
}

Azure Security Checks

# Azure-specific scan
tfsec --include-checks AZURE*

# Common Azure security issues:
# - Storage account encryption
# - Network security groups
# - Key Vault configuration
# - SQL Database security
# - Virtual machine security

Azure Examples

# ❌ Insecure storage account
resource "azurerm_storage_account" "bad_example" {
  name                     = "storageaccountname"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "GRS"
  # Missing encryption configuration
}

# ✅ Secure storage account
resource "azurerm_storage_account" "good_example" {
  name                     = "storageaccountname"
  resource_group_name      = azurerm_resource_group.example.name
  location                 = azurerm_resource_group.example.location
  account_tier             = "Standard"
  account_replication_type = "GRS"

  enable_https_traffic_only = true
  min_tls_version          = "TLS1_2"

  encryption {
    services {
      blob {
        enabled = true
      }
      file {
        enabled = true
      }
    }
    source = "Microsoft.Storage"
  }
}

Google Cloud Security Checks

# GCP-specific scan
tfsec --include-checks GCP*

# Common GCP security issues:
# - Storage bucket permissions
# - Compute instance security
# - IAM bindings
# - Network security
# - Encryption settings

GCP Examples

# ❌ Public storage bucket
resource "google_storage_bucket" "bad_example" {
  name     = "my-bucket"
  location = "US"

  uniform_bucket_level_access = false
}

resource "google_storage_bucket_iam_member" "bad_example" {
  bucket = google_storage_bucket.bad_example.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

# ✅ Secure storage bucket
resource "google_storage_bucket" "good_example" {
  name     = "my-bucket"
  location = "US"

  uniform_bucket_level_access = true

  encryption {
    default_kms_key_name = google_kms_crypto_key.example.id
  }
}

resource "google_storage_bucket_iam_member" "good_example" {
  bucket = google_storage_bucket.good_example.name
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:${google_service_account.example.email}"
}

Inline Suppressions

Suppression Comments

# Suppress specific check
resource "aws_s3_bucket" "example" {
  #tfsec:ignore:AWS002
  bucket = "my-bucket"
  # Bucket encryption handled by separate resource
}

# Suppress multiple checks
resource "aws_security_group" "example" {
  #tfsec:ignore:AWS007,AWS008
  name_prefix = "example"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Suppress with explanation
resource "aws_instance" "example" {
  #tfsec:ignore:AWS012:This instance needs public IP for external access
  ami           = "ami-12345678"
  instance_type = "t2.micro"

  associate_public_ip_address = true
}

Block-Level Suppressions

# Suppress entire resource block
#tfsec:ignore:*
resource "aws_s3_bucket" "legacy" {
  bucket = "legacy-bucket"
  # Legacy bucket with known issues
  # Will be migrated in next sprint
}

# Suppress specific attribute
resource "aws_db_instance" "example" {
  allocated_storage = 20
  engine           = "mysql"

  #tfsec:ignore:AWS017
  storage_encrypted = false  # Encryption handled at application level
}

Conditional Suppressions

# Suppress based on condition
resource "aws_s3_bucket" "example" {
  bucket = var.bucket_name

  # Only ignore in development environment
  #tfsec:ignore:AWS002:exp:2024-12-31
  # Temporary suppression with expiration
}

CI/CD Integration

GitHub Actions

# .github/workflows/tfsec.yml
name: TFSec Security Scan

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  tfsec:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Run TFSec
      uses: aquasecurity/tfsec-action@v1.0.3
      with:
        soft_fail: true
        format: sarif
        output: tfsec.sarif

    - name: Upload SARIF file
      uses: github/codeql-action/upload-sarif@v2
      if: always()
      with:
        sarif_file: tfsec.sarif

    - name: TFSec Report
      uses: aquasecurity/tfsec-pr-commenter-action@v1.3.1
      with:
        github_token: ${{ github.token }}

GitLab CI

# .gitlab-ci.yml
tfsec:
  stage: security
  image: aquasec/tfsec:latest
  script:
    - tfsec --format gitlab-sast --out gl-sast-report.json .
  artifacts:
    reports:
      sast: gl-sast-report.json
    paths:
      - gl-sast-report.json
    expire_in: 1 week
  only:
    - merge_requests
    - main

Jenkins Pipeline

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('TFSec Scan') {
            steps {
                script {
                    docker.image('aquasec/tfsec:latest').inside {
                        sh '''
                            tfsec --format junit --out tfsec-results.xml .
                            tfsec --format json --out tfsec-results.json .
                        '''
                    }
                }
            }
            post {
                always {
                    publishTestResults testResultsPattern: 'tfsec-results.xml'
                    archiveArtifacts artifacts: 'tfsec-results.json', fingerprint: true
                }
            }
        }
    }
}

Azure DevOps

# azure-pipelines.yml
trigger:
- main

pool:
  vmImage: 'ubuntu-latest'

steps:
- task: Docker@2
  displayName: 'Run TFSec Security Scan'
  inputs:
    command: 'run'
    arguments: >
      --rm
      -v $(Build.SourcesDirectory):/src
      aquasec/tfsec:latest
      --format junit --out /src/tfsec-results.xml /src

- task: PublishTestResults@2
  displayName: 'Publish TFSec Results'
  inputs:
    testResultsFormat: 'JUnit'
    testResultsFiles: 'tfsec-results.xml'
    testRunTitle: 'TFSec Security Scan'
  condition: always()

Custom Checks

Writing Custom Checks

// custom-check.go
package main

import (
    "github.com/aquasecurity/tfsec/pkg/provider"
    "github.com/aquasecurity/tfsec/pkg/result"
    "github.com/aquasecurity/tfsec/pkg/rule"
    "github.com/aquasecurity/tfsec/pkg/scanner"
)

func init() {
    scanner.RegisterCheckRule(rule.Rule{
        Provider:    provider.AWSProvider,
        Service:     "s3",
        ShortCode:   "custom-bucket-naming",
        Summary:     "S3 bucket names should follow company naming convention",
        Impact:      "Non-standard bucket names may cause confusion",
        Resolution:  "Use company naming convention: company-env-purpose-random",
        Explanation: "All S3 buckets should follow the company naming convention",
        Links: []string{
            "https://company.com/docs/naming-conventions",
        },
        Terraform: &rule.EngineMetadata{
            GoodExamples: []string{`
resource "aws_s3_bucket" "good_example" {
  bucket = "mycompany-prod-data-abc123"
}
            `},
            BadExamples: []string{`
resource "aws_s3_bucket" "bad_example" {
  bucket = "my-random-bucket-name"
}
            `},
            Links: []string{
                "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket",
            },
            RemediationMarkdown: `
Ensure S3 bucket names follow the company naming convention:
- Format: company-environment-purpose-randomstring
- Use lowercase letters and hyphens only
- Keep names descriptive but concise
            `,
        },
        Severity: rule.SeverityMedium,
    }, func(s *scanner.Scanner, rule *rule.Rule) (results []result.Result) {
        // Custom check logic here
        return results
    })
}

Custom Check Configuration

# custom-checks.yml
checks:
  - id: CUSTOM001
    name: "Company S3 Naming Convention"
    description: "S3 buckets must follow company naming convention"
    severity: MEDIUM
    provider: aws
    service: s3
    resource_type: aws_s3_bucket
    attribute: bucket
    pattern: "^mycompany-(dev|staging|prod)-[a-z]+-[a-z0-9]{6}$"
    message: "Bucket name must follow format: mycompany-{env}-{purpose}-{random}"

Loading Custom Checks

# Load custom checks from directory
tfsec --custom-check-dir ./custom-checks

# Load custom checks from multiple directories
tfsec --custom-check-dir ./custom-checks --custom-check-dir ./team-checks

# Use custom configuration file
tfsec --config-file ./custom-tfsec-config.yml

Advanced Features

Terraform Plan Analysis

# Analyze Terraform plan file
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
tfsec --tfplan tfplan.json

# Analyze plan with specific format
tfsec --tfplan tfplan.json --format json --out plan-results.json

Module Analysis

# Scan Terraform modules
tfsec ./modules/

# Scan specific module
tfsec ./modules/vpc/

# Scan with module path context
tfsec --include-paths "modules/**" --exclude-paths "examples/**"

Workspace Analysis

# Analyze specific Terraform workspace
terraform workspace select production
terraform plan -out=prod-plan
terraform show -json prod-plan > prod-plan.json
tfsec --tfplan prod-plan.json --out prod-security-report.json

Integration with Terraform Cloud

# Download plan from Terraform Cloud
# (requires API token and run ID)
curl -H "Authorization: Bearer $TFC_TOKEN" \
     -H "Content-Type: application/vnd.api+json" \
     "https://app.terraform.io/api/v2/runs/$RUN_ID/plan" \
     | jq -r '.data.attributes.log-read-url' \
     | xargs curl -o plan.json

tfsec --tfplan plan.json

Reporting and Metrics

Detailed Reporting

# Generate comprehensive report
tfsec --format json --include-passed --out detailed-report.json

# Generate SARIF report for security tools
tfsec --format sarif --out security-report.sarif

# Generate multiple formats
tfsec --format json --out results.json
tfsec --format html --out report.html
tfsec --format csv --out metrics.csv

Metrics Collection

# Extract metrics from JSON report
jq '.results | group_by(.severity) | map({severity: .[0].severity, count: length})' results.json

# Count issues by provider
jq '.results | group_by(.rule.provider) | map({provider: .[0].rule.provider, count: length})' results.json

# Get top failing checks
jq '.results | group_by(.rule.id) | map({check: .[0].rule.id, count: length}) | sort_by(.count) | reverse | .[0:10]' results.json

Dashboard Integration

# Python script to parse TFSec results
import json
import matplotlib.pyplot as plt

def analyze_tfsec_results(results_file):
    with open(results_file, 'r') as f:
        data = json.load(f)

    # Count by severity
    severity_counts = {}
    for result in data['results']:
        severity = result['severity']
        severity_counts[severity] = severity_counts.get(severity, 0) + 1

    # Create visualization
    plt.figure(figsize=(10, 6))
    plt.bar(severity_counts.keys(), severity_counts.values())
    plt.title('Security Issues by Severity')
    plt.xlabel('Severity')
    plt.ylabel('Count')
    plt.savefig('tfsec-dashboard.png')

    return severity_counts

# Usage
results = analyze_tfsec_results('results.json')
print(f"Security issues found: {results}")

Performance Optimization

Large Codebase Optimization

# Optimize for large codebases
tfsec --concurrency-limit 10 --exclude-paths "**/.terraform/**"

# Scan specific directories only
tfsec ./environments/production/

# Use include patterns for focused scanning
tfsec --include-paths "**/*.tf" --exclude-paths "**/examples/**"

Caching and Incremental Scans

# Cache results for faster subsequent scans
export TFSEC_CACHE_DIR=~/.tfsec-cache
tfsec --cache-dir ~/.tfsec-cache

# Incremental scanning (scan only changed files)
# Get changed files from git
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD | grep '\.tf$' | tr '\n' ' ')
if [ -n "$CHANGED_FILES" ]; then
    tfsec $CHANGED_FILES
fi

Parallel Processing

# Run multiple TFSec instances in parallel
find . -name "*.tf" -type d | \
    xargs -I {} -P 4 tfsec --format json --out {}/tfsec-results.json {}

# Combine results
jq -s 'add' **/tfsec-results.json > combined-results.json

Troubleshooting

Common Issues

# Debug mode for troubleshooting
tfsec --debug

# Verbose output
tfsec --verbose

# Check version and supported providers
tfsec --version

# Validate configuration file
tfsec --config-file tfsec.yml --debug

Memory and Performance Issues

# Increase memory limit for large projects
export GOMAXPROCS=4
export GOGC=100

# Reduce memory usage
tfsec --concurrency-limit 2 --exclude-paths "**/.terraform/**"

# Profile memory usage
tfsec --profile-memory --out memory-profile.prof

False Positives

# Handle false positives with suppressions
# Use inline comments for specific cases
#tfsec:ignore:AWS001:This is a false positive because...

# Update configuration to exclude problematic checks
# .tfsec.yml
exclude_checks:
  - AWS001  # False positive in our use case

# Report false positives to TFSec team
# Create issue at: https://github.com/aquasecurity/tfsec/issues

Migration to Trivy

Trivy Integration

# TFSec is now part of Trivy
# Install Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# Scan with Trivy (includes TFSec functionality)
trivy config .

# Use TFSec-compatible options
trivy config --format json --output results.json .

Migration Commands

# Old TFSec command
tfsec --format json --out results.json

# New Trivy equivalent
trivy config --format json --output results.json .

# Migrate configuration
# .tfsec.yml becomes .trivyignore or trivy.yaml

Best Practices

Security Scanning Strategy

# 1. Start with baseline scan
tfsec --format json --out baseline.json

# 2. Implement in CI/CD with soft fail initially
tfsec --soft-fail

# 3. Gradually reduce acceptable risk levels
tfsec --minimum-severity HIGH

# 4. Regular security reviews
tfsec --format html --out weekly-report.html

Team Adoption

# Developer workflow:
# 1. Pre-commit hooks
echo '#!/bin/bash
tfsec --minimum-severity HIGH
' > .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

# 2. IDE integration
# Install TFSec extension for VS Code or other IDEs

# 3. Regular training
# Schedule security awareness sessions
# Share common vulnerability patterns
# Review security findings in team meetings

Configuration Management

# Version control all configuration
git add .tfsec.yml
git commit -m "Add TFSec security scanning configuration"

# Environment-specific configurations
# .tfsec-dev.yml - Development environment
# .tfsec-prod.yml - Production environment

# Use appropriate config per environment
tfsec --config-file .tfsec-${ENVIRONMENT}.yml

Resources

Documentation

Community

Training