Lab 48 — Detecção: GuardDuty, Security Hub, Config
O problema, e a empresa que o tem
A Cadência é a mesma equipe de duas pessoas dos laboratórios anteriores, e o L41 já resolveu a role da aplicação que rodava com AdministratorAccess. Só que naquele processo, o Access Analyzer também apontou algo que ninguém tinha ido atrás: a conta tem GuardDuty habilitado desde a criação — vinha ligado por padrão numa landing zone que um consultor configurou — e ele já gerou achados de verdade. Todos foram parar num tópico SNS que manda e-mail para os dois.
Doze dias antes deste laboratório começar, chegou um achado do tipo "CryptoCurrency:EC2/BitcoinTool.B!DNS": uma instância da conta conversando com um domínio conhecido de mineração de criptomoeda. O e-mail chegou numa sexta-feira, no meio do sprint de fechamento de mês, e ficou sem leitura até a terça seguinte — quando alguém abriu por acaso, procurando outro e-mail no mesmo filtro.
A instância não era crítica, e o achado acabou sendo um falso positivo — uma imagem de teste que alguém subiu com uma dependência desatualizada e removeu no mesmo dia. Mas o susto revelou o problema real: a Cadência não sabe dizer, para NENHUM achado passado, se ele foi tratado, ignorado por engano ou está simplesmente esquecido. GuardDuty estar ligado deu a aparência de proteção; não deu proteção nenhuma.
O que este laboratório NÃO é
Não é resposta a incidente nem análise de blast radius — descobrir o que uma credencial vazada alcançou é o L50, e ele consome o ticket que este laboratório cria como ponto de partida. Também não é identidade de workload (L42, já assumido pelo L41) nem agregação multiconta (L43) — Security Hub aqui roda numa única conta, e a agregação entre contas aparece só na seção de evolução em níveis, como o degrau seguinte, não como pré-requisito.
O que você vai conseguir fazer
Objetivos verificáveis: cada um se prova com um comando na seção de implantação, não com a sensação de ter entendido a diferença entre os três serviços.
- Distinguir o papel de GuardDuty (detector de comportamento), Config (avaliador de conformidade) e Security Hub (agregador) sem confundir os três.
- Habilitar os dois detectores e provar, com um achado de teste, que o caminho mínimo (SNS para e-mail) não produz nenhuma ação rastreável.
- Agregar GuardDuty e Config no Security Hub e ler o mesmo achado normalizado em ASFF nos dois lugares.
- Escrever uma regra do EventBridge que casa achado por Severity.Label, e explicar por que ela dispara tanto em achado novo quanto em achado atualizado.
- Calcular o prazo de resposta a partir da severidade, com SLA diferente por nível — não um valor único para qualquer achado.
- Distinguir remediação automatizável de ação irreversível, e implementar o freio de aprovação humana para a segunda.
- Medir, num painel, quantos tickets cumpriram o prazo e quantos estouraram, por severidade.
- Diagnosticar por que um achado que teve a severidade escalada não gerou ticket novo, e corrigir a causa na função.
- Explicar por que uma regra do Config avaliada só por gatilho periódico deixa uma janela de até 24 horas sem detectar configuração fora de conformidade.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Detecção vs. conformidade vs. agregação | SOA-C02, SAP-C02 | os três papéis distintos e como se conectam | por que Security Hub não detecta nada sozinho |
| Formato ASFF | SOA-C02, SAP-C02 | achado normalizado antes de virar evento | o que o formato padroniza entre GuardDuty, Config e terceiros |
| Gatilho de regra do Config | SOA-C02 | mudança de configuração vs. periódico, os dois juntos | por que usar só o periódico deixa janela de até 24 horas |
| Padrão de evento do EventBridge | DOP-C02, SAA-C03 | regra casada por Severity.Label e Workflow.Status | que achado atualizado dispara o MESMO detail-type que achado novo |
| Detecção vs. prevenção | SOA-C02 | GuardDuty e Config detectam; WAF e SCP previnem | por que este laboratório reage ao incidente, não o impede |
| Severidade e priorização | SOA-C02, SAP-C02 | SLA diferenciado por Severity.Label | por que achado LOW não deve virar ticket individual |
| Automação com humano no laço | SAP-C02, DOP-C02 | Step Functions pausando para aprovação | quando remediação automática é aceitável, e quando não é |
| Menor privilégio na automação | SAA-C03, SOA-C02 | policy do Lambda derivada do uso, herdada do L41 | por que copiar uma policy ampla reabre o problema que o L41 fechou |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um bucket S3 público sem nenhum tráfego malicioso registrado e pergunta por que o GuardDuty não achou nada. A resposta é de papel, não de configuração: GuardDuty detecta COMPORTAMENTO, e não houve comportamento anômalo — o bucket ficou público, ninguém o acessou de forma suspeita. É achado de CONFIGURAÇÃO, e quem enxerga isso é o Config, com uma regra avaliando aquele recurso.
Requisitos, e como cada um muda o desenho
Requisito não funcional que não aparece numa linha de configuração é intenção. A coluna da direita é onde cada um deixou marca.
| Requisito | Valor declarado | O que ele decide no desenho |
|---|---|---|
| Achado CRITICAL reconhecido rapidamente | até 15 minutos | EventBridge aciona a função de ticket direto, sem fila intermediária que somaria latência de poll |
| Conformidade avaliada continuamente | sem janela cega grande | regra do Config com gatilho de mudança de configuração como principal, periódico só como rede de segurança |
| Sem SOC nem plantão 24 horas | equipe de 2 pessoas | nenhuma ação irreversível é automática — Step Functions sempre pausa para aprovação |
| Sem orçamento para SIEM terceiro | ferramenta nativa da conta | Security Hub como agregador, não uma plataforma de terceiro com licença por usuário |
| Achado de baixa severidade não pode virar ruído | triagem sustentável | LOW e INFORMATIONAL não geram ticket individual — entram em relatório agregado semanal |
| Rastreabilidade de quem tratou o quê | auditável | ticket em DynamoDB com dono, status e histórico — não um e-mail que se perde na caixa |
| Hipótese declarada: aprovar manualmente toda contenção | preferência explícita da equipe | toda automação de contenção passa pelo fluxo de aprovação, mesmo ações tecnicamente reversíveis |
| Cobertura dos dois tipos de achado | comportamento e conformidade | GuardDuty E Config alimentando o mesmo agregador — um não substitui o outro |
Por que o EventBridge chama o Lambda direto, sem fila no meio
Uma fila (SQS) entre o EventBridge e a função somaria o intervalo de poll ao tempo de reconhecimento — e o requisito declarado é 15 minutos para CRITICAL, não "15 minutos mais o que a fila demorar para entregar". Fila faz sentido quando se precisa amortecer rajada além da concorrência do Lambda; para o volume desta conta, a invocação direta é mais simples e não sacrifica o prazo.
Arquitetura mínima: o achado que ninguém trata
- → achado publicado (finding)
- → e-mail de notificação
- Segurança e identidade
- Integração de apps
- Fora da AWS
Este desenho está tecnicamente "protegido": o detector existe e gera achado real. O defeito não é ausência de detecção — é que o único destino de qualquer achado, de qualquer severidade, é a mesma caixa de entrada, sem prazo e sem dono. Percorra os passos e repare que o achado de mineração de criptomoeda tem a mesma aparência do achado informativo do dia anterior.
- A detecção nunca foi o problema. GuardDuty já correlaciona CloudTrail de gerenciamento, VPC Flow Logs e consultas de DNS por padrão, sem nenhuma configuração adicional. Uma instância conversando com um IP conhecido de mineração de criptomoeda gera achado real, no minuto em que o padrão é observado.
- Um caminho só, para toda severidade. O tópico SNS não discrimina: um achado CRITICAL e um achado INFORMATIONAL seguem exatamente pela mesma assinatura, com o mesmo formato de e-mail. Não existe, neste desenho, nenhum ponto onde a severidade decide algo.
- O e-mail chega, e é onde a triagem para. Não há sistema de ticket, prazo ou dono. "Tratar o achado" depende de uma pessoa abrir o e-mail, entender a gravidade sozinha e lembrar de agir — três passos que competem com o resto do trabalho do dia.
- A prova: um achado de teste também não produz ação rastreável. Gerar um achado de amostra (`create-sample-findings`) e observar o resultado é a prova deste laboratório: o e-mail chega, e nada além dele existe — nenhum registro, nenhum prazo, nenhuma consulta que responda "isso foi tratado?".
- Por que alguém monta exatamente assim. Porque é o padrão: em muitas contas o GuardDuty já vem habilitado, e ligar a notificação por e-mail é a configuração de menor esforço. A conta "parece" protegida em qualquer checklist superficial — e é exatamente essa aparência que torna o defeito difícil de perceber sem medir.
Arquitetura para produção: prazo por severidade, com freio para o irreversível
- → achado normalizado em ASFF
- → resultado de conformidade normalizado em ASFF
- → evento Security Hub Findings - Imported
- → regra casada por Severity.Label
- → ticket com prazo calculado pela severidade
- → achado crítico automatizável aciona a contenção
- → aprovação exigida antes da ação irreversível
- → prazo vs. resolução, agregado por severidade
- Segurança e identidade
- Gestão e governança
- Integração de apps
- Compute
- Banco de dados
- Fora da AWS
O que muda não é "mais serviço": é que o achado passa a ter prazo. Security Hub agrega os dois detectores num placar único; o EventBridge, não uma pessoa olhando console, decide que aquele achado vira ticket; e a ação que não se pode desfazer para numa aprovação humana, não numa função Lambda. Percorra os passos: cada peça nova rastreia a um requisito da seção anterior.
- Dois detectores continuam sendo dois. GuardDuty entra sem nenhuma alteração em relação ao desenho mínimo. Config é o serviço novo, e ele não substitui o GuardDuty: avalia se um bucket S3 tem bloqueio de acesso público, o que não é comportamento anômalo nenhum — é desvio de configuração, e o GuardDuty não olha para isso.
- Security Hub normaliza, não decide. Os achados de origens diferentes chegam com formatos diferentes; o Security Hub converte todos para ASFF (AWS Security Finding Format) e é isso que permite UMA regra do EventBridge, adiante, funcionar para achado de qualquer um dos dois detectores.
- O gatilho é o evento, não o console. O Security Hub publica "Security Hub Findings - Imported" no EventBridge, e esse mesmo tipo de evento dispara tanto quando um achado é importado pela primeira vez quanto quando ele é atualizado — inclusive quando a severidade muda. É o detalhe que a seção de falhas deste módulo explora.
- O prazo nasce da severidade, não de "assim que der". A função de ticket lê o campo Severity.Label do achado e calcula um prazo diferente por nível — não existe UM prazo único no sistema. É a diferença entre processo e boa vontade.
- Achado automatizável tem freio. A função de ticket NUNCA executa contenção sozinha: para achado CRITICAL de um tipo automatizável, ela apenas inicia um fluxo que para numa aprovação. Um diagnóstico errado nesse ponto ainda pode ser corrigido, porque nada irreversível aconteceu.
- O painel mede prazo, não contagem. A pergunta que importa não é "quantos achados tivemos", é "quantos "cumpriram o prazo da própria severidade". Um mês com 40 achados LOW e um CRITICAL vencido é pior do que um mês com 200 achados LOW e zero vencidos.
Como funciona ponta a ponta
| Severidade (ASFF) | Reconhecimento | Contenção/resolução | Vira ticket individual? |
|---|---|---|---|
| CRITICAL | 15 minutos | 1 hora | sim, imediato |
| HIGH | 1 hora | 4 horas | sim, imediato |
| MEDIUM | 1 dia útil | 3 dias úteis | sim, na fila do dia útil |
| LOW | não se aplica | não se aplica | não — entra no relatório semanal agregado |
| INFORMATIONAL | não se aplica | não se aplica | não — arquivado, consultável sob demanda |
Por que LOW e INFORMATIONAL não abrem ticket
Tratar toda severidade com o mesmo processo é o que mais gera fadiga de alerta. Se um achado informativo compete pela mesma fila que um achado crítico, a equipe aprende — rapidamente e com razão — a tratar a fila inteira como ruído. O relatório semanal agregado existe para que o volume baixo continue visível sem competir por atenção imediata.
// O que o EventBridge entrega quando o Security Hub importa um achado.
// E o payload que a funcao de ticket le para calcular o prazo.
{
"version": "0",
"id": "6a7e8f2c-...",
"detail-type": "Security Hub Findings - Imported",
"source": "aws.securityhub",
"account": "111122223333",
"time": "2026-08-07T14:03:11Z",
"region": "us-east-1",
"detail": {
"findings": [
{
"Id": "arn:aws:guardduty:us-east-1:111122223333:detector/abc/finding/xyz",
"ProductArn": "arn:aws:securityhub:us-east-1::product/aws/guardduty",
"GeneratorId": "arn:aws:guardduty:us-east-1:111122223333:detector/abc",
// O tipo e o que a funcao usa para decidir se e AUTOMATIZAVEL.
"Types": ["CryptoCurrency:EC2/BitcoinTool.B!DNS"],
"Severity": { "Label": "CRITICAL", "Normalized": 90 },
// NEW e o unico status que a regra do EventBridge deixa passar.
"Workflow": { "Status": "NEW" },
"Resources": [
{ "Type": "AwsEc2Instance", "Id": "arn:aws:ec2:us-east-1:111122223333:instance/i-0abc" }
],
"FirstObservedAt": "2026-08-07T14:02:40Z",
"UpdatedAt": "2026-08-07T14:02:40Z"
}
]
}
}As decisões, e o que se perde em cada uma
📋 Uma equipe de duas pessoas, sem SOC e sem plantão formal, precisa que achado crítico de segurança vire ação com prazo — sem orçamento para uma ferramenta terceira de SIEM.
Os três serviços já existem na conta e não pedem infraestrutura nova: o custo é por achado avaliado e por item de configuração gravado, não por licença. A automação resolve exatamente o problema declarado — achado sem dono e sem prazo — sem tentar resolver um problema que a equipe não tem, que é correlação entre múltiplas fontes de terceiros ou investigação forense profunda. Isso é o L50.
Alt: SIEM de terceiro (Splunk, Datadog Security) — Maior poder de correlação entre fontes, e cobra ingestão de dado e licença por usuário — dois custos que crescem com o volume e que o orçamento declarado não comporta. Faz sentido quando há múltiplas nuvens ou fontes on-premises para correlacionar, o que não é o caso aqui.
Alt: Alarme do CloudWatch direto sobre eventos do GuardDuty — Resolve a notificação, mas não a agregação: sem o Security Hub, um achado de conformidade do Config segue um caminho totalmente separado, com regra e formato próprios — dobra a manutenção para a metade da cobertura.
Alt: Amazon Detective — Serve para investigar a CAUSA de um achado já aberto — gráfico de comportamento ao longo do tempo — e não para decidir se o achado vira ticket. É complementar à automação deste laboratório, não substituto dela.
Alt: Triagem manual diária de um relatório consolidado — Não cobre o requisito de reconhecimento em 15 minutos para achado CRITICAL: uma rotina diária, por definição, tem janela de até 24 horas entre o achado nascer e alguém olhar para ele.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Agregador de achado | Security Hub nativo | SIEM terceiro; alarme direto por serviço | já existe na conta, cobra por achado e não por licença | correlação entre múltiplas nuvens ou fontes on-premises, que a Cadência não tem |
| Gatilho do EventBridge | Severity.Label em CRITICAL/HIGH, Workflow.Status = NEW | casar todo achado sem filtro; casar só por tipo de recurso | evita reabrir ticket a cada reavaliação do mesmo achado sem mudança real | achado que muda de severidade precisa de chave própria — tratado na seção de falhas |
| Gatilho do Config | mudança de configuração + periódico de 24h como rede de segurança | só periódico (padrão do console); só mudança | pega o desvio no instante em que acontece, sem depender só da notificação | mais eventos avaliados; o custo por item de configuração sobe com o volume de mudança |
| Cálculo do prazo | SLA por severidade, calculado na função | prazo único para todo achado; prazo definido manualmente por quem abre o ticket | o processo não depende de alguém lembrar a política correta | mudar o SLA exige alterar código, não uma configuração de negócio separada |
| Contenção automática | sempre passa por aprovação humana | contenção 100% automática para achado CRITICAL | a equipe de duas pessoas prefere errar para o lado de aprovar manualmente | tempo de resposta depende de alguém estar disponível para aprovar |
| Armazenamento do ticket | DynamoDB sob demanda | RDS; planilha compartilhada | volume imprevisível e picos súbitos não pedem capacidade provisionada | consulta ad-hoc complexa é mais limitada que SQL |
A dívida que este laboratório cria, e que não paga aqui
Um achado CRITICAL cujo tipo não está na lista de "automatizável" vira ticket, mas nenhum fluxo de contenção inicia — alguém decide manualmente o próximo passo, sem runbook. Formalizar esse runbook, e medir o alcance real de uma credencial comprometida, é o L50: este módulo entrega o ticket com prazo; o que se faz com ele quando a resposta certa não é óbvia é o laboratório seguinte.
Construir: GuardDuty e Config, dois detectores que não se substituem
Os dois entram no mesmo arquivo porque são o par que mais se confunde: parecem "a mesma coisa de segurança", e não são. GuardDuty observa tráfego e chamada de API. Config observa o ESTADO declarado de um recurso, comparado contra uma regra.
# detectores.tf — dois detectores, dois papeis que nao se substituem
# GuardDuty ja analisa CloudTrail de gerenciamento, VPC Flow Logs e logs de
# DNS por padrao, sem nenhuma configuracao adicional — e essa parte este
# laboratorio NAO precisa construir; ela ja existe no momento em que o
# detector e criado.
resource "aws_guardduty_detector" "principal" {
enable = true
# O minimo suportado. Isto controla so a cadencia de REEXPORTACAO de um
# achado JA EXISTENTE que foi atualizado (ex: nova evidencia no mesmo
# achado) — um achado NOVO publica no EventBridge perto de tempo real,
# independente deste valor. Ainda assim vale o minimo: quando o mesmo
# achado ganha evidencia nova, 15 min bate melhor no SLA do que o padrao
# de 6 horas do console.
finding_publishing_frequency = "FIFTEEN_MINUTES"
}
# AWS Config avalia CONFIGURACAO, nao comportamento. Precisa de um gravador
# antes de qualquer regra ter o que avaliar contra.
resource "aws_config_configuration_recorder" "principal" {
name = "${var.projeto}-config"
role_arn = aws_iam_role.config.arn
recording_group {
# Gravar so os tipos que as regras deste laboratorio avaliam evita o
# custo oculto de gravar item de configuracao que ninguem audita — o
# Config cobra por item gravado, nao por regra.
all_supported = false
resource_types = ["AWS::S3::Bucket", "AWS::IAM::Role"]
}
}
resource "aws_config_delivery_channel" "principal" {
name = "${var.projeto}-config"
s3_bucket_name = aws_s3_bucket.config.id
depends_on = [aws_config_configuration_recorder.principal]
}
resource "aws_config_configuration_recorder_status" "principal" {
name = aws_config_configuration_recorder.principal.name
is_enabled = true
depends_on = [aws_config_delivery_channel.principal]
}
# A regra gerenciada da AWS para bloqueio de acesso publico em bucket S3.
# O ponto central deste laboratorio: DOIS gatilhos, nao um so.
resource "aws_config_config_rule" "bucket_sem_acesso_publico" {
name = "${var.projeto}-s3-bloqueio-publico"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITED"
}
scope {
compliance_resource_types = ["AWS::S3::Bucket"]
}
# Gatilho por MUDANCA: reavalia no instante em que a configuracao do
# bucket muda. E o que pega o bucket que ficou publico as 14h32.
#
# Gatilho PERIODICO (abaixo, via maximum_execution_frequency implicito de
# 24h no console; aqui explicito para nao depender do padrao) e a rede de
# seguranca — cobre o que a notificacao de mudanca eventualmente perder.
# Um so dos dois deixa janela: so periodico, ate 24h sem detectar; so
# mudanca, um evento perdido nunca e corrigido.
maximum_execution_frequency = "TwentyFour_Hours"
depends_on = [aws_config_configuration_recorder.principal]
}
output "detector_guardduty" {
value = aws_guardduty_detector.principal.id
description = "ID do detector — usado para gerar achado de teste na secao de provas"
}
Por que gravar só dois tipos de recurso
AWS Config cobra por item de configuração gravado. Gravar "todos os tipos suportados" antes de ter uma regra que os avalie é o custo oculto mais comum desta seção — dado pago que nenhuma regra jamais lê. Comece pelos tipos que uma regra real avalia, e amplie a lista quando escrever a próxima regra.
Construir: Security Hub agregando os dois em ASFF
Não há recurso para "conectar" GuardDuty e Config ao Security Hub: a integração é automática quando os três estão na mesma conta e região. O que se declara é o padrão de conformidade que roda por cima disso.
# securityhub.tf — o agregador; nao detecta nada, normaliza e da o placar
resource "aws_securityhub_account" "principal" {
# Habilita SO o padrao que decidimos avaliar (abaixo), nao os dois
# principais de uma vez. Cada padrao ligado multiplica o numero de
# verificacoes de conformidade rodadas — e e cobrado por verificacao.
enable_default_standards = false
}
resource "aws_securityhub_standards_subscription" "fsbp" {
standards_arn = "arn:aws:securityhub:${var.regiao}::standards/aws-foundational-security-best-practices/v/1.0.0"
depends_on = [aws_securityhub_account.principal]
}
# GuardDuty e Config publicam para o Security Hub por integracao nativa: nao
# existe um recurso Terraform de "assinatura de produto" para os dois porque
# a assinatura e automatica quando ambos estao habilitados na mesma conta e
# regiao que o Security Hub. Assinatura explicita de produto so existe para
# integracao de terceiro.
Cada padrão ligado multiplica verificação, não só achado
Security Hub cobra por verificação de conformidade rodada, e cada padrão habilitado (AWS Foundational Security Best Practices, CIS AWS Foundations, e outros) roda seu próprio conjunto de controles — muitos se sobrepõem entre padrões. Ligar dois padrões quando um cobre o essencial dobra o custo de verificação sem dobrar o discernimento sobre a postura real da conta.
Construir: a regra do EventBridge e a função que calcula o prazo
É aqui que o e-mail vira processo. A regra decide QUAIS achados chegam à função; a função decide QUANDO o prazo vence e SE existe um fluxo de contenção a iniciar.
# resposta.tf — o gatilho por severidade, o ticket e o freio de aprovacao
resource "aws_dynamodb_table" "tickets" {
name = "${var.projeto}-tickets-seguranca"
# Sob demanda: o volume de achado e picos imprevisiveis (uma varredura
# externa gera dezenas de achados no mesmo minuto). Capacidade provisionada
# aqui e estimar um numero que nao existe.
billing_mode = "PAY_PER_REQUEST"
hash_key = "achado_id"
attribute {
name = "achado_id"
type = "S"
}
}
resource "aws_cloudwatch_event_rule" "achado_critico_ou_alto" {
name = "${var.projeto}-achado-critico-alto"
# O MESMO detail-type dispara para achado novo e para achado atualizado —
# e por isso o filtro de Workflow.Status = NEW e o que evita reabrir um
# ticket a cada reavaliacao do mesmo achado sem mudanca real.
event_pattern = jsonencode({
source = ["aws.securityhub"]
"detail-type" = ["Security Hub Findings - Imported"]
detail = {
findings = {
Severity = { Label = ["CRITICAL", "HIGH"] }
Workflow = { Status = ["NEW"] }
}
}
})
}
resource "aws_cloudwatch_event_target" "para_a_funcao_de_ticket" {
rule = aws_cloudwatch_event_rule.achado_critico_ou_alto.name
target_id = "funcao-ticket"
arn = aws_lambda_function.ticket.arn
}
resource "aws_lambda_permission" "eventbridge_invoca" {
statement_id = "PermiteEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.ticket.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.achado_critico_ou_alto.arn
}
resource "aws_lambda_function" "ticket" {
function_name = "${var.projeto}-ticket-seguranca"
role = aws_iam_role.ticket_lambda.arn
handler = "TicketSeguranca::TicketSeguranca.Function::FunctionHandler"
runtime = "dotnet8"
timeout = 15
filename = data.archive_file.ticket.output_path
environment {
variables = {
TABELA_TICKETS = aws_dynamodb_table.tickets.name
ARN_MAQUINA_APROVACAO = aws_sfn_state_machine.aprovacao.arn
}
}
}
resource "aws_sfn_state_machine" "aprovacao" {
name = "${var.projeto}-aprovacao-contencao"
role_arn = aws_iam_role.step_functions.arn
# Definicao completa na secao seguinte — e o freio para o irreversivel.
definition = file("${path.module}/aprovacao.asl.json")
}
# Menor privilegio: PutItem/GetItem/UpdateItem restritos a UMA tabela — nao
# dynamodb:* e nao Resource "*". E o habito que o L41 existe para instalar,
# e que copiar uma role generica reabriria.
data "aws_iam_policy_document" "ticket_lambda" {
statement {
effect = "Allow"
actions = ["dynamodb:PutItem", "dynamodb:GetItem", "dynamodb:UpdateItem"]
resources = [aws_dynamodb_table.tickets.arn]
}
statement {
effect = "Allow"
actions = ["states:StartExecution"]
resources = [aws_sfn_state_machine.aprovacao.arn]
}
statement {
effect = "Allow"
# PutMetricData nao aceita recurso especifico: e operacao de namespace de
# metrica, nao de recurso individual. E por isso que o "*" aparece aqui,
# e em nenhum outro lugar desta policy.
actions = ["cloudwatch:PutMetricData"]
resources = ["*"]
}
}
resource "aws_iam_role_policy" "ticket_lambda" {
role = aws_iam_role.ticket_lambda.id
policy = data.aws_iam_policy_document.ticket_lambda.json
}
O `*` que teria aparecido aqui, e por que não aparece
A tentação mais comum ao escrever a policy deste Lambda é dar `dynamodb:*` "para não precisar voltar depois". Nesta conta isso significaria que uma função que só deveria abrir ticket teria permissão para apagar QUALQUER tabela DynamoDB da conta — inclusive as que nada têm a ver com segurança. É exatamente o hábito que o L41 existe para corrigir, e reaparece aqui porque toda automação nova é uma nova chance de copiar uma policy ampla em vez de derivar uma estreita.
{
"Comment": "Contencao de achado CRITICAL: para numa aprovacao antes de qualquer acao irreversivel",
"StartAt": "AguardaAprovacao",
"States": {
"AguardaAprovacao": {
"Type": "Task",
"Resource": "arn:aws:states:::sns:publish.waitForTaskToken",
"Parameters": {
"TopicArn": "${var.arn_topico_aprovacao}",
"Message.$": "$",
"MessageAttributes": {
"TaskToken": { "DataType": "String", "StringValue.$": "$$.Task.Token" }
}
},
"TimeoutSeconds": 3600,
"Next": "Decisao",
"Catch": [
{ "ErrorEquals": ["States.Timeout"], "Next": "AprovacaoExpirou" }
]
},
"Decisao": {
"Type": "Choice",
"Choices": [
{ "Variable": "$.aprovado", "BooleanEquals": true, "Next": "ExecutaContencao" }
],
"Default": "AprovacaoNegada"
},
"ExecutaContencao": {
"Type": "Succeed"
},
"AprovacaoNegada": {
"Type": "Fail",
"Error": "AnalistaRejeitou",
"Cause": "Analista revisou o achado e decidiu que a acao nao deve ser executada"
},
"AprovacaoExpirou": {
"Type": "Fail",
"Error": "SemRespostaEmUmaHora",
"Cause": "Ninguem aprovou nem rejeitou dentro do prazo — o achado continua ABERTO no ticket"
}
}
}
Construir: o Lambda em C#, e por que ele não decide sozinho pela ação irreversível
A função tem duas responsabilidades, e só duas: calcular o prazo e, para o caso automatizável, iniciar o fluxo de aprovação. Ela nunca executa uma contenção — isolar instância, revogar credencial — diretamente.
// Function.cs — o Lambda que transforma achado em ticket com prazo
using Amazon.Lambda.Core;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Amazon.StepFunctions;
using Amazon.StepFunctions.Model;
using System.Text.Json;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace TicketSeguranca;
public record Severidade(string Label);
public record Achado(string Id, Severidade Severity, List<string> Types, List<object> Resources);
public record Findings(List<Achado> Findings);
public record EventoEventBridge(Findings Detail);
public class Function
{
// SLA por severidade — nao existe UM prazo, existe uma tabela. Achado
// LOW e INFORMATIONAL nem chegam aqui: o padrao do EventBridge ja os
// filtra antes de esta funcao ser invocada.
private static readonly Dictionary<string, TimeSpan> PrazoPorSeveridade = new()
{
["CRITICAL"] = TimeSpan.FromMinutes(15),
["HIGH"] = TimeSpan.FromHours(1),
};
private readonly AmazonDynamoDBClient _dynamo = new();
private readonly AmazonStepFunctionsClient _stepFunctions = new();
private readonly string _tabela = Environment.GetEnvironmentVariable("TABELA_TICKETS")!;
private readonly string _maquinaAprovacao = Environment.GetEnvironmentVariable("ARN_MAQUINA_APROVACAO")!;
public async Task FunctionHandler(EventoEventBridge evento, ILambdaContext contexto)
{
foreach (var achado in evento.Detail.Findings)
{
var agora = DateTimeOffset.UtcNow;
var prazo = agora + PrazoPorSeveridade[achado.Severity.Label];
// Idempotencia CORRETA: a chave inclui a severidade atual, nao
// so o identificador do achado. Um achado que escala de MEDIUM
// para CRITICAL muda de chave e gera um SEGUNDO registro com
// prazo mais curto — em vez de esta funcao ignorar em silencio
// a atualizacao porque "esse achado ja tem ticket". E o defeito
// que a secao de falhas deste modulo provoca de proposito.
var chave = $"{achado.Id}#{achado.Severity.Label}";
await _dynamo.PutItemAsync(new PutItemRequest
{
TableName = _tabela,
Item = new Dictionary<string, AttributeValue>
{
["achado_id"] = new AttributeValue { S = chave },
["severidade"] = new AttributeValue { S = achado.Severity.Label },
["criado_em"] = new AttributeValue { S = agora.ToString("O") },
["prazo"] = new AttributeValue { S = prazo.ToString("O") },
["status"] = new AttributeValue { S = "ABERTO" },
["tipo"] = new AttributeValue { S = achado.Types.FirstOrDefault() ?? "desconhecido" },
},
// Nao sobrescreve um ticket ja aberto para a MESMA chave —
// mas uma chave nova (severidade diferente) sempre grava.
ConditionExpression = "attribute_not_exists(achado_id)",
});
// So achado CRITICAL de um tipo conhecido como automatizavel
// entra no fluxo de aprovacao. Esta funcao NUNCA executa a
// contencao: ela apenas inicia a maquina de estados que vai
// parar numa aprovacao humana antes de qualquer acao.
if (achado.Severity.Label == "CRITICAL" && EhAutomatizavel(achado))
{
await _stepFunctions.StartExecutionAsync(new StartExecutionRequest
{
StateMachineArn = _maquinaAprovacao,
Input = JsonSerializer.Serialize(new { achado.Id, achado.Resources }),
});
}
}
}
// Lista curta e explicita, de proposito: contencao automatizada so se
// propoe para tipos de achado onde a acao (isolar instancia, revogar
// credencial) e conhecida e a equipe ja decidiu que quer o fluxo de
// aprovacao para eles. Tipo fora da lista vira ticket, sem fluxo de
// contencao — alguem decide manualmente o proximo passo.
private static bool EhAutomatizavel(Achado achado) =>
achado.Types.Any(t => t.StartsWith("CryptoCurrency") || t.StartsWith("UnauthorizedAccess"));
}
A chave composta é o que corrige o defeito da seção de falhas
Incluir a severidade na chave do registro (`achado_id#severidade`) parece redundante até o achado escalar: sem isso, uma reavaliação de MEDIUM para CRITICAL colide com a `ConditionExpression` de "não sobrescreve" e o Lambda descarta a atualização em silêncio. Com a severidade na chave, a escalada vira um SEGUNDO registro, com prazo mais curto — que é o comportamento correto.
Implantar, e provar que o achado virou ação
Cinco provas. Nenhuma aceita "chegou o e-mail" como resultado — cada uma tem um número, um estado ou uma ausência esperada, e a quarta é a que mais evidencia a diferença entre os dois desenhos.
# provas.sh — cinco medicoes; nenhuma conclusao vem de "chegou o e-mail"
PROJETO=ffv-lab; REGIAO=us-east-1
DETECTOR=$(aws guardduty list-detectors --query 'DetectorIds[0]' --output text)
# ── Prova 1: o achado de teste realmente nasce no GuardDuty ──────────────────
aws guardduty create-sample-findings --detector-id "$DETECTOR" --region "$REGIAO"
sleep 30
aws guardduty list-findings --detector-id "$DETECTOR" \
--query 'length(FindingIds)' --output text
# Esperado: numero maior que zero. Achados de amostra levam ate alguns
# minutos para aparecer — se vier zero, aguarde e repita antes de concluir falha.
# ── Prova 2: o mesmo achado aparece no Security Hub, normalizado ─────────────
aws securityhub get-findings \
--filters '{"ProductName":[{"Value":"GuardDuty","Comparison":"EQUALS"}]}' \
--query 'Findings[0].{Id:Id,Severidade:Severity.Label,Status:Workflow.Status}' \
--output table
# Esperado: uma linha com Severidade preenchida. Vazio aqui significa que a
# integracao GuardDuty->Security Hub nao esta ativa nesta conta/regiao.
# ── Prova 3: o ticket nasce com prazo, nao so com conteudo ───────────────────
aws dynamodb scan --table-name "${PROJETO}-tickets-seguranca" \
--query 'Items[0].{Severidade:severidade.S,Prazo:prazo.S,Status:status.S}' \
--output table
# Esperado: campo Prazo preenchido, e igual a Criado_em mais o SLA da
# severidade — 15 min para CRITICAL, 1 h para HIGH. Prazo vazio e a
# funcao de ticket nao concluiu, ou o achado nao casou a regra do EventBridge.
# ── Prova 4: o caminho minimo (so e-mail) nao produz NENHUM registro ─────────
# Rode com a stack de producao DESLIGADA (so GuardDuty + SNS do desenho
# minimo) e repita a prova 1. Depois:
aws dynamodb scan --table-name "${PROJETO}-tickets-seguranca" \
--query 'length(Items)' --output text 2>/dev/null || echo "tabela nao existe"
# Esperado: "tabela nao existe", ou 0 itens. E a prova de que o e-mail,
# sozinho, nunca produziu acao rastreavel nenhuma.
# ── Prova 5: a aprovacao humana realmente bloqueia a contencao ───────────────
aws stepfunctions list-executions \
--state-machine-arn "$(terraform output -raw arn_maquina_aprovacao)" \
--status-filter RUNNING \
--query 'executions[].{Iniciada:startDate,Status:status}' --output table
# Esperado, para achado CRITICAL automatizavel: uma execucao em RUNNING,
# parada no estado AguardaAprovacao, ate alguem responder ou o prazo de
# 1 h expirar. Se o status for SUCCEEDED sem ninguem ter aprovado, o freio
# nao esta funcionando.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · O achado nasce de verdade | `create-sample-findings` + `list-findings` | contagem de achados maior que zero | zero após alguns minutos indica detector desabilitado ou região errada |
| 2 · Chega normalizado no Security Hub | `get-findings` filtrado por produto | linha com Severidade e Status preenchidos | lista vazia indica integração GuardDuty→Security Hub não habilitada nesta conta/região |
| 3 · O ticket nasce com prazo | `dynamodb scan` na tabela de tickets | campo Prazo = Criado_em + SLA da severidade | prazo vazio indica que a regra do EventBridge não casou o achado, ou a função falhou |
| 4 · O caminho mínimo não deixa rastro | repetir a prova 1 só com GuardDuty + SNS | tabela de tickets inexistente ou vazia | qualquer registro aqui significa que a stack de produção ainda está ativa |
| 5 · A aprovação bloqueia a contenção | `list-executions` da máquina de estados | execução em RUNNING, parada em AguardaAprovacao | SUCCEEDED sem aprovação humana registrada é o freio falhando |
Quebrar de propósito: três falhas e o diagnóstico
As três produzem o mesmo sintoma superficial — "o achado não gerou a ação certa" — e o que separa uma da outra é onde exatamente a cadeia quebra.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Dedupe esconde escalada de severidade | gere um achado MEDIUM, deixe criar o ticket, depois force a mesma chave sem a severidade (`achado_id` puro) e reenvie como CRITICAL | o achado crítico não gera ticket novo nem atualiza o prazo do antigo | log do Lambda mostra `ConditionalCheckFailedException` sendo engolido, ou a chave sem severidade | chave composta `achado_id#severidade`, como no código desta seção — nunca só o ID |
| Config só com gatilho periódico | remova `maximum_execution_frequency` do gatilho de mudança e deixe só o periódico | um bucket fica público por horas antes de o achado aparecer | compare o horário em que o bucket mudou com `UpdatedAt` do achado no Config | mantenha os dois gatilhos: mudança como principal, periódico como rede de segurança |
| Função de ticket com IAM amplo | anexe `dynamodb:*` e `states:*` em vez da policy restrita da seção anterior | nada quebra visivelmente — é esse o problema | Access Analyzer (L41) sobre a role do Lambda, depois de dias de uso real | derivar a policy do uso medido, com `Resource` restrito às duas ARNs que a função toca |
A falha que não aparece em nenhum log de erro
Achado escalando de severidade sem gerar novo ticket não lança exceção, não falha health check e não aparece em painel de erro — o Lambda termina com sucesso, porque `ConditionExpression` falhar é o comportamento ESPERADO para um achado repetido. A única forma de flagrar isso é comparar a severidade do achado bruto no Security Hub com a do ticket aberto, o que é exatamente o que a seção de observabilidade mede.
Um bucket S3 da Cadência ficou público por 40 minutos. Ao revisar depois, ninguém encontra achado do GuardDuty sobre o episódio, embora o Security Hub esteja habilitado e agregando normalmente. Qual é a explicação mais provável?
Segurança: o que muda quando a resposta fica automática
Automatizar a abertura de ticket é seguro por natureza: só cria rastro. O risco real deste laboratório mora em três lugares específicos — a permissão da própria função de resposta, o freio de aprovação, e a hipótese de que a automação continua correta depois que ninguém mexe nela por meses.
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Função de ticket com IAM mais amplo que o necessário | média | médio | policy derivada do uso real, herdada da disciplina do L41 | Access Analyzer (unused access) sobre a role da função | restringir a policy e reimplantar; nenhuma ação retroativa necessária |
| Aprovação de contenção contornada por pressa ("aprovar tudo em lote") | média | alto | o fluxo exige token de aprovação por execução — não existe atalho de "aprovar todos" | CloudTrail no papel de quem aprova, correlacionado ao volume de aprovações por minuto | revisar as decisões do período e reverter contenção aplicada sem análise real |
| Dedupe por identificador esconde achado escalado | média | alto | chave composta por severidade, como implementado nesta seção | comparar severidade do achado bruto com a do ticket aberto para o mesmo `achado_id` | reabrir o ticket com o prazo recalculado pela severidade atual |
| GuardDuty ou Config desligado silenciosamente para "reduzir ruído" | baixa | crítico | fora do escopo deste laboratório — impedir isso estruturalmente é controle de organização, do L43 | alarme sobre o próprio estado do detector, não sobre os achados que ele gera | reabilitar e investigar o motivo pelo qual foi desligado |
| Ticket CRITICAL aberto sem ninguém olhar dentro do prazo | média | alto | este laboratório garante o TICKET, não a página imediata — combinar com alarme que acorda alguém é o L52 | painel de prazo estourado, revisado no início e no fim do expediente | escalonamento manual para o segundo membro do time |
| Falso positivo recorrente consome a atenção do time | alta | baixo a médio | medir taxa de falso positivo por tipo de achado | campo "não é achado real" no fechamento do ticket, contado por tipo | suprimir o TIPO específico de achado — nunca o detector inteiro |
Observabilidade: as perguntas que o painel tem de responder
Um painel de resposta a achado tem uma função estreita: dizer se o processo está cumprindo o que prometeu, por severidade. Contagem de achado sozinha não responde isso.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Quantos tickets CRITICAL estão com o prazo estourado agora? | consulta na tabela de tickets, prazo < agora, status ABERTO | contenção não está acontecendo no tempo prometido | qualquer valor > 0 |
| Qual % dos tickets fechou dentro do prazo, por severidade? | razão entre resolvido-a-tempo e total, agrupado por severidade | processo cumprindo o SLA declarado | ≥ 95% para CRITICAL e HIGH |
| Um achado escalou de severidade sem gerar ticket novo? | comparar Severity.Label do achado bruto com o do ticket aberto para o mesmo `achado_id` | a falha da seção anterior, em produção | qualquer divergência |
| Quantas remediações automáticas dispararam vs. exigiram aprovação? | contagem de execuções do Step Functions por estado final | proporção de contenção automatizável na carga real | informativo — não é limiar de alarme |
| Qual a taxa de falso positivo, por tipo de achado? | tickets fechados como "não é achado real" ÷ total, por Types | regra ruidosa demais para o ambiente real | > 30% num tipo específico |
| O Config está avaliando por mudança, ou só no ciclo periódico? | comparar `UpdatedAt` do achado com o horário real da mudança de configuração | janela cega de conformidade | diferença > alguns minutos indica gatilho de mudança ausente |
A métrica que engana neste laboratório
Contagem total de achados subindo não é degradação — pode ser só mais superfície monitorada (mais tipo de recurso gravado pelo Config, por exemplo). O sinal que importa é a PROPORÇÃO dentro do prazo, não o volume bruto.
Escala: 10, 10 mil, 1 milhão de avaliações
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 3 a 5 achados/mês, 2 tipos de recurso no Config | cada achado tratado individualmente | nada; é o cenário da Cadência | nada |
| 500 achados/mês, conta com mais serviços | o relatório semanal de LOW cresce | triagem em lote começa a exigir agrupamento por tipo, não item a item | agrupar achados do mesmo tipo/recurso no relatório em vez de listar um a um |
| 1 milhão de avaliações de Config/dia | muitos recursos gravados, muita avaliação de regra | o custo por item de configuração passa a ser linha visível na fatura | restringir `resource_types` aos que alguma regra realmente avalia |
| Rajada: uma nova regra do Config avalia todo o histórico de uma vez | milhares de achados de conformidade no mesmo minuto | a função de ticket pode saturar limite de concorrência do Lambda ou de escrita do DynamoDB | processar em lote com backoff, não uma invocação por achado — é o assunto do L36 |
| Falha de um serviço regional (não de AZ) | GuardDuty, Config e Security Hub são serviços regionais e gerenciados — não há "AZ" que o consumidor escolha ou perca aqui | a unidade real de falha é a REGIÃO, não a zona de disponibilidade | para conta com requisito de continuidade entre regiões, replicar a agregação é decisão de arquitetura separada, fora deste laboratório |
Por que este laboratório não tem uma linha de "falha de AZ"
GuardDuty, Config, Security Hub, EventBridge e DynamoDB são serviços gerenciados sem AZ exposta ao consumidor — a AWS já distribui a resiliência internamente. Forçar uma linha de "falha de AZ" aqui seria inventar um risco que este desenho não tem, só para preencher um formato. O risco real em escala é limite de concorrência e throughput, não topologia de rede.
Custo: as dimensões, não o valor
Nenhum serviço aqui cobra por hora ligada. Todos cobram por volume processado — o que significa que o desenho mínimo (só GuardDuty) e o de produção custam quase o mesmo em detecção, e a diferença de custo real está na agregação e na automação.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | Cadência, 3 a 5 achados/mês, 2 tipos de recurso no Config | GuardDuty e Config no piso de uso; Security Hub com um padrão | desprezível | nenhuma; otimizar aqui é gastar atenção onde não há dinheiro |
| Produção pequena | ~50 achados/mês, 5 tipos de recurso no Config | mais item de configuração gravado; mais verificação de padrão do Security Hub | baixa e previsível | revisar `resource_types` do Config a cada tipo novo de recurso adotado |
| Alta escala | conta com centenas de recursos, múltiplas regras do Config | volume de item de configuração e de achado avaliado cresce com o número de recursos | passa a ser linha visível na fatura de Config e Security Hub | desligar padrão do Security Hub redundante; restringir escopo de regra do Config |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| GuardDuty | volume de CloudTrail, VPC Flow Logs e DNS analisado | proteções adicionais (malware, EKS, RDS) somam dimensões próprias — habilite só o que a conta usa |
| AWS Config | item de configuração gravado + avaliação de regra | `all_supported = true` sem regra correspondente é o custo oculto mais comum |
| Security Hub | achado ingerido + verificação de conformidade por padrão habilitado | dois padrões sobrepostos avaliam o mesmo controle duas vezes |
| Lambda e DynamoDB | invocação e requisição, sob demanda | desprezível neste volume; vira relevante só em rajada de milhares de achados |
| EventBridge | evento no barramento padrão é sem custo adicional para esta regra | barramento customizado, se adotado depois, tem cobrança própria por milhão de eventos |
O ganho de custo que não está em nenhuma fatura da AWS
O achado de mineração de criptomoeda ficou 12 dias sem leitura porque tratar e-mail de segurança competia com o resto do trabalho da semana. O custo que este laboratório ataca não é a fatura da AWS — é o tempo até alguém perceber, que nenhuma linha de cobrança mede.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | achado com prazo, dono e painel de SLA — não mais e-mail perdido | ninguém acorda para achado CRITICAL fora do expediente | alarme que acorda alguém, ligado ao ticket (L52) | alta |
| Segurança | dois detectores agregados, ação irreversível com aprovação humana | runbook de contenção ainda não formalizado para todo tipo de achado | resposta a incidente com blast radius medido (L50) | alta |
| Confiabilidade | Config com gatilho de mudança e periódico, sem depender de um só | conta única — falha regional (não de AZ) tira a detecção inteira | avaliar necessidade de continuidade entre regiões, se o requisito surgir | média |
| Eficiência de performance | resposta em minutos para achado crítico, sem fila intermediária | rajada grande de achados pode saturar concorrência do Lambda | processamento em lote com backoff para picos (L36) | média |
| Otimização de custos | Config grava só os tipos de recurso que uma regra avalia | um segundo padrão do Security Hub duplicaria verificação sem necessidade | revisar padrões habilitados a cada trimestre | média |
| Sustentabilidade | nenhum recurso fica ocioso — tudo aqui é cobrado por uso | histórico de ticket cresce sem política de retenção declarada | definir TTL na tabela de tickets para achado fechado há mais de um ano | baixa |
Por que "risco que fica" aponta duas vezes para o mesmo laboratório
Segurança e Confiabilidade citam o L50 como melhoria porque os dois riscos residuais — runbook de contenção não formalizado e conta única sem plano de continuidade regional — nascem do mesmo limite deste módulo: ele entrega o TICKET com prazo, não a investigação completa do alcance de um incidente. Não é acaso a mesma seta aparecer duas vezes; é o mesmo limite visto por dois pilares diferentes.
Evolução em níveis: o que muda, e o que passa a doer
A terceira arquitetura não é um desenho: é a resposta a QUANDO trocar de desenho. Cada nível resolve um risco e compra outro.
GuardDuty habilitado, achado por SNS/e-mail, sem sistema de rastreio. É onde a Cadência estava, e o defeito é invisível até alguém medir.Security Hub agregando GuardDuty e Config, EventBridge por severidade, ticket com prazo, aprovação humana para o irreversível.Cada tipo automatizável de achado ganha um runbook testado, com o blast radius conhecido antes do incidente acontecer (L50).Security Hub com administração delegada agregando achados de várias contas da organização; SCP impedindo desligar detector (L43).Integração com ferramenta de ticket externa (Jira, ServiceNow) via EventBridge; SOAR com playbook versionado.Achados históricos e decisões de triagem viram dado: um modelo resume achados relacionados numa narrativa única para quem aprova.A ordem não é negociável, e o motivo é concreto
Runbook de contenção (nível 3) sem prazo e dono definidos (nível 2) não tem gatilho para ser acionado — ele fica documentado e não executado. E agregação multiconta (nível 4) sobre um processo que já falha numa conta só multiplica o mesmo defeito por N contas, em vez de corrigi-lo.
Onde IA entra nesta arquitetura, e onde não entra
GuardDuty já usa aprendizado de máquina internamente para detectar comportamento anômalo — mas essa não é a "IA" de que esta seção trata; ela já está embutida no serviço e não é uma decisão de desenho deste laboratório. A pergunta real é outra: onde um modelo, adicionado por cima deste desenho, agregaria valor real?
O cálculo de prazo, o roteamento por severidade e o freio de aprovação são deterministicos por decisão, não por limitação: um achado CRITICAL tem 15 minutos porque a equipe declarou isso, e nenhum modelo deveria "decidir" esse prazo com base em padrão histórico — seria trocar uma política de segurança por uma inferência estatística, exatamente onde a certeza importa mais.
Há um lugar onde IA ajudaria de verdade, e ele é modesto: quando vários achados relacionados chegam juntos — por exemplo, cinco achados do Config sobre o mesmo bucket em minutos, mais um achado do GuardDuty sobre a mesma instância — um modelo poderia resumir isso numa narrativa única para quem vai aprovar a contenção, em vez da pessoa ler cinco tickets separados para reconstruir o mesmo incidente sozinha.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | reduzir o tempo que o analista gasta reconstruindo o contexto de um incidente espalhado em vários achados |
| Por que uma regra não bastaria? | agrupar por recurso ou janela de tempo já cobre boa parte; IA se justifica quando o volume de achados relacionados por incidente cresce além do que agrupamento simples consegue nomear |
| De onde viriam os dados? | o próprio Security Hub (achados relacionados por recurso) e o histórico de tickets já resolvidos, como exemplo de bons resumos |
| Qual o risco? | um resumo que omite um achado relevante, e o analista aprova a contenção sem ver a evidência bruta que faltou |
| Por que não agora? | a Cadência trata de 3 a 5 achados por mês — volume insuficiente para o resumo automático valer o risco de omissão. Isso muda em escala de centenas de achados por semana |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "decidir se o achado é falso positivo e fechar o ticket sozinho" troca um sinal que já é confiável — a classificação de tipo e severidade que GuardDuty e Config já calculam com método determinístico — por uma inferência que pode errar com confiança. Onde existe classificação direta do próprio serviço, IA por cima não aumenta precisão, só aumenta superfície de erro. Um agente que diagnostica incidente com ferramenta somente-leitura, sem decidir sozinho, é o L96.
Anti-padrões deste laboratório
| Anti-padrão | Por que alguém faz | Por que é problema | Sintoma em produção | Forma correta | Quando é aceitável |
|---|---|---|---|---|---|
| Achado só por e-mail, sem sistema de ticket | é a configuração de menor esforço, e a conta "parece" protegida em qualquer checklist | nenhum achado tem prazo, dono ou histórico consultável | "não sei se isso foi tratado" sobre um achado de semanas atrás | Security Hub agregando, EventBridge roteando, ticket com prazo | ambiente de desenvolvimento sem dado real, onde achado não tem consequência |
| Confiar só no GuardDuty e ignorar o Config | GuardDuty já parece cobrir "segurança" sozinho | confunde ausência de comportamento malicioso com conformidade de configuração | bucket público há meses, zero achado do GuardDuty sobre ele | os dois detectores juntos, alimentando o mesmo agregador | nunca — são complementares, não intercambiáveis |
| Dedupe de ticket só pelo identificador do achado | parece o jeito óbvio de evitar duplicar ticket para o mesmo achado | esconde silenciosamente a escalada de severidade do mesmo achado | achado crítico com prazo de um achado médio, sem ninguém perceber | chave composta incluindo a severidade atual | nunca — o custo de corrigir é uma coluna a mais na chave |
| Remediação automática sem aprovação para ação irreversível | parece "mais seguro" reagir na hora do que esperar alguém aprovar | um falso positivo vira dano real, sem chance de desfazer | instância de produção terminada por engano, por um achado que era falso positivo | Step Functions parando em aprovação antes de qualquer ação irreversível | ação genuinamente reversível (ex.: revogar uma sessão temporária) pode dispensar aprovação |
| SLA igual para toda severidade | uma regra só é mais simples de escrever e de testar | achado informativo compete pela mesma fila que achado crítico, e a fila vira ruído | equipe aprendendo a ignorar a fila inteira de tickets de segurança | prazo por Severity.Label, com LOW/INFORMATIONAL fora da fila individual | conta pequena demais para ter mais de uma severidade relevante — raro |
| Desligar o detector para "reduzir ruído" | silenciar é mais rápido do que investigar por que uma regra gera falso positivo | perde a detecção real junto com o ruído | um incidente real não gera achado nenhum, porque o detector estava desligado | suprimir por TIPO específico de achado, mantendo o detector ligado | nunca em produção; talvez em conta de sandbox isolada, sem dado real |
Os dois antipadrões mais perigosos não avisam antes de doer
SLA uniforme e dedupe por identificador puro têm uma característica em comum: os dois passam por qualquer teste superficial. O sistema "funciona" — achado vira ticket, ticket tem prazo — e só um achado real escalando de severidade, ou um mês inteiro de fila de baixa prioridade, revela o defeito. É por isso que a seção de observabilidade mede exatamente essas duas coisas, e não só "o sistema está no ar".
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Achado aparece no GuardDuty, mas não no Security Hub | integração entre os dois não está ativa nesta conta/região | confira se GuardDuty e Security Hub estão habilitados na MESMA conta e região | Security Hub → Integrations | habilitar os dois na mesma conta/região; a assinatura entre eles é automática |
| Achado aparece no Security Hub, mas nenhum ticket é criado | o padrão do EventBridge não casa Severity.Label ou Workflow.Status do achado | teste o padrão da regra contra o evento real capturado | evento em `detail.findings[].Severity.Label` e `.Workflow.Status` | ajustar o event pattern; lembrar que Workflow.Status pode não ser NEW se o achado já foi tocado |
| Ticket criado sem prazo (campo vazio) | severidade do achado fora do mapa conhecido pela função (ex.: valor inesperado) | leia o log de execução do Lambda para a invocação específica | CloudWatch Logs da função de ticket | mapa de SLA com valor padrão explícito para severidade desconhecida — nunca silencioso |
| Bucket fica público por horas antes do achado aparecer | regra do Config só com gatilho periódico, sem gatilho de mudança | compare o horário real da mudança com `UpdatedAt` do achado | definição da regra do Config, campo de trigger | adicionar gatilho de mudança de configuração como principal |
| Severidade escala e o ticket original não muda | dedupe por identificador puro, sem a severidade na chave | compare a severidade do achado bruto no Security Hub com a do ticket aberto | lógica de chave no Lambda de ticket | chave composta `achado_id#severidade`, como nesta seção |
| Fluxo de aprovação nunca inicia para achado crítico | o tipo do achado não está na lista de tipos automatizáveis da função | confira `Types` do achado contra a lista em `EhAutomatizavel` | código da função de ticket | ou é decisão certa (esse tipo não deveria automatizar) ou falta incluir o tipo na lista |
| Painel mostra 100% de SLA cumprido, e o time sabe que não é verdade | métrica calculada a partir da criação do TICKET, não do achado original | compare o timestamp usado na métrica com `FirstObservedAt` do achado | consulta que alimenta o painel | usar `FirstObservedAt` do achado, não `criado_em` do ticket, como início do prazo |
| Mesmo tipo de achado gera ticket toda semana, sempre falso positivo | regra ruidosa para o ambiente específico, sem exceção configurada | conte tickets fechados como "não é achado real", agrupados por tipo | histórico de fechamento de ticket, agrupado por `tipo` | suprimir aquele tipo específico de achado — não desligar o detector inteiro |
A pergunta que resolve metade destes casos
Antes de mexer em regra ou permissão, pergunte: o achado chegou até o Security Hub? Se sim, chegou ao EventBridge? Se sim, a função foi invocada? Cada "sim" isola uma etapa inteira da investigação — a maioria dos problemas mora numa etapa só, e testá-las em ordem é mais rápido que revisar o desenho inteiro de uma vez.
Limpeza: o que o destroy não leva
Este laboratório não cria recurso que cobra parado por hora — nenhum NAT Gateway, nenhum balanceador. O que sobrevive ao destroy é DADO: achado retido e ticket aberto.
#!/usr/bin/env bash
# limpar.sh — a ordem importa; achado do GuardDuty sobrevive ao Terraform
set -euo pipefail
PROJETO="${PROJETO:?defina PROJETO}"
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. CONFORMIDADE DE ACHADOS: os 90 dias de retencao SO valem com o detector
# ATIVO ou SUSPENSO. Deletar o detector — que e o que este destroy faz —
# PERDE os achados existentes; a AWS e explicita que a configuracao e os
# achados "sao perdidos e nao podem ser recuperados". Se voce precisa do
# historico, exporte para S3 ANTES do destroy; depois dele, nao ha achado
# nenhum para consultar, retido ou nao.
echo "ATENCAO: deletar o detector do GuardDuty PERDE os achados existentes"
echo "— a retencao de 90 dias so vale com o detector vivo. Exporte para S3"
echo "ANTES deste destroy se o historico importa; depois, nao ha mais o que buscar."
# 3. BUCKET DO CONFIG: guarda snapshot de configuracao e nao esvazia sozinho.
aws s3 rm "s3://${PROJETO}-config" --recursive 2>/dev/null || true
aws s3api delete-bucket --bucket "${PROJETO}-config" 2>/dev/null || true
# 4. PADRAO DO SECURITY HUB: a assinatura do padrao (FSBP) para de rodar
# verificacao com o destroy, mas confirme — verificacao ja em andamento
# no momento do destroy pode ser cobrada.
aws securityhub get-enabled-standards --query 'StandardsSubscriptions' --output table 2>/dev/null || \
echo "Security Hub ja desabilitado nesta conta/regiao."
# 5. TABELA DE TICKETS: contem achado ABERTO sem resolucao. Exporte antes de
# apagar, ou a rastreabilidade que este laboratorio existe para criar
# desaparece junto com a tabela.
aws dynamodb scan --table-name "${PROJETO}-tickets-seguranca" \
--query 'Items[?status.S==`ABERTO`]' --output json > tickets-abertos-backup.json 2>/dev/null || true
# 6. Prova final: nada com o nome do projeto de pe.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=${PROJETO} \
--query "ResourceTagMappingList[].ResourceARN" --output table
| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Detector do GuardDuty | sim | não | achados já gerados são PERDIDOS junto — 90 dias só vale com o detector vivo; exporte para S3 antes do destroy se o histórico importa |
| Recorder e regra do Config | sim | não | item de configuração já gravado permanece no bucket até ser removido |
| Bucket de snapshot do Config | não | sim, GB-mês | buckets S3 não são removidos por padrão pelo destroy quando contêm objeto |
| Assinatura de padrão do Security Hub | sim | não | para de rodar verificação; nenhuma cobrança após o destroy |
| Tabela de tickets (DynamoDB) | sim | não | mas o CONTEÚDO — tickets abertos, histórico de tratamento — some com ela |
| Máquina de estados e regra do EventBridge | sim | não | nada a destacar; sem estado persistente próprio |
Exporte antes de apagar, não depois
GuardDuty retém achado por até 90 dias, mas só ENQUANTO o detector existir — apagar o detector (o que este `destroy` faz) perde os achados junto, antes mesmo do prazo. A tabela de tickets some inteira com o `destroy` do mesmo jeito. Se este laboratório fica no ar por tempo suficiente para acumular achado real, exporte o histórico ANTES de destruir — não existe forma de recuperar depois.
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Achado sem prazo nem dono | ticket em DynamoDB, prazo calculado pela severidade | processo, não boa vontade — o prazo nasce com o registro, não depende de alguém lembrar |
| Dois tipos de achado em dois caminhos separados | Security Hub agregando GuardDuty e Config | uma regra do EventBridge serve para qualquer origem, porque ambos chegam normalizados em ASFF |
| Toda severidade tratada igual | SLA por Severity.Label, LOW/INFORMATIONAL fora da fila individual | evita fadiga de alerta sem perder o achado de baixa severidade — ele vai para o relatório |
| Ação irreversível decidida na pressa | Step Functions parando em aprovação humana | a equipe de duas pessoas decidiu explicitamente errar para o lado de aprovar manualmente |
| Severidade escalada sem re-triagem | chave composta achado_id#severidade | o mesmo achado, mais grave, precisa de um registro novo — não pode colidir com o antigo |
| Config só pega desvio até 24h depois | gatilho de mudança de configuração como principal | pega o desvio no instante em que acontece; periódico vira só a rede de segurança |
- GuardDuty detecta comportamento anômalo, ou Config avalia configuração contra regra.
- O achado chega ao Security Hub e é normalizado em ASFF, com Severity.Label e Workflow.Status.
- Security Hub publica "Security Hub Findings - Imported" no EventBridge.
- A regra casa achado CRITICAL ou HIGH com Workflow.Status igual a NEW.
- A função de ticket calcula o prazo a partir da severidade e grava o registro no DynamoDB.
- Para achado CRITICAL de tipo automatizável, a função inicia o fluxo de aprovação.
- Step Functions pausa, esperando um analista aprovar ou rejeitar a contenção.
- O painel recalcula, por severidade, quantos tickets cumpriram o prazo e quantos não.
- Se a mesma origem reavaliar o achado com severidade maior, um segundo ticket nasce, com prazo mais curto.
Perguntas frequentes
❓ GuardDuty, Security Hub e AWS Config fazem a mesma coisa?
❓ Por que um achado de segurança por e-mail é pior do que não ter detecção nenhuma?
❓ Todo achado do GuardDuty deveria virar um ticket individual?
❓ AWS Config detecta um bucket S3 que ficou público assim que isso acontece?
❓ O que é o formato ASFF, e por que o Security Hub o usa?
❓ Automatizar a criação do ticket também deveria automatizar a remediação?
❓ Por que a severidade de um achado do GuardDuty pode mudar depois de criado?
❓ Preciso de administração delegada multiconta para este laboratório funcionar?
Fixando
Um achado nasce como MEDIUM e a função de ticket cria um registro com prazo de 1 dia útil. Duas horas depois, o GuardDuty reavalia o mesmo achado (mesmo identificador) com evidência nova, e o Security Hub o reimporta como CRITICAL. O ticket original continua com o prazo de 1 dia útil, sem nenhuma atualização. Qual é a causa mais provável?
A equipe da Cadência considera automatizar totalmente a resposta a todo achado CRITICAL — incluindo terminar a instância comprometida sem aprovação — para reduzir o tempo de contenção. O desenho deste laboratório exige aprovação humana para toda ação irreversível. Qual é o risco mais direto de abandonar essa exigência?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L41 concluído (menor privilégio e Access Analyzer), CloudTrail habilitado, Terraform básico |
| Conhecimentos adquiridos | o papel distinto de GuardDuty, Config e Security Hub; o formato ASFF como o que permite uma regra servir para qualquer origem; SLA derivado da severidade, não uniforme; o freio de aprovação humana para ação irreversível; e por que dedupe por identificador puro esconde escalada de severidade |
| Limitação que fica | roda numa conta só, sem agregação multiconta; e nem todo tipo de achado CRITICAL tem runbook de contenção formal — os automatizáveis entram no fluxo de aprovação, os demais viram ticket sem próximo passo definido |
| Próximo exemplo recomendado | L50 — resposta a incidente e blast radius. Consome o ticket que este laboratório cria como ponto de partida da investigação, e formaliza a contenção com tempo cronometrado |
| Também habilitado por este módulo | L43 (multiconta com SCP) pode reutilizar a mesma automação com administração delegada do Security Hub; L52 (alarme que acorda alguém) é o complemento natural para achado CRITICAL fora do expediente; L96 (agente de operação) é onde IA soma evidência de múltiplos achados sem decidir sozinha |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: Processing GuardDuty findings with Amazon EventBridge e a documentação de retenção de achados do GuardDuty — a janela de até 90 dias contada da geração, válida só com o detector ativo ou suspenso — deletar o detector perde os achados junto; Adding, Updating, and Deleting AWS Config Rules — os dois tipos de gatilho de regra (mudança de configuração e periódico) e a frequência padrão de 24 horas do gatilho periódico; e AWS Security Hub events — Amazon EventBridge — o evento "Security Hub Findings - Imported", confirmado como disparado tanto por `BatchImportFindings` (achado novo) quanto por `BatchUpdateFindings` (achado atualizado). Os valores de preço não aparecem neste módulo por decisão: use o AWS Pricing Calculator, porque preço varia por região e envelhece mais rápido que o conteúdo.
O que não foi verificado, e você deve conferir na sua conta
O SLA por severidade (15 min para CRITICAL, 1h para HIGH, e os demais) é uma política de processo que a Cadência declarou para este laboratório, não um valor que a AWS define ou recomenda — derive o seu da capacidade real da sua equipe de responder, não copie o número. A lista de tipos de achado considerados "automatizáveis" no código do Lambda também é um exemplo didático: decidir quais tipos merecem fluxo de contenção automatizado (mesmo com aprovação) é uma decisão de segurança da sua organização, e deve ser revisada por quem responde por ela antes de qualquer uso real.
Terminou de ler?
Marcar como concluído registra o XP, mantém sua sequência e coloca 3 cartas deste módulo na fila de revisão espaçada.
Próximos passos sugeridos
Temas deste módulo
Discussão
Carregando comentários…