Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Implementar detector de secrets hardcoded via regex (senhas, API keys, URLs com credenciais)
Implementar scanner de IaC que identifica recursos com configurações inseguras
Completar gerar_relatorio_compliance() com seções SOC2 e ISO27001
Usar Claude para análise contextualizada e plano de remediação priorizado
Entender as categorias de controles SOC2 e ISO27001 relevantes para IaC
Por que isso importa
Secrets hardcoded são a causa #1 de brechas de segurança em repositórios de código. Em 2023, o GitHub reportou que sua ferramenta de detecção de secrets bloqueou mais de 1 milhão de commits com credenciais expostas. Uma única DATABASE_URL com senha em texto no código pode comprometer um banco de dados inteiro.
Para compliance SOC2 e ISO27001, relatórios de segurança são mandatórios em auditorias. Um scanner automatizado que gera esses relatórios reduz semanas de trabalho manual de auditoria para minutos.
Conceitos Fundamentais
Detector de Secrets via Regex
PADROES_SECRETS = [
(r'(?i)(password|passwd|pwd)\s*=\s*["\']([^"\']{8,})["\']', 'PASSWORD'),
(r'(?i)(secret|api_key|access_key)\s*=\s*["\']([^"\']{10,})["\']', 'SECRET/API_KEY'),
(r'sk-ant-api\d+-[a-zA-Z0-9]+', 'ANTHROPIC_API_KEY'),
(r'AKIA[0-9A-Z]{16}', 'AWS_ACCESS_KEY'),
(r'postgresql://[^@]+:[^@]+@[^/]+', 'DATABASE_URL_COM_CREDENCIAIS'),
]
def detectar_secrets(codigo: str) -> list[dict]:
encontrados = []
for padrao, tipo in PADROES_SECRETS:
for match in re.finditer(padrao, codigo):
linha = codigo[:match.start()].count('\n') + 1
encontrados.append({
'tipo': tipo,
'linha': linha,
'trecho': match.group(0)[:50] + '...',
'severidade': 'CRITICO',
})
return encontrados
re.finditer retorna todos os matches — não apenas o primeiro. codigo[:match.start()].count('\n') + 1 conta linhas até o início do match para reportar a linha exata.Scanner de IaC
CHECKLIST_SEGURANCA = [
('S3 público', r'acl\s*=\s*["\']public', 'CRITICO', 'Remover ACL pública'),
('SSH aberto', r'from_port\s*=\s*22.*cidr_blocks.*0\.0\.0\.0/0', 'CRITICO', 'Restringir SSH a IPs corporativos'),
('RDS público', r'publicly_accessible\s*=\s*true', 'ALTO', 'Colocar RDS em subnet privada'),
('Sem encryption', r'resource.*aws_db_instance(?!.*encrypted)', 'MEDIO', 'Habilitar encryption at rest'),
('Sem tags', r'resource.*\{(?!.*tags)', 'BAIXO', 'Adicionar tags obrigatórias'),
]
def escanear_iac(codigo: str) -> list[dict]:
problemas = []
for nome, padrao, severidade, remediacao in CHECKLIST_SEGURANCA:
if re.search(padrao, codigo, re.DOTALL | re.IGNORECASE):
problemas.append({'nome': nome, 'severidade': severidade, 'remediacao': remediacao})
return problemas
re.DOTALL faz . matchear \n — essencial para padrões que se espalham por múltiplas linhas no HCL.TODO: gerar_relatorio_compliance(analise, problemas, secrets)
O TODO pede completar o relatório com as categorias SOC2 e ISO27001:
def gerar_relatorio_compliance(analise: str, problemas: list, secrets: list) -> str:
criticos = len([p for p in problemas + secrets if p.get('severidade') == 'CRITICO'])
altos = len([p for p in problemas if p.get('severidade') == 'ALTO'])
medios = len([p for p in problemas if p.get('severidade') == 'MEDIO'])
# Classificação por categoria de compliance
itens_acesso = [p for p in problemas if p['nome'] in ('SSH aberto', 'S3 público', 'RDS público')]
itens_crypto = [p for p in problemas if p['nome'] in ('Sem encryption',)]
itens_secrets_list = [s for s in secrets if s['tipo'] in ('PASSWORD', 'SECRET/API_KEY')]
status_geral = 'REPROVADO' if criticos > 0 else ('AVISO' if altos > 0 else 'APROVADO')
return f'''# Relatório de Compliance — {datetime.now().strftime('%Y-%m-%d')}
## Sumário Executivo
- **Status**: {status_geral}
- Secrets expostos: {len(secrets)}
- Problemas críticos: {criticos} | Altos: {altos} | Médios: {medios}
---
## SOC2 — Critérios Relevantes
### CC6 — Controle de Acesso Lógico
{"✅ Nenhuma violação de controle de acesso" if not itens_acesso else chr(10).join(f"❌ {p['nome']}: {p['remediacao']}" for p in itens_acesso)}
### CC6.7 — Credenciais em Repositórios
{"✅ Nenhuma credencial exposta" if not itens_secrets_list else chr(10).join(f"❌ {s['tipo']} na linha {s.get('linha', '?')}" for s in itens_secrets_list)}
### CC7 — Operações do Sistema
{"✅ Recursos com configurações seguras" if not problemas else chr(10).join(f"⚠️ {p['nome']}: {p['remediacao']}" for p in problemas[:3])}
---
## ISO27001 — Controles Aplicáveis
### A.9 — Controle de Acesso
{"✅ Sem violações detectadas" if not itens_acesso else chr(10).join(f"❌ {p['nome']}: {p['remediacao']}" for p in itens_acesso)}
### A.10 — Criptografia
{"✅ Recursos com encryption configurada" if not itens_crypto else chr(10).join(f"❌ {p['nome']}: {p['remediacao']}" for p in itens_crypto)}
### A.12 — Segurança de Operações
Recursos monitorados: {len(problemas)} verificações realizadas
---
## Plano de Remediação Priorizado
{analise[:600]}
---
## Próximos Passos
1. Rotacionar IMEDIATAMENTE qualquer credencial exposta
2. Aplicar fixes críticos antes do próximo deploy
3. Adicionar pré-commit hook com detect-secrets
4. Revisar e aprovar este relatório com equipe de segurança
'''Aprofundamento Técnico
Categorias SOC2 TC Relevantes para DevOps
Categoria | Sigla | O que verifica |
Logical Access | CC6 | Controle de quem acessa recursos |
System Operations | CC7 | Monitoramento e detecção de anomalias |
Change Management | CC8 | Processos de deploy e aprovação |
Para IaC, CC6 é o mais crítico: recursos sem controle de acesso (S3 público, SSH aberto, RDS acessível) violam diretamente CC6.
ISO27001 Relevante
Controle | Descrição |
A.9.1 | Política de controle de acesso |
A.10.1 | Criptografia em trânsito e em repouso |
A.12.6 | Gestão de vulnerabilidades técnicas |
A.14.2 | Segurança no ciclo de desenvolvimento |
Integração com Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
- repo: https://github.com/bridgecrewio/checkov
rev: v3.0.0
hooks:
- id: checkov
args: ['--quiet', '--framework', 'terraform']Com esses hooks, qualquer commit com secrets ou IaC inseguro é bloqueado antes de entrar no repositório.
Exemplos Anotados
Exemplo 1: Código com Todos os Problemas
CODIGO_INSEGURO = '''
DATABASE_URL = "postgresql://admin:SuperSecret123@prod-db.internal:5432/myapp"
AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
resource "aws_s3_bucket" "data" {
bucket = "company-data-prod"
acl = "public-read" # ← S3 público
}
resource "aws_security_group_rule" "ssh" {
from_port = 22
cidr_blocks = ["0.0.0.0/0"] # ← SSH aberto para o mundo
}
'''
secrets = detectar_secrets(CODIGO_INSEGURO)
# → [{'tipo': 'DATABASE_URL_COM_CREDENCIAIS', 'linha': 2, ...}, {'tipo': 'AWS_SECRET_KEY', ...}]
problemas = escanear_iac(CODIGO_INSEGURO)
# → [{'nome': 'S3 público', 'severidade': 'CRITICO', ...}, {'nome': 'SSH aberto', ...}]
analise = analisar_com_llm(CODIGO_INSEGURO, problemas, secrets)
relatorio = gerar_relatorio_compliance(analise, problemas, secrets)
print(relatorio)Exemplo 2: Relatório Completo com Seções SOC2
# Com o TODO implementado, o relatório inclui:
print(relatorio)
# → # Relatório de Compliance — 2024-01-15
# → ## Sumário Executivo
# → - Status: REPROVADO
# → - Secrets expostos: 3
# → - Problemas críticos: 2 | Altos: 1 | Médios: 1
# → ...
# → ## SOC2 — Critérios Relevantes
# → ### CC6 — Controle de Acesso Lógico
# → ❌ S3 público: Remover ACL pública
# → ❌ SSH aberto: Restringir SSH a IPs corporativosPadrões e Armadilhas
Padrões
Padrão 1: Truncar trecho do secret no relatório
'trecho': match.group(0)[:50] + '...', # não expor o secret completo no logO relatório confirma que um secret foi encontrado, mas não o expõe completo — afinal, o relatório pode ser compartilhado.
Padrão 2: re.DOTALL para padrões multilinha em HCL
re.search(padrao, codigo, re.DOTALL | re.IGNORECASE)
# DOTALL: '.' inclui '\n' → padrões podem cruzar linhas
# IGNORECASE: 'PASSWORD' e 'password' e 'Password' são iguaisPadrão 3: Severidade estruturada para priorização
# Ordenar por severidade antes de exibir
ORDEM = {'CRITICO': 0, 'ALTO': 1, 'MEDIO': 2, 'BAIXO': 3}
problemas.sort(key=lambda p: ORDEM.get(p['severidade'], 4))Armadilhas
⚠️ Armadilha 1: Falsos positivos em nomes de variáveis
# "password_validation" vai matchear o padrão de PASSWORD
# Use palavra delimitadores mais precisos:
r'(?i)\bpassword\b\s*=\s*["\'][^"\']{8,}["\']' # \b = word boundary⚠️ Armadilha 2: Scanner de IaC baseado em string não detecta variáveis
resource "aws_s3_bucket" "data" {
acl = var.bucket_acl # ← "public-read" como variável não é detectado
}Para isso, use checkov ou tfsec que fazem análise estática real do HCL.
⚠️ Armadilha 3: Relatório de compliance sem data e escopo
# Sem data e repositório, o relatório não tem validade legal
f'# Relatório de Compliance — {datetime.now().strftime("%Y-%m-%d")}\n'
f'## Escopo: repositório {repo_url}, branch {branch}'Se não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
1 TODO principal no starter.py:
TODO — gerar_relatorio_compliance(analise, problemas, secrets): Complete as seções [TODO: preencher...] com os dados reais dos parâmetros:
# SOC2 CC6:
itens_acesso = [p for p in problemas if 'público' in p['nome'] or 'SSH' in p['nome']]
for item in itens_acesso:
texto += f"❌ {item['nome']}: {item['remediacao']}\n"
# ISO27001 A.10:
itens_crypto = [p for p in problemas if 'encryption' in p['nome'].lower()]
for item in itens_crypto:
texto += f"❌ {item['nome']}: {item['remediacao']}\n"Rode python starter.py e verifique que o relatório inclui todas as categorias de compliance preenchidas.
Agora você está pronto para o lab.