Infrastructure as Code security: scanning de Terraform e CloudFormation

1. Por que Segurança em IaC é Crítica para Devs

A adoção de Infrastructure as Code transformou a forma como provisionamos recursos em nuvem. Com GitOps, qualquer alteração na infraestrutura passa por pull requests, revisões e merge — exatamente como código de aplicação. No entanto, esse mesmo fluxo introduz um risco silencioso: a “misconfiguration as code”. Um bucket S3 configurado como público em um arquivo Terraform pode ser deployado para centenas de ambientes antes que alguém perceba.

Diferente de vulnerabilidades de aplicação (como SQL injection), falhas de infraestrutura costumam ser mais difíceis de detectar em tempo de execução. Uma política IAM com Action: "*" e Resource: "*" não gera erro — ela simplesmente funciona, dando acesso total a quem não deveria ter. As consequências são reais: exposição massiva de dados, cryptojacking em instâncias EC2 mal configuradas e movimentação lateral para outros serviços da conta.

2. Principais Ameaças em Templates Terraform e CloudFormation

Hardcoded Secrets

O erro mais comum entre desenvolvedores é inserir chaves de acesso diretamente nos templates:

resource "aws_iam_user" "deployer" {
  name = "deployer"
}

resource "aws_iam_access_key" "deployer_key" {
  user = aws_iam_user.deployer.name
}

# Perigoso: secret inline no código
output "secret_key" {
  value = aws_iam_access_key.deployer_key.secret
  sensitive = true # Apenas esconde no console, mas está no state file
}

Permissões Excessivas

Políticas muito permissivas são um convite para ataques:

resource "aws_iam_role_policy" "lambda_exec_role" {
  name = "lambda_exec_policy"
  role = aws_iam_role.lambda_exec.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = "*"   # Jamais use Action: "*"
        Resource = "*"   # Jamais use Resource: "*"
      }
    ]
  })
}

Configurações Inseguras por Padrão

Muitos recursos têm defaults inseguros. Buckets públicos, security groups abertos para 0.0.0.0/0 e RDS sem SSL habilitado são exemplos clássicos:

resource "aws_security_group" "web_sg" {
  name = "web_sg"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # SSH aberto para o mundo
  }
}

3. Ferramentas de Scanning Estático para IaC

checkov

Ferramenta da Bridgecrew que analisa mais de 1000 políticas pré-definidas para Terraform, CloudFormation, Kubernetes e outros. Suporta regras customizadas em Rego (OPA).

# Instalação
pip install checkov

# Scanning básico
checkov -d ./terraform --framework terraform

# Saída compacta (apenas falhas)
checkov -d ./terraform --compact

tfsec / trivy

tfsec é focado exclusivamente em Terraform, com regras específicas para provedores AWS, Azure e GCP. O Trivy (da Aqua Security) unificou o tfsec em sua ferramenta, oferecendo scanning de IaC, containers e dependências.

# Com tfsec (legado)
tfsec ./terraform

# Com trivy (recomendado)
trivy config ./terraform

cfn-lint e cfn-nag

Para CloudFormation, o cfn-lint valida a sintaxe do template, enquanto o cfn-nag busca padrões inseguros:

# cfn-lint
cfn-lint template.yaml

# cfn-nag
cfn_nag_scan --input-path template.yaml

Integração CI/CD

GitHub Actions com checkov:

name: IaC Security Scan
on: [pull_request]

jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run Checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: terraform/
          framework: terraform
          soft_fail: false
          output_format: cli

4. Políticas Customizadas e Compliance como Código

Regras Rego no checkov

Crie um diretório custom_policies/ com arquivos .rego:

# custom_policies/block_public_s3.rego
package main

deny[msg] {
  resource := input.resource.aws_s3_bucket[_]
  resource.config.block_public_access == null
  msg := sprintf("Bucket %s não possui block_public_access configurado", [resource.name])
}

Execute com:

checkov -d ./terraform --external-checks-dir ./custom_policies

Mapeamento para Frameworks

O checkov já inclui mapeamentos para CIS Benchmarks, SOC2, PCI-DSS e HIPAA. Use a flag --framework para filtrar:

checkov -d ./terraform --framework terraform --check CKV_AWS_53  # CIS 2.1.1

Exemplo: Bloquear Deploy sem block_public_access

No pipeline, adicione um step que falhe se o bucket não tiver a configuração:

- name: Check S3 Public Access
  run: |
    checkov -d terraform/ --check CKV_AWS_53 --compact --quiet
    if [ $? -ne 0 ]; then
      echo "Bucket sem block_public_access. Corrija antes de merge."
      exit 1
    fi

5. Prevenção de Drift e Segurança Contínua

Detecção em Pull Requests

Configure gatilhos que executem scanning em cada PR:

name: PR Security Gate

on:
  pull_request:
    paths:
      - 'terraform/**'

