Security as Code Prevenindo Falhas Antes do Deploy

Em ambientes de nuvem, a velocidade dos deploys cresce continuamente. A cada sprint, releases se tornam mais frequentes, automatizadas e distribuídas. No entanto, essa agilidade traz um efeito colateral perigoso: erros de configuração podem ir para produção em minutos, abrindo portas para incidentes de segurança que poderiam ter sido evitados.

Times de desenvolvimento e infraestrutura operam hoje em modelos altamente ágeis: CI/CD, infra como código, microserviços.
Mas a segurança nem sempre acompanha esse ritmo.

Modelos tradicionais dependem de revisões manuais no final do ciclo, quando tudo já está pronto ou até mesmo em produção. E aí surgem dois problemas:

  • Custo de correção mais alto
  • Risco de incidentes devido a erros que poderiam ter sido barrados antes

Security as Code resolve esse gargalo ao trazer a segurança para o início do ciclo, automatizando verificações e prevenindo erros antes mesmo de chegar ao deploy.

O caso do bucket exposto

Todos os anos, vemos incidentes praticamente idênticos envolvendo buckets S3 públicos ou mal configurados. Alguns exemplos conhecidos:

  • mcGraw Hill – milhões de registros de estudantes expostos
  • Premier Diagnostics – dados sensíveis acessíveis publicamente
  • Raindeer – informações pessoais deixadas em buckets sem proteção

O bucket não estava criptografado, não tinha autenticação adequada e estava acessível publicamente. O pior? Isso poderia ter sido evitado com uma simples verificação automatizada no código Terraform ou através de remediações automáticas através do AWS Config.

Esse tipo de falha não é exclusividade de grandes empresas. Qualquer organização que não tenha práticas de segurança automatizadas corre o mesmo risco. A diferença entre o desastre e a prevenção está em colocar a segurança no início do ciclo de desenvolvimento e é aí que entra o Security as Code.

O que é Security as Code

Security as Code é a prática de definir, versionar e automatizar políticas de segurança no mesmo fluxo de desenvolvimento e entrega de código.
Ao invés de revisar manualmente configurações depois que o ambiente está no ar, as regras são aplicadas e validadas antes do deploy.

Objetivos principais

  1. Prevenção proativa: evitar que configurações inseguras sejam criadas.
  2. Consistência: todos os ambientes seguem os mesmos padrões.
  3. Velocidade: segurança integrada ao pipeline, sem gargalos manuais.
  4. Compliance contínuo: auditoria facilitada com relatórios e histórico.

Security as Code no ciclo DevSecOps

O DevSecOps coloca a segurança como responsabilidade compartilhada entre desenvolvedores, operações e segurança. O Security as Code é o motor dessa abordagem, ele garante que as regras de segurança não fiquem “guardadas” em documentos, mas vivam no código e pipelines.

Fluxo simplificado:

  1. Desenvolvedor escreve código de infraestrutura.
  2. Pipeline executa verificações de segurança automaticamente.
  3. Se violar as regras → pipeline falha.
  4. Se passar → deploy autorizado.

Ferramentas populares de Security as Code

Ferramenta Foco principal Pontos fortes
Checkov IaC (Terraform, CloudFormation, K8s) Open source, fácil de integrar, centenas de checks prontos.
OPA / Rego Políticas genéricas Muito flexível, pode validar qualquer formato de dado, mas requer curva de aprendizado.
Terraform Cloud Sentinel Políticas para Terraform Integração nativa com Terraform Enterprise/Cloud, mas proprietário.
CloudFormation Guard Templates AWS CloudFormation Focado em AWS, fácil para quem já usa CloudFormation.
Conftest Políticas em YAML/JSON Simples, versátil, ideal para pipelines leves.

Estratégia do hands-on

Vamos criar alguns buckets no s3 via Terraform e implementar políticas que bloqueiem qualquer recurso s3 de estarem publicamente acessíveis e validar se outras features como versionamento esta habilitado. Isso será integrado à pipeline localmente, mas a mesma lógica pode ser aplicada no GitHub Actions, GitLab CI, etc.

Cenário 1: Bloqueio de buckets públicos na conta

Arquivo block_public_access.tf:

provider "aws" {
  region = "us-east-1"
}

resource "aws_s3_account_public_access_block" "account_level" {
  block_public_acls       = true
  ignore_public_acls      = true
  block_public_policy     = true
  restrict_public_buckets = true
}