jobs:
  tf-plan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Terraform Plan
        run: |
          cd terraform
          terraform init
          terraform plan -out=tfplan
      - name: Checkov on Plan
        run: |
          checkov -f terraform/tfplan --framework terraform

Monitoramento Pós-Deploy

Use ferramentas como AWS Config ou Terraform Cloud Drift Detection para comparar o estado real com o template aprovado:

# AWS Config rule para detectar buckets públicos
resource "aws_config_config_rule" "s3_bucket_public_read_prohibited" {
  name = "s3-bucket-public-read-prohibited"

  source {
    owner             = "AWS"
    source_identifier = "S3_BUCKET_PUBLIC_READ_PROHIBITED"
  }
}

6. Boas Práticas para Devs no Dia a Dia

Use Variáveis, Nunca Literais

# Ruim
resource "aws_db_instance" "db" {
  master_password = "MinhaSenhaForte123!"
}

# Bom
variable "db_password" {
  type      = string
  sensitive = true
}

resource "aws_db_instance" "db" {
  master_password = var.db_password
}

Menor Privilégio

Sempre restrinja ações e recursos ao mínimo necessário:

resource "aws_iam_policy" "lambda_policy" {
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["s3:GetObject", "s3:ListBucket"]
        Resource = ["arn:aws:s3:::meu-bucket-app/*", "arn:aws:s3:::meu-bucket-app"]
      }
    ]
  })
}

Versionamento e Dependências

Use módulos de fontes confiáveis (Terraform Registry oficial) e fixe versões:

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.0.0"  # Versão fixa, não latest
}

Documente Exceções

Quando uma regra de segurança precisa ser suprimida, documente no código:

# checkov:skip=CKV_AWS_53: Bucket usado para assets públicos intencionais
resource "aws_s3_bucket" "public_assets" {
  bucket = "meu-site-assets"
  acl    = "public-read"
}

7. Exemplo Prático: Pipeline Seguro com Terraform + checkov

Estrutura do Repositório

terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── .checkov.yml
└── custom_policies/
    └── block_public_s3.rego

Arquivo .checkov.yml

compact: true
quiet: true
skip-check:
  - CKV_AWS_18  # Exceção aprovada: bucket de logs
output-format: cli

Pipeline GitHub Actions Completo

name: IaC Security Pipeline

on:
  pull_request:
    branches: [main]
    paths:
      - 'terraform/**'

jobs:
  security-scan:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_version: 1.5.0

      - name: Terraform Init
        run: terraform init
        working-directory: ./terraform

      - name: Terraform Validate
        run: terraform validate
        working-directory: ./terraform

      - name: Install Checkov
        run: pip install checkov

      - name: Run Checkov Scan
        id: checkov
        run: |
          checkov -d ./terraform \
            --framework terraform \
            --external-checks-dir ./terraform/custom_policies \
            --config-file ./terraform/.checkov.yml \
            --output json > checkov_report.json

          # Falha se encontrar erros críticos
          CRITICAL=$(jq '.summary.failed | length' checkov_report.json)
          if [ "$CRITICAL" -gt 0 ]; then
            echo "Falhas críticas encontradas: $CRITICAL"
            cat checkov_report.json | jq '.results.failed_checks[] | {resource, check_id, severity}'
            exit 1
          fi

      - name: Upload Report
        uses: actions/upload-artifact@v3
        with:
          name: checkov-report
          path: checkov_report.json

Saída Esperada

Quando o scanning encontra falhas críticas, o pipeline bloqueia o merge:

Falhas críticas encontradas: 2

{
  "resource": "aws_s3_bucket.public_assets",
  "check_id": "CKV_AWS_53",
  "severity": "CRITICAL"
}
{
  "resource": "aws_security_group.web_sg",
  "check_id": "CKV_AWS_260",
  "severity": "CRITICAL"
}

Referências

  • Checkov Documentation — Documentação oficial da ferramenta de scanning estático para IaC, com exemplos de políticas e integrações.
  • tfsec GitHub Repository — Repositório oficial do tfsec (agora parte do Trivy) com regras de segurança para Terraform.
  • AWS cfn-nag GitHub — Ferramenta da Stelligent para análise de segurança em templates CloudFormation, com mais de 200 regras.
  • Trivy IaC Scanning Guide — Guia oficial do Trivy para scanning de configurações IaC, incluindo Terraform e CloudFormation.
  • CIS AWS Foundations Benchmark — Benchmark de segurança da CIS para AWS, com recomendações mapeáveis para políticas de IaC.
  • Bridgecrew OPA Policies — Repositório com políticas Rego customizáveis para checkov, abrangendo centenas de recursos AWS.
  • Terraform Security Best Practices — Tutoriais oficiais da HashiCorp sobre segurança em Terraform, incluindo gerenciamento de secrets e políticas.