Vamos agora testar criar um bucket com public-read, a tentativa de aplicar public-read deve falhar, mostrando que o bloqueio está ativo.

aws s3api create-bucket --bucket bucket-teste-publico --region us-east-1
aws s3api put-bucket-acl --bucket bucket-teste-publico --acl public-read

Cenário 2: Política como código com Checkov

Arquivo insecure.tf: (inseguro propositalmente):

resource "aws_s3_bucket" "insecure" {
  bucket = "bucket-sem-seguranca-${random_id.suffix.hex}"
}

resource "random_id" "suffix" {
  byte_length = 4
}

Criando política customizada do Checkov

Arquivo s3_policy.yaml:

metadata:
  name: "S3 Buckets devem ter versionamento habilitado"
  id: "CUSTOM_AWS_S3_1"
  category: "Security"
scope:
  provider: aws
definition:
  and:
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: versioning
      operator: exists

Execute o checkov com a política customizada:

checkov -d . --external-checks-dir .

Saída esperada:

FAILED for resource: aws_s3_bucket.insecure

Corrigindo

Atualize o insecure.tf:

resource "aws_s3_bucket" "secure" {
  bucket = "bucket-seguro-${random_id.suffix.hex}"

  versioning {
    enabled = true
  }
}

resource "aws_s3_bucket" "log_bucket" {
  bucket = "bucket-de-logs-${random_id.log_suffix.hex}"
}

resource "random_id" "log_suffix" {
  byte_length = 4
}

Agora é só executar o scan novamente e agora ele deve retornar sucesso:

checkov -d . --external-checks-dir .

Auto-remediação com AWS Config

Em ambientes dinâmicos, com múltiplas contas, times diferentes e centenas de recursos sendo criados diariamente, um risco adicional surge: o drift, quando um recurso muda depois do deploy e foge dos padrões aprovados.

É aqui que entra o AWS Config, um dos serviços mais poderosos da AWS para quem deseja segurança contínua e governança automatizada, ele permite:

  • Monitorar recursos em tempo real
  • Registrar todo o histórico de alterações
  • Avaliar se cada recurso está em conformidade com as regras da empresa
  • Gerar alertas
  • Principalmente, aplicar auto-remediação quando algo estiver fora do padrão

Ou seja:

Se um desvio acontecer, ele será corrigido automaticamente sem intervenção humana.

Isso reduz risco operacional, diminui o tempo de exposição e aumenta a maturidade de segurança da organização.

Como funciona o ciclo de auto-remediação

1.Regra do AWS Config monitora um recurso e avalia se ele está em conformidade. 2. Se a avaliação for NON_COMPLIANT, a regra pode acionar:

  • Um SSM Automation Runbook, ou
  • Uma Lambda Function
  1. O runbook/função executa a correção e retorna o recurso ao estado desejado.
  2. AWS Config reavalia o recurso e confirma a conformidade.

Garantir que grupos de segurança não permitam tráfego 0.0.0.0/0 na porta 22

Mesmo em ambientes maduros, um dos problemas mais recorrentes é a abertura acidental de portas perigosas para a internet especialmente SSH. Isso representa risco de ataques de força bruta e exposição desnecessária do servidor.

Objetivo da auto-remediação

  • Detectar automaticamente qualquer Security Group que permita acesso SSH de qualquer origem pública.
  • Executar uma ação de correção removendo a regra insegura.

1. Regra do AWS Config

Usamos a regra gerenciada: INCOMING_SSH_DISABLED Ela verifica se existe alguma regra de inbound SSH aberta para 0.0.0.0/0.

2. Ação de remediação

Podemos anexar um SSM Automation Runbook que remove automaticamente a regra insegura.

Exemplo de runbook pronto da AWS: AWS-DisablePublicAccessForSecurityGroup ele:

  • localiza regras de entrada perigosas
  • remove apenas aquelas que estão fora da conformidade
  • mantém outras regras intactas

Fluxo da auto-remediação

  1. Alguém cria ou altera um Security Group e adiciona a regra:
Inbound
Porta: `22`
Origem: `0.0.0.0/0`
  1. AWS Config detecta a regra e marca como NON_COMPLIANT
  2. O runbook é acionado automaticamente
  3. A regra insegura é removida em segundos
  4. O recurso volta ao estado COMPLIANT

Métricas e ROI: O impacto real do Security as Code

Implementar Security as Code não é apenas uma questão de segurança, é também um investimento com retorno mensurável. Organizações que adotam essas práticas reportam:

Redução de vulnerabilidades

  • 70-80% menos vulnerabilidades críticas chegando a produção (fonte: estudos de DevSecOps da Gartner)
  • Tempo médio de detecção (MTTD) reduzido de dias para minutos
  • Tempo médio de remediação (MTTR) reduzido em até 60%

Economia de tempo e recursos

  • Revisões manuais de segurança: de 2-3 dias para execução automatizada em minutos
  • Custo de correção: corrigir na fase de desenvolvimento custa 10x menos que em produção
  • Horas de engenharia: times economizam 15-20 horas/semana em revisões manuais

Impacto financeiro

Considere o custo médio de um incidente de segurança:

  • Pequenas empresas: $120k - $1.2M por incidente
  • Médias empresas: $1.2M - $8.5M por incidente
  • Grandes empresas: $8.5M+ por incidente

Um único bucket S3 exposto pode resultar em:

  • Multas regulatórias (LGPD, GDPR)
  • Perda de confiança de clientes
  • Custos de resposta a incidentes
  • Danos à reputação

ROI típico: Para cada $1 investido em Security as Code, empresas economizam $5-10 em custos de remediação e incidentes evitados.

Integração com CI/CD: GitHub Actions e GitLab CI

Vamos ver como integrar o Checkov em pipelines reais de CI/CD.

GitHub Actions

Crie o arquivo .github/workflows/security-scan.yml:

name: Security Scan

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

jobs:
  checkov:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Run Checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: terraform/
          framework: terraform
          output_format: cli,sarif
          output_file_path: console,results.sarif
          soft_fail: false  # Falha o pipeline se encontrar problemas
          
      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v2
        if: always()
        with:
          sarif_file: results.sarif

      - name: Comment PR with results
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        with:
          script: |
            const fs = require('fs');
            const results = fs.readFileSync('results.sarif', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '## Security Scan Results\n\n' + results
            });

GitLab CI

Crie o arquivo .gitlab-ci.yml:

stages:
  - security
  - deploy

security_scan:
  stage: security
  image: bridgecrew/checkov:latest
  script:
    - checkov -d terraform/ --output cli --output json --output-file-path console,checkov-report.json
  artifacts:
    reports:
      sast: checkov-report.json
    paths:
      - checkov-report.json
    expire_in: 1 week
  allow_failure: false  # Bloqueia o pipeline se falhar

deploy:
  stage: deploy
  script:
    - terraform apply -auto-approve
  only:
    - main
  needs:
    - security_scan

Output real do Checkov

Quando você executa o Checkov, a saída é bem detalhada:

$ checkov -d terraform/

       _               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   < (_) \ V / 
  \___|_| |_|\___|\___|_|\_\___/ \_/  
                                      
By bridgecrew.io | version: 2.3.187

terraform scan results:

Passed checks: 12, Failed checks: 3, Skipped checks: 0

Check: CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"
	FAILED for resource: aws_s3_bucket.data
	File: /main.tf:15-18
	Guide: https://docs.bridgecrew.io/docs/s3_13-enable-logging

		15 | resource "aws_s3_bucket" "data" {
		16 |   bucket = "my-data-bucket"
		17 |   acl    = "private"
		18 | }

Check: CKV_AWS_19: "Ensure the S3 bucket has server-side encryption enabled"
	FAILED for resource: aws_s3_bucket.data
	File: /main.tf:15-18

Check: CKV_AWS_21: "Ensure the S3 bucket has versioning enabled"
	FAILED for resource: aws_s3_bucket.data
	File: /main.tf:15-18

Comparação: Checkov vs OPA/Rego

Vamos ver a mesma política implementada em ambas as ferramentas.

Checkov (YAML)

metadata:
  name: "S3 deve ter encryption e versioning"
  id: "CUSTOM_S3_SECURE"
  category: "Security"
scope:
  provider: aws
definition:
  and:
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: versioning.enabled
      operator: equals
      value: true
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: server_side_encryption_configuration
      operator: exists

OPA/Rego

package terraform.s3

import input as tfplan

deny[msg] {
    resource := tfplan.resource_changes[_]
    resource.type == "aws_s3_bucket"
    
    not resource.change.after.versioning[_].enabled
    
    msg := sprintf(
        "S3 bucket '%s' deve ter versionamento habilitado",
        [resource.address]
    )
}

deny[msg] {
    resource := tfplan.resource_changes[_]
    resource.type == "aws_s3_bucket"
    
    not resource.change.after.server_side_encryption_configuration
    
    msg := sprintf(
        "S3 bucket '%s' deve ter encryption habilitado",
        [resource.address]
    )
}

Quando usar cada um:

  • Checkov: Mais simples, ideal para começar, centenas de checks prontos
  • OPA/Rego: Mais flexível, ideal para políticas complexas e customizadas

Troubleshooting e boas práticas

Falsos positivos comuns

Problema 1: Buckets de logs não precisam de versionamento

# Solução: Criar exceção por tag
metadata:
  name: "S3 versioning exceto logs"
  id: "CUSTOM_S3_VERSIONING"
scope:
  provider: aws
definition:
  and:
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: versioning.enabled
      operator: equals
      value: true
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: tags.Purpose
      operator: not_equals
      value: "logs"

Problema 2: Recursos legados que não podem ser alterados

Use o arquivo .checkov.baseline para suprimir checks específicos:

# .checkov.baseline
{
  "checks": {
    "CKV_AWS_18": {
      "skip": [
        {
          "id": "aws_s3_bucket.legacy_bucket",
          "reason": "Bucket legado, migração planejada para Q2 2025"
        }
      ]
    }
  }
}

Debugging de políticas customizadas

Use o modo verbose para entender por que uma política falhou:

checkov -d . --external-checks-dir policies/ -v

Teste políticas isoladamente:

# Testar apenas uma política específica
checkov -d . --check CUSTOM_S3_SECURE

Estrutura de repositório recomendada

.
├── .github/
│   └── workflows/
│       └── security-scan.yml
├── terraform/
│   ├── modules/
│   │   ├── s3/
│   │   ├── ec2/
│   │   └── vpc/
│   ├── environments/
│   │   ├── dev/
│   │   ├── staging/
│   │   └── prod/
│   └── main.tf
├── policies/
│   ├── s3/
│   │   ├── encryption.yaml
│   │   ├── versioning.yaml
│   │   └── public-access.yaml
│   ├── iam/
│   │   └── least-privilege.yaml
│   └── ec2/
│       └── security-groups.yaml
├── .checkov.baseline
└── README.md

Casos de uso avançados

1. Validação de IAM Policies

Política Checkov para IAM:

metadata:
  name: "IAM roles não devem ter wildcard em actions"
  id: "CUSTOM_IAM_NO_WILDCARD"
  category: "IAM"
scope:
  provider: aws
definition:
  cond_type: attribute
  resource_types:
    - aws_iam_role_policy
    - aws_iam_policy
  attribute: policy
  operator: not_contains
  value: '"Action": "*"'

2. Encryption at Rest obrigatória

Para RDS:

metadata:
  name: "RDS deve ter encryption at rest"
  id: "CUSTOM_RDS_ENCRYPTION"
scope:
  provider: aws
definition:
  cond_type: attribute
  resource_types:
    - aws_db_instance
  attribute: storage_encrypted
  operator: equals
  value: true

Para EBS:

metadata:
  name: "EBS volumes devem ser criptografados"
  id: "CUSTOM_EBS_ENCRYPTION"
scope:
  provider: aws
definition:
  cond_type: attribute
  resource_types:
    - aws_ebs_volume
  attribute: encrypted
  operator: equals
  value: true

3. Compliance com frameworks

PCI-DSS Requirement 3.4 (Encryption):

metadata:
  name: "PCI-DSS 3.4: Dados sensíveis devem ser criptografados"
  id: "PCI_DSS_3_4"
  category: "Compliance"
  guidelines: "PCI-DSS v3.2.1"
scope:
  provider: aws
definition:
  or:
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: server_side_encryption_configuration
      operator: exists
    - cond_type: attribute
      resource_types:
        - aws_db_instance
      attribute: storage_encrypted
      operator: equals
      value: true

HIPAA Security Rule (Encryption):

metadata:
  name: "HIPAA: PHI deve ser criptografado em repouso"
  id: "HIPAA_ENCRYPTION"
  category: "Compliance"
  guidelines: "HIPAA Security Rule § 164.312(a)(2)(iv)"
scope:
  provider: aws
definition:
  and:
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: tags.DataClassification
      operator: equals
      value: "PHI"
    - cond_type: attribute
      resource_types:
        - aws_s3_bucket
      attribute: server_side_encryption_configuration
      operator: exists

Integração com SIEM e Observabilidade

Enviando resultados para Datadog

# send_to_datadog.py
import json
import requests
import os

def send_checkov_to_datadog(report_file):
    with open(report_file) as f:
        results = json.load(f)
    
    api_key = os.getenv('DD_API_KEY')
    
    for check in results['results']['failed_checks']:
        event = {
            'title': f"Security Check Failed: {check['check_id']}",
            'text': check['check_name'],
            'alert_type': 'error',
            'tags': [
                f"resource:{check['resource']}",
                f"check_id:{check['check_id']}",
                'source:checkov',
                'security:vulnerability'
            ]
        }
        
        requests.post(
            'https://api.datadoghq.com/api/v1/events',
            headers={'DD-API-KEY': api_key},
            json=event
        )

if __name__ == '__main__':
    send_checkov_to_datadog('checkov-report.json')

Dashboard de Compliance

Crie métricas customizadas no Datadog:

# datadog-dashboard.json
{
  "title": "Security as Code - Compliance Dashboard",
  "widgets": [
    {
      "definition": {
        "type": "timeseries",
        "requests": [
          {
            "q": "sum:checkov.failed_checks{*} by {severity}",
            "display_type": "bars"
          }
        ],
        "title": "Failed Security Checks por Severidade"
      }
    },
    {
      "definition": {
        "type": "query_value",
        "requests": [
          {
            "q": "sum:checkov.compliance_score{*}",
            "aggregator": "avg"
          }
        ],
        "title": "Compliance Score (%)",
        "precision": 2
      }
    }
  ]
}

Alertas proativos

Configure alertas no Datadog para notificar quando:

# datadog-monitor.yaml
name: "Critical Security Checks Failed"
type: metric alert
query: "sum(last_5m):sum:checkov.failed_checks{severity:critical} > 0"
message: |
  {{#is_alert}}
  Checks de segurança críticos falharam!
  
  Recursos afetados: {{value}}
  
  Ação necessária: Revisar o PR e corrigir antes do merge.
  {{/is_alert}}
tags:
  - security
  - critical
  - checkov

Governança de políticas

Quem aprova mudanças nas políticas?

Estabeleça um processo claro:

  1. Proposta: Qualquer membro do time pode propor uma nova política
  2. Revisão técnica: Time de segurança valida a política
  3. Teste: Política é testada em ambiente de dev/staging
  4. Aprovação: Requer aprovação de Security Lead + Engineering Manager
  5. Rollout gradual: Implementar primeiro como warning, depois como bloqueio

CODEOWNERS para políticas

# .github/CODEOWNERS
/policies/                    @security-team
/policies/iam/*              @security-team @iam-admins
/policies/network/*          @security-team @network-team
/.checkov.baseline           @security-team @engineering-managers

Versionamento de políticas

Use tags semânticas:

git tag -a policies-v1.2.0 -m "Adiciona validação de encryption para RDS"
git push origin policies-v1.2.0

Próximos passos

Se você chegou até aqui, já tem todo o conhecimento necessário para começar. Agora é hora de agir:

Para começar hoje:

  1. Instale o Checkov: pip install checkov
  2. Rode um scan no seu código: checkov -d terraform/
  3. Analise os resultados: identifique os 3 problemas mais críticos
  4. Corrija um por vez: comece pequeno, ganhe confiança
  5. Automatize: adicione ao seu pipeline de CI/CD

Recursos adicionais:

Conclusão

Security as Code não é um luxo e nem uma tendência: é um pilar obrigatório para empresas que querem operar em nuvem com segurança e velocidade.

A diferença entre organizações que sofrem incidentes de segurança e aquelas que os previnem está na capacidade de automatizar, validar e remediar problemas antes que cheguem à produção.

Começar pode parecer intimidador, mas lembre-se: você não precisa implementar tudo de uma vez. Comece com os checks mais críticos, ganhe experiência, e expanda gradualmente.

O importante é dar o primeiro passo hoje. Seu futuro eu (e seu time de segurança) vão agradecer.

Comentários