Lab 49 — Dado pessoal: minimizar, mascarar, não logar
O problema, e a empresa que o tem
A Cadência é a mesma equipe de duas pessoas dos laboratórios anteriores. Há três meses, o faturamento passou a exigir CPF no cadastro de pedido — requisito fiscal, não capricho. O campo entrou no tipo Pedido, dentro de Cliente, e ninguém revisou o que isso mudava no resto do sistema.
O que mudou, sem ninguém decidir, foi o log. A rota de criação de pedido sempre registrou log.LogInformation("Pedido criado {@Pedido}", pedido) — e o operador de destructuring do Serilog serializa o objeto inteiro. Com o CPF dentro de Cliente, ele passou a estar em todo evento de log desde então, num grupo com retenção de 90 dias.
Ninguém percebeu até um estagiário, preparando um levantamento interno sobre dado pessoal, rodar o Amazon Macie pela primeira vez contra uma cópia exportada dos logs. O achado: milhares de ocorrências de CPF, em produção, há três meses. Não houve vazamento externo — mas a pergunta "quem já leu isso?" não tinha resposta boa, porque a política de leitura do grupo de logs sempre foi mais ampla que a do banco.
O que este laboratório NÃO é
Não é certificação de conformidade legal. Minimização de dado é um princípio técnico presente tanto na LGPD (art. 6º, inciso III) quanto no GDPR (art. 5º, 1.c) — mas conformidade de verdade depende de base legal do tratamento, finalidade declarada e avaliação jurídica que este módulo não faz e não substitui. O que ele entrega é a engenharia: como um dado pessoal deixa de aparecer onde não devia, e como provar isso.
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.
- Explicar por que o Macie não encontra CPF que está apenas no CloudWatch Logs, sem nunca ter sido exportado.
- Nomear a ordem de decisão — minimizar antes de mascarar antes de proteger — e dizer o que cada etapa resolve que a anterior não resolve.
- Projetar uma allowlist de campo de log que impede CPF de aparecer por padrão, mesmo quando o domínio ganha um campo novo.
- Configurar retenção e exportação de um grupo de logs de forma deliberada, e explicar por que o padrão da AWS é reter para sempre.
- Rodar uma varredura do Macie sobre uma amostra real e interpretar um achado do identificador BRAZIL_CPF_NUMBER.
- Diagnosticar um job do Macie que conclui sem achado por falta de permissão de descriptografia na CMK.
- Restringir a leitura do grupo de logs a um grupo não maior que o que lê o banco de origem do dado.
- Diferenciar mascaramento reversível de anonimização, e dizer quando cada um se aplica.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Classificação de dado sensível | SAP-C02 | Macie com identificador gerenciado BRAZIL_CPF_NUMBER | que identificadores gerenciados cobrem tipo e país; identificador customizado é para padrão próprio da empresa |
| Minimização de dado | SAP-C02 | allowlist de campo na escrita do log | minimizar é decisão de CÓDIGO, não de infraestrutura |
| Detecção vs prevenção | SAP-C02 | Macie como detecção, allowlist como prevenção | scanner que roda depois do fato não impede o fato — só o revela mais cedo |
| Retenção de log como decisão | SAP-C02, DOP-C02 | `retention_in_days` explícito em vez do padrão indefinido | o padrão da AWS é reter para sempre até alguém configurar o contrário |
| Escopo de varredura do Macie | SAP-C02 | Macie varre S3, não CloudWatch Logs diretamente | por que uma etapa de exportação é obrigatória no desenho, não opcional |
| Cifra em repouso do log | SAP-C02, SAA-C03 | grupo de logs com a CMK do L46 | cifrar não é o mesmo problema que decidir o que entra no log |
| Amostragem vs varredura completa | SAP-C02 | descoberta automatizada amostra; job agendado pode cobrir mais | em alto volume, achado zero da descoberta automatizada não é garantia absoluta |
| Mascaramento vs anonimização | SAP-C02 | CPF parcial permanece correlacionável a uma pessoa | dado mascarado normalmente ainda é dado pessoal, não dado anônimo |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve um cenário com Macie configurado e "zero achados" e pergunta se o ambiente está seguro. A resposta certa é que não se pode concluir isso sem mais informação: pode ser que a fonte nunca tenha sido exportada para S3, que a CMK não conceda descriptografia ao Macie, ou que o job simplesmente nunca tenha rodado. Achado zero só vale alguma coisa quando se sabe que a varredura de fato leu o conteúdo.
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 |
|---|---|---|
| CPF não pode aparecer em nenhum log de aplicação | obrigatório, ponto de partida | obriga tipo de log projetado por allowlist, não filtro pós-fato |
| Suporte precisa localizar pedido a partir de CPF informado por telefone | caso de uso real, não hipotético | autoriza um campo DERIVADO — CPF mascarado — só onde essa necessidade existe, não em todo log |
| Auditoria pede prova recorrente, não pontual | mensal, para relatório interno | obriga descoberta automatizada/agendada, não varredura manual esquecível |
| Log continua útil para diagnosticar incidente | não pode virar log vazio | a allowlist inclui Id, Loja, Valor e Status — o que resolve incidente sem expor pessoa |
| Leitura do log não pode ser mais ampla que leitura do banco | mesmo grupo de acesso | política de leitura do grupo de logs espelha a política do banco de origem |
| Cifra em repouso do log | herdada do L46 | reutiliza a CMK existente; não cria uma chave nova por domínio de dado |
| Bucket de exportação não é armazenamento permanente | ciclo de vida curto | expira em 7 dias — existe só para o Macie enxergar, não para guardar |
Arquitetura mínima: o CPF que ninguém decidiu logar
Este é o desenho que a Cadência tem hoje, e ele é o ponto de partida honesto: a aplicação funciona, o log é útil para debug, e o defeito é invisível até alguém procurar por ele — que é exatamente o que este laboratório começa fazendo.
- → cadastro com CPF
- → log estruturado da entidade completa (inclui CPF)
- → logs:GetLogEvents concedido a um grupo amplo
- → create-export-task (evento único, sob demanda)
- → objeto exportado, alvo do job de descoberta
- → achado informa; não revoga leitura que já ocorreu
- Fora da AWS
- Compute
- Gestão e governança
- Segurança e identidade
- Armazenamento
Este desenho publica de verdade e o defeito é visível: o CPF chega ao log porque ninguém filtrou, fica retido pelos 90 dias do GRUPO DE LOGS — não do banco —, e só aparece como achado depois que alguém pensa em exportar uma cópia e rodar o Macie sobre ela. Percorra os passos e repare que o Macie não enxerga o CloudWatch Logs: ele só varre S3.
- O CPF chega como dado de negócio legítimo. O cadastro de pedido precisa de CPF por motivo fiscal ou de identificação de cliente. O problema nunca é o CPF existir no sistema — é ele aparecer num lugar sem o controle de acesso e a retenção que o dado merece.
- Logar a entidade inteira é o menor número de linhas. Um logger que serializa o objeto com um operador de destructuring (`{@Pedido}`) grava tudo o que o tipo tiver — hoje e no futuro. Ninguém decidiu logar o CPF; ninguém decidiu nada. O tipo ganhou um campo, e o log ganhou junto.
- Quem lê o grupo de logs também lê o CPF. A política de leitura do log costuma ser mais generosa que a do banco — "é só log, para debug". O efeito é um segundo banco de dados de CPF, sem os controles do primeiro.
- Macie não vê o CloudWatch Logs — só vê S3. É o mal-entendido mais comum desta pilha. "Configurar o Macie" sem uma etapa de exportação não encontra nada, porque não há nada em S3 para ele analisar. A exportação não é opcional: é a ponte.
- O job de descoberta classifica o que ninguém filtrou. O identificador gerenciado `BRAZIL_CPF_NUMBER` reconhece o padrão brasileiro com um dígito de proximidade a uma palavra-chave. É detecção pronta, sem escrever um identificador customizado.
- O achado chega tarde: não desfaz as leituras que já aconteceram. Entre o dia em que o CPF entrou no log e o dia em que alguém rodou esta varredura manual, qualquer leitor autorizado — e a lista costuma ser grande — já pôde ler o dado. Detecção não é prevenção; ela só diz que já é tarde.
Antes de corrigir qualquer coisa, prove que o problema existe de verdade. Uma consulta do CloudWatch Logs Insights por padrão de CPF, sobre uma amostra recente, é mais honesta que "acho que tem CPF lá".
# Rode ANTES de qualquer correcao. O numero que sair daqui e a linha de base.
aws logs start-query \
--log-group-name "/ecs/ffv-lab-api" \
--start-time "$(date -d '-1 day' +%s)" --end-time "$(date +%s)" \
--query-string 'fields @timestamp, @message | filter @message like /\d{3}\.\d{3}\.\d{3}-\d{2}/ | stats count() as ocorrencias'
# Na Cadencia: 1.212 eventos analisados, 340 com padrao de CPF — cerca de 28% de
# todo log de criacao de pedido do ultimo dia. Medir corrigiu a suposicao de
# "deve ser raro".Uma vez no log, o CPF não sai com um clique
Não existe API para apagar um evento de log isolado. O CloudWatch Logs só oferece `DeleteLogStream`, que apaga o fluxo INTEIRO, e a exclusão por retenção, que apaga tudo o que ela alcança — nunca um evento específico preservando o resto. A consequência é dura: uma vez que o CPF está escrito, a única correção real é impedir que o PRÓXIMO apareça. "Vou limpar o que já está lá" não é uma opção disponível.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. Se você não conseguir apontar o requisito, a peça é adorno — e este desenho não tem nenhuma.
- → cadastro com CPF
- → log estruturado só com campos da allowlist (CPF nunca é serializado)
- → cifra em repouso com a CMK do L46, reaproveitada
- → logs:GetLogEvents restrito ao mesmo grupo que lê o banco de pedidos
- → aciona a exportação diária (CreateExportTask), sem depender de lembrete
- → objetos exportados, bucket com ciclo de vida de 7 dias
- → descoberta automatizada contínua amostra o inventário do bucket
- → achado publicado via EventBridge → SNS: aqui, achado é regressão
- Fora da AWS
- Compute
- Gestão e governança
- Segurança e identidade
- Integração de apps
- Armazenamento
A troca não é "esconder o CPF no log" — é ele nunca chegar lá. O formatter por allowlist tira a decisão da hora da LEITURA e põe na hora da ESCRITA; a exportação e a descoberta viram rotina agendada, não lembrete; e "achado zero" passa a ser prova recorrente, não afirmação única. Cada peça nova rastreia a um requisito da seção anterior.
- O CPF continua chegando como dado legítimo de negócio. Nada muda do lado do cliente. A diferença inteira está do outro lado da aplicação — em como o dado é tratado a partir do momento em que ela recebe.
- A allowlist decide na escrita, não na leitura. A projeção de log é um TIPO à parte, com só os campos aprovados. O CPF não está ausente por filtro — está ausente porque o tipo que chega ao logger nunca o teve. Um campo novo no domínio não aparece no log até alguém decidir incluí-lo.
- Cifrar não substitui minimizar. A CMK do L46 protege os BYTES do grupo de logs contra leitura fora da AWS. Ela não decide o que entra no log — essa decisão é da allowlist, uma seção acima. As duas camadas resolvem problemas diferentes.
- A lista de quem lê o log não pode crescer sozinha. A política de leitura passa a espelhar a política de acesso ao banco de pedidos. Se um dia alguém pede acesso "só para ver o log", a pergunta certa é se essa pessoa já teria acesso ao banco — se não teria, o log não deveria dar.
- A exportação vira rotina, não lembrete. O agendamento diário substitui a memória de uma pessoa. Ele roda mesmo quando ninguém está pensando em auditoria — e é isso que torna a prova recorrente em vez de pontual.
- A descoberta é contínua, e amostra — não é garantia unitária. A descoberta automatizada reavalia o inventário do bucket continuamente, com técnica de amostragem que a documentação não expressa como percentual fixo. Em volume alto, "zero achado" da automatizada é sinal forte, não certeza absoluta — para garantia mais forte, um job agendado com profundidade maior cobre mais.
- Achado aqui é regressão, e alguém precisa ver isso rápido. Diferente do desenho mínimo, onde o achado era a DESCOBERTA do problema, aqui ele é a exceção: se aparecer, algo que devia estar filtrado passou. O alerta existe para que essa exceção não durma numa tela que ninguém olha.
A diferença estrutural em relação ao desenho anterior não é "adicionar cifra": é mover a decisão do momento da LEITURA (Macie encontra e alguém reage) para o momento da ESCRITA (a allowlist nunca deixa o dado sair do domínio). O Macie continua no desenho — mas como segunda camada, provando recorrentemente que a primeira funciona.
A correção com maior efeito por linha alterada
Trocar `log.LogInformation("Pedido criado {@Pedido}", pedido)` por `pedido.ParaLog()` é uma chamada de método. Ela resolve a causa raiz — o CPF para de existir no que chega ao logger — enquanto cifra, política de leitura e varredura continuam sendo camadas necessárias, mas para o dado que ainda assim aparecer por engano em outro lugar do sistema.
O caminho de um CPF, do cadastro ao achado — ou à ausência dele
O nome de cada etapa não é jargão: é onde uma decisão específica acontece. Perder de vista qual etapa é responsável pelo quê é o que faz alguém tentar "corrigir" no Macie um problema que só se corrige no código da aplicação.
A consequência que mais gente erra: Macie não vê CloudWatch Logs
É o mal-entendido mais comum desta pilha, e vale repetir com precisão: o Amazon Macie descobre dado sensível em objetos do Amazon S3. Ele não tem integração que leia CloudWatch Logs diretamente. Qualquer desenho que "configura o Macie sobre os logs" sem uma etapa explícita de exportação está descrevendo um sistema que nunca vai encontrar nada — não porque o log está limpo, mas porque o Macie nunca o viu.
// Esquema SIMPLIFICADO de um finding de sensitive data do Macie, ilustrativo — os
// campos completos estao na referencia de findings da documentacao oficial.
{
"type": "SensitiveData:S3Object/Personal",
"category": "SENSITIVE_DATA",
"resourcesAffected": {
"s3Object": {
"bucket": { "name": "ffv-lab-export-logs-macie" },
"key": "ecs/ffv-lab-api/2026/08/07/log-export.gz"
}
},
"classificationDetails": {
"result": {
"status": "COMPLETE",
"sensitiveData": [{
"category": "PERSONAL_INFORMATION",
"detections": [
// BRAZIL_CPF_NUMBER e identificador GERENCIADO — nao exige regex propria.
{ "type": "BRAZIL_CPF_NUMBER", "count": 340 }
]
}]
}
}
}As decisões, e o que se perde em cada uma
📋 A Cadência precisa provar, de forma recorrente e auditável, que nenhum CPF aparece nos logs de uma API que atende 30 lojas — sem transformar isso num processo manual que alguém esquece de rodar.
A allowlist impede o CPF de existir no log; o Macie prova, todo dia, que a allowlist continua funcionando conforme o código evolui. Um sem o outro deixa um buraco: só allowlist não tem prova recorrente e auditável para quem pergunta "como você sabe que está limpo hoje?"; só Macie detecta depois que o dado já foi escrito e possivelmente lido por qualquer leitor autorizado do grupo de logs — que costuma ser mais gente do que quem acessa o banco de origem. Detecção sem prevenção é sempre tardia; prevenção sem detecção é sempre não verificada.
Alt: Só Macie, sem allowlist — Detecta depois do fato. Toda leitura que aconteceu entre a escrita do CPF e o dia em que alguém rodou (ou o agendamento rodou) a varredura já ocorreu sem nenhuma proteção — o achado só confirma o estrago, não evita.
Alt: Só allowlist, sem Macie — Sem prova recorrente de que a allowlist continua correta. Um campo novo no domínio, adicionado seis meses depois por outra pessoa, pode escapar da projeção sem que ninguém perceba até uma auditoria manual.
Alt: Mascaramento por regex pós-serialização, sem allowlist nem Macie — É uma lista de BLOQUEIO: cobre só o padrão de CPF previsto na regex, e qualquer variação de formato ou campo pessoal diferente passa despercebida — e sem Macie, ninguém percebe quando ela falha.
Alt: Varredura manual esporádica, quando alguém lembra — É o desenho mínimo deste laboratório, e ele é honesto sobre o que é: prova pontual, não recorrente. Serve para descobrir o problema uma vez, não para garantir que ele não volte.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Ordem de defesa | minimizar → mascarar → proteger, nessa ordem | proteger primeiro (só cifra); mascarar primeiro (regex pós-fato) | cada camada resolve o que a anterior não resolve; invertida, gasta-se em cifra um dado que não devia existir | nenhuma — é a ordem que o próprio catálogo (L46 → L49) segue |
| Tipo de descoberta | automatizada (contínua, por amostragem) | job SCHEDULED com profundidade maior; job ONE_TIME | cobre o caso de uso recorrente sem o custo de analisar 100% a cada ciclo | não é garantia absoluta em alto volume — mitigado pela prevenção na escrita |
| Retenção do log | 90 dias, explícita | padrão indefinido; retenção curta de 7 dias | atende ao caso real de suporte investigar um incidente antigo | qualquer CPF que passe pela allowlist por falha também fica retido 90 dias |
| Onde mora a allowlist | tipo de log dedicado no código | filtro de infraestrutura (ex.: subscription filter com regex) | decisão de código é revisada em pull request; filtro de infraestrutura é mais fácil de esquecer | exige disciplina de manter o tipo atualizado quando o domínio muda |
| Escopo da política de leitura | espelha o acesso ao banco de origem | acesso amplo "para facilitar debug" | o log não deveria ser mais fácil de ler que o dado original | debug ocasional passa a exigir pedir acesso, em vez de já ter |
| CMK usada | a mesma do L46, reutilizada | chave nova dedicada a este laboratório | a granularidade certa é por domínio de proteção, não por módulo de ensino | exige atualizar a política da chave existente em vez de partir do zero |
A dívida que este módulo não paga
A allowlist protege o caminho que ela cobre — o log de criação e de erro do pedido. Um endpoint novo, escrito por outra pessoa, que loga sua própria entidade sem passar pela mesma disciplina, cria o problema de novo em outro lugar. Governança de todo o acervo de dado — não só deste serviço — é o L69, e ele depende deste.
Construir: grupo de logs cifrado, exportação e descoberta contínua
Três coisas nesta pilha resolvem problemas diferentes, e por isso estão em recursos diferentes: a cifra protege os bytes, a retenção decide por quanto tempo eles existem, e a descoberta prova recorrentemente que o conteúdo está limpo.
# logs-macie.tf — grupo de logs cifrado, exportação diária e descoberta contínua
# A CMK do L46, NÃO uma chave nova. Rotacionar chave por laboratório multiplica
# o número de chaves sem multiplicar a protecao — a granularidade certa e por
# DOMINIO de dado (o log de aplicacao da Cadencia), nao por modulo de ensino.
data "aws_kms_key" "logs" {
key_id = "alias/${var.projeto}-app-cmk"
}
resource "aws_cloudwatch_log_group" "api" {
name = "/ecs/${var.projeto}-api"
# Decisao de negocio, nao padrao tecnico: o padrao da AWS e reter para
# sempre ate alguem configurar isto. 90 dias atende o caso real de suporte
# investigar um pedido antigo, sem virar arquivo permanente.
retention_in_days = 90
kms_key_id = data.aws_kms_key.logs.arn
tags = { Projeto = var.projeto }
}
# Este statement e o que resolve a falha mais silenciosa desta pilha: sem ele,
# o Macie consegue ENUMERAR os objetos do bucket mas nao DESCRIPTOGRAFAR o
# conteudo, e um job roda, conclui "COMPLETE" e nao encontra nada — nao porque
# o log esta limpo, mas porque ele nunca leu o conteudo.
data "aws_iam_policy_document" "cmk_permite_macie" {
statement {
sid = "PermiteMacieDescriptografar"
effect = "Allow"
principals {
type = "Service"
identifiers = ["macie.amazonaws.com"]
}
actions = ["kms:Decrypt", "kms:DescribeKey"]
resources = ["*"] # a policy de recurso vive na propria chave; o recurso aqui e implicito
condition {
test = "StringEquals"
variable = "aws:SourceAccount"
values = [data.aws_caller_identity.atual.account_id]
}
}
}
# O bucket NAO e armazenamento: e uma copia de trabalho para a varredura
# enxergar o log. Macie varre S3, nunca CloudWatch Logs diretamente — e essa
# frase sozinha resolve a duvida mais comum sobre por que "configurar o
# Macie" nao encontrou nada.
resource "aws_s3_bucket" "export_logs" {
bucket = "${var.projeto}-export-logs-macie"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "export_logs" {
bucket = aws_s3_bucket.export_logs.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = data.aws_kms_key.logs.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "export_logs" {
bucket = aws_s3_bucket.export_logs.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "export_logs" {
bucket = aws_s3_bucket.export_logs.id
rule {
id = "expira-rapido"
status = "Enabled"
# Mante-lo mais que alguns dias duplica a superficie que a varredura
# deveria estar REDUZINDO, nao ampliando. 7 dias cobre folga para o job
# diario rodar de novo se o primeiro ciclo falhar.
expiration { days = 7 }
}
}
# Habilita a conta no Macie. ATENCAO na secao de limpeza: este recurso e por
# CONTA, nao por laboratorio — destrui-lo desabilita o Macie inteiro.
resource "aws_macie2_account" "conta" {
finding_publishing_frequency = "FIFTEEN_MINUTES"
status = "ENABLED"
}
# Descoberta AUTOMATIZADA, nao job avulso: o Macie reavalia continuamente o
# inventario do bucket e amostra objetos, sem que ninguem precise lembrar de
# disparar uma varredura. "Zero achado" aqui e resultado esperado do dia a
# dia — achado POSITIVO e a excecao que merece alerta.
resource "aws_macie2_account" "descoberta_automatizada" {
depends_on = [aws_macie2_account.conta]
# A configuracao fina de amostragem e escopo do bucket fica no console ou
# na API de automated-discovery-configuration; o Terraform provider cobre a
# habilitacao da conta, que e o pre-requisito.
}
# A engrenagem que substitui "alguem lembra de exportar": todo dia, sem
# depender de disciplina humana, o log do dia anterior vira objeto em S3.
resource "aws_scheduler_schedule" "exportar_logs" {
name = "${var.projeto}-exportar-logs-diario"
group_name = "default"
flexible_time_window { mode = "OFF" }
schedule_expression = "cron(0 3 * * ? *)" # 03h UTC, fora do pico da Cadencia
target {
arn = aws_lambda_function.exportar_log.arn # cria a export-task do dia anterior
role_arn = aws_iam_role.scheduler_exportar.arn
}
}
# Publica achados no EventBridge, de onde um alarme os leva a um topico SNS.
# "Achado" aqui e regressao: alguem escreveu um campo pessoal fora da
# allowlist, e o alerta precisa aparecer antes que vire habito.
resource "aws_sns_topic" "achado_pii" {
name = "${var.projeto}-achado-pii-log"
kms_master_key_id = data.aws_kms_key.logs.id
}
output "bucket_export_logs" {
value = aws_s3_bucket.export_logs.bucket
description = "onde a descoberta automatizada do Macie procura o log exportado"
}
A CMK que bloqueia sem avisar
Se o bucket de exportação é cifrado com uma CMK própria, o Macie só consegue DESCRIPTOGRAFAR e classificar o conteúdo se a política da chave conceder `kms:Decrypt` ao papel vinculado ao serviço dele. Sem essa permissão, o Macie reporta apenas metadado do objeto — tamanho, contagem — e o job conclui como `COMPLETE` sem nunca ter lido uma linha. "Zero achado" nesse caso é ausência de leitura, não ausência de dado, e é a falha mais silenciosa desta pilha inteira.
Construir: quem pode ler o grupo de logs
Minimizar o que entra no log resolve metade do problema. A outra metade é quem tem permissão de ler o que sobrou — e essa política costuma ser escrita antes de o log conter dado pessoal, e nunca revisada depois que passou a conter.
# iam-logs.tf — a leitura do log nao pode ser mais ampla que a leitura do banco
data "aws_iam_policy_document" "leitura_logs_api" {
statement {
effect = "Allow"
actions = [
"logs:GetLogEvents",
"logs:FilterLogEvents",
"logs:DescribeLogStreams",
"logs:StartQuery",
"logs:GetQueryResults",
]
resources = ["${aws_cloudwatch_log_group.api.arn}:*"]
# Nenhuma acao aqui e de conta — todas suportam recurso especifico, e por
# isso NENHUMA usa "*". Se algum dia precisar de uma que so aceite conta
# (como logs:DescribeLogGroups em alguns cenarios), a regra da casa vale:
# a frase que justifica o "*" entra como comentario, ou ele nao entra.
}
}
# O TESTE que decide se a politica esta certa: liste quem ela autoriza e
# compare com a lista de quem le o banco de pedidos de origem. Se a lista de
# leitores do LOG e maior que a lista de leitores do BANCO, o log e uma copia
# menos protegida do mesmo dado — e minimizar no codigo nao resolve isso
# sozinho, porque o problema deixou de ser "o que esta no log" e passou a ser
# "quem pode ler o que esta no log".
resource "aws_iam_role" "leitor_logs_api" {
name = "${var.projeto}-leitor-logs-api"
assume_role_policy = data.aws_iam_policy_document.assume_leitor.json
# Este papel e concedido ao MESMO grupo de pessoas que ja tem leitura no
# banco de pedidos — nao a "todo mundo no time de plantao".
}
resource "aws_iam_role_policy" "leitura_logs_api" {
role = aws_iam_role.leitor_logs_api.id
policy = data.aws_iam_policy_document.leitura_logs_api.json
}
# Algumas acoes do Macie SAO de conta, nao de recurso — confira no IAM
# Policy Reference antes de copiar um "*" as cegas. Aqui, a acao que provisiona
# a descoberta automatizada e de conta, e por isso a unica que aparece com
# recurso amplo nesta pilha inteira.
data "aws_iam_policy_document" "macie_conta" {
statement {
effect = "Allow"
actions = ["macie2:EnableMacie", "macie2:GetAutomatedDiscoveryConfiguration"]
resources = ["*"]
}
}
O "*" que aparece na política, e por que ele se justifica
Nenhuma ação de leitura de log desta política usa `Resource: "*"` — todas aceitam o ARN do grupo específico. O `*` só aparece na política do Macie, para ações que são de CONTA (habilitar o serviço, ler a configuração de descoberta automatizada) e não suportam recurso individual. A regra não muda por serviço: todo `*` carrega a frase que explica por que não pode ser mais estreito, ou não deveria estar ali.
Construir: a allowlist que decide na escrita, não na leitura
O tipo de log é a allowlist. Ele não filtra o objeto de domínio — ele simplesmente não tem os campos que não deveriam chegar ao log, e por isso não existe forma de eles "escaparem" por um logger que outra pessoa escreva no futuro.
// LogSeguro.cs — a allowlist que decide o que chega ao log, na ESCRITA
public sealed record PedidoLogSeguro(
Guid Id,
string Loja,
decimal Valor,
string StatusPagamento);
// Repare no que NAO esta aqui: CpfCliente, NomeCliente, Endereco,
// Telefone. A ausencia E a allowlist — o campo nao existe no TIPO, entao
// nao existe forma de ele "escapar" por um logger novo que outra pessoa
// escreva daqui a seis meses. Bloqueio por lista de permissao e mais
// seguro por padrao que filtrar depois: quem esquece de atualizar o
// filtro vaza; quem esquece de atualizar a allowlist nao loga nada novo
// ate decidir explicitamente.
public static class PedidoLogSeguroExtensions
{
public static PedidoLogSeguro ParaLog(this Pedido pedido) =>
new(pedido.Id, pedido.Loja, pedido.Valor, pedido.StatusPagamento.ToString());
}
public sealed class PedidosController(ILogger<PedidosController> log, AppDb db)
{
public async Task<IResult> Criar(Pedido pedido)
{
await db.Pedidos.AddAsync(pedido);
await db.SaveChangesAsync();
// ANTES (o antipadrao que este laboratorio corrige):
// log.LogInformation("Pedido criado {@Pedido}", pedido);
// O operador `@` do Serilog SERIALIZA O OBJETO INTEIRO, incluindo
// Cliente, que tem Cpf. Ninguem decidiu logar o CPF — ninguem
// decidiu NADA: o destructuring pegou tudo o que existia no tipo
// no dia em que a linha foi escrita, e continua pegando tudo o que
// existir nele no futuro, sem revisao de codigo nenhuma.
//
// DEPOIS: a projecao e o UNICO caminho ate o logger.
log.LogInformation("Pedido criado {@Pedido}", pedido.ParaLog());
return Results.Created($"/api/pedidos/{pedido.Id}", pedido);
}
// O MESMO cuidado vale no caminho de erro — e e o que a secao de falhas
// deste modulo mostra ser o mais esquecido.
public async Task<IResult> TratarFalha(Exception ex, Pedido pedido)
{
// ERRADO: log.LogError(ex, "Falha ao processar {@Pedido}", pedido);
// Handler de excecao e o lugar onde mais se loga o objeto inteiro
// "para ter contexto" — e e exatamente onde a allowlist tem de
// valer tambem, sem excecao para o caminho de erro.
log.LogError(ex, "Falha ao processar pedido {@Pedido}", pedido.ParaLog());
return Results.Problem();
}
}
O caminho de erro é o mais esquecido, e o mais perigoso
É comum blindar o log do caminho feliz e deixar o handler de exceção logando o objeto inteiro "para ter contexto ao depurar". É exatamente onde o CPF mais aparece em produção: erro é o momento em que alguém quer o máximo de informação possível, e esquece que a mesma disciplina da allowlist se aplica ali também.
Construir: quando o dado parcial ainda serve
Existe um caso de uso real na Cadência para localizar um pedido a partir do CPF que o cliente informa por telefone. Isso autoriza um campo DERIVADO, mascarado — não o campo bruto, e não em todo lugar que loga um pedido.
// Mascaramento.cs — quando o dado parcial ainda serve, e quando nao serve
public static class Mascaramento
{
// Usar SOMENTE quando alguem prova um caso de uso real — aqui, suporte
// precisando confirmar um pedido a partir do CPF que o cliente informou
// por telefone. "Pode ser util algum dia" nao e prova; e a justificativa
// que qualquer campo indevido usa.
//
// Mantem os 3 ultimos digitos, suficientes para confirmacao humana
// cruzada com outro dado (nome, numero do pedido), e derruba o resto.
public static string Mascarar(string cpf)
{
var digitos = new string(cpf.Where(char.IsDigit).ToArray());
if (digitos.Length != 11) return "***.***.***-**"; // formato inesperado: nao arrisca expor
return $"***.***.{digitos[6..9]}-**";
}
}
// O CAMPO DERIVADO entra na allowlist, nunca o campo bruto — e so nos tipos
// de log que genuinamente precisam dele, nao em todo lugar que loga Pedido.
public sealed record PedidoLogComSuporte(
Guid Id, string Loja, decimal Valor, string CpfMascarado);
public static class PedidoLogComSuporteExtensions
{
public static PedidoLogComSuporte ParaLogDeSuporte(this Pedido pedido) =>
new(pedido.Id, pedido.Loja, pedido.Valor,
Mascaramento.Mascarar(pedido.Cliente.Cpf));
// O valor BRUTO do Cpf nunca sai do escopo de Pedido para o de
// log — ele e consumido aqui dentro e descartado.
}
// IMPORTANTE, e e por isso que este mascaramento tem sua propria secao no
// modulo: "***.***.789-**" AINDA e dado pessoal. Ele e correlacionavel a uma
// pessoa por quem ja tem outro dado dela (nome, pedido, telefone). Tratar
// mascarado como anonimo e decisao de risco disfarcada de decisao tecnica —
// use o mesmo controle de acesso e retencao que valeria para o CPF completo.
Mascarado não é anônimo
`***.***.789-**` ainda é dado pessoal: qualquer pessoa que já tenha outro dado do cliente — nome, número do pedido, telefone — pode correlacionar o resto. Anonimização de verdade exige que a reidentificação deixe de ser possível, o que é um padrão muito mais alto do que mascarar dígitos. Trate o campo mascarado com o MESMO controle de acesso e retenção que valeria para o CPF completo — nunca como "já resolvido".
O tipo errado de projeção nem compila
Como `PedidoLogSeguro` e `PedidoLogComSuporte` são tipos distintos, com campos fixos, um revisor de pull request vê exatamente o que vai para o log só olhando a assinatura do método — não precisa rastrear onde `pedido` é usado no corpo inteiro da função. É uma correção que se verifica em segundos de revisão, não em produção.
Implantar, e provar com número — não com "parece limpo"
Cinco provas. Nenhuma aceita "rodei o Macie e não achou nada" como resultado sozinho — a quarta e a quinta são as que mais gente pula, e são as que decidem se o achado zero significa alguma coisa.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · A linha de base tem CPF de verdade | CloudWatch Logs Insights com regex de CPF | contagem de ocorrências > 0 antes da correção | zero aqui, antes de corrigir nada, significa que o cenário de teste não está reproduzindo o antipadrão |
| 2 · A exportação realmente cria objeto | `aws s3 ls s3://ffv-lab-export-logs-macie/` após o CreateExportTask | objeto novo, com timestamp do dia | ausência de objeto: a tarefa de exportação falhou ou nunca rodou — confira `describe-export-tasks` |
| 3 · O Macie realmente leu o conteúdo | `aws macie2 describe-classification-job` — campo de objetos classificados | contagem de "classificados com sucesso" igual à contagem de objetos no bucket | divergência entre objetos no bucket e objetos classificados aponta para permissão de KMS, não para log limpo |
| 4 · O achado aparece antes da correção | `aws macie2 list-findings` + `get-findings`, filtrando o tipo SensitiveData | ao menos um achado com detecção `BRAZIL_CPF_NUMBER` | zero achado aqui, com a linha de base > 0 confirmada na prova 1, volta à prova 3: o Macie não leu o conteúdo |
| 5 · A correção zera o achado, de novo — não só uma vez | repita cadastro + exportação + varredura após aplicar a allowlist | zero achado, com o mesmo processo de exportação funcionando (prova 2 e 3 continuam passando) | achado remanescente aponta para um caminho que não passa pela projeção — o handler de erro é o suspeito mais comum |
# Prova complementar de seguranca: quem pode ler o grupo de logs?
aws iam simulate-principal-policy \
--policy-source-arn "$(terraform output -raw arn_papel_amplo_de_debug)" \
--action-names logs:GetLogEvents \
--resource-arns "$(terraform output -raw arn_log_group)"
# Esperado: "implicitDeny" para qualquer papel que nao seja o leitor dedicado.
# Se um papel generico de debug retornar "allowed", a politica de leitura ainda
# esta mais ampla do que a do banco de origem — a correcao do codigo nao basta.Quebrar de propósito: três falhas e o diagnóstico
As três parecem, à primeira vista, "o Macie não encontrou nada" — que soa bom. É exatamente por isso que são perigosas: um resultado que parece sucesso pode ser ausência de leitura, não ausência de dado.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| O handler de erro loga o objeto inteiro | force uma exceção no fluxo de criação de pedido e mantenha `{@Pedido}` bruto só no catch | achado do Macie aparece só nos logs de exceção, não nos de sucesso | compare o formatter do caminho feliz com o do handler de exceção | o handler de erro usa a MESMA projeção segura, nunca o objeto bruto |
| A CMK não concede descriptografia ao Macie | remova o statement que autoriza `macie.amazonaws.com` na política da chave | job conclui `COMPLETE`, sem achado, mesmo com CPF garantido no bucket | compare objetos totais do bucket com objetos efetivamente classificados — não só o status do job | adicionar o statement na política da CMK autorizando o papel vinculado ao serviço |
| O agendamento diário falha silenciosamente | desabilite o `aws_scheduler_schedule` sem desabilitar o alarme de achado | zero achado por semanas, e ninguém sabe se é porque está limpo ou porque parou de rodar | histórico de execuções do EventBridge Scheduler, não só o resultado do último achado | alarme de AUSÊNCIA de execução em 24h, não só de achado positivo |
A pergunta que resolve as três
Antes de confiar num "zero achado": o dado NUNCA chegou ao S3, ou chegou e o Macie NÃO CONSEGUIU LER o conteúdo? A primeira é ausência de exportação ou de agendamento; a segunda é quase sempre permissão de chave KMS. Nenhuma das duas é "está limpo".
Depois de configurar o Macie para varrer o bucket de exportação dos logs, o time descobre que ele nunca encontrou o CPF que sabem estar presente há meses no CloudWatch Logs. Qual é a explicação mais provável?
Segurança: o log como um segundo banco de dados
Todo controle que existe para o banco de origem do dado precisa de um equivalente para o log, ou o log é a porta destrancada ao lado da porta trancada.
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| CPF exposto a um grupo de leitores maior que o do banco de origem | alta | alto | política de leitura do grupo de logs restrita ao mesmo grupo que acessa o banco | IAM Access Analyzer / revisão periódica de quem tem `logs:GetLogEvents` | revogar o acesso excedente; tratar como incidente se houve leitura fora do esperado |
| Campo novo do domínio entra no log sem decisão | alta | médio | tipo de log dedicado (allowlist) em vez de serializar a entidade de domínio | Macie recorrente sobre a exportação diária | corrigir a projeção; o ciclo seguinte de varredura precisa sair zero |
| CMK sem permissão para o papel do Macie; descoberta "conclui" sem analisar conteúdo | média | alto | statement explícita na política da chave autorizando o papel vinculado ao serviço | comparar objetos analisados vs. objetos com classificação completa, não só o status do job | corrigir a política da chave; rodar o job de novo antes de confiar em achado zero |
| Bucket de exportação vira cópia esquecida de dado pessoal | média | alto | ciclo de vida de 7 dias e bloqueio de acesso público | métrica de tamanho do bucket ao longo do tempo | expirar objetos manualmente; investigar por que o ciclo de vida não rodou |
| Ambiente de desenvolvimento usa cópia de dado real de produção | média | alto | massa de teste sintética; nunca cópia direta do banco de produção | Macie também configurado sobre buckets e exportações de desenvolvimento | apagar a cópia real; gerar massa sintética |
| Dado mascarado tratado como anonimizado numa decisão de retenção ou compartilhamento | baixa | médio | nomear explicitamente: mascarado ainda é dado pessoal correlacionável | revisão de decisão de compartilhamento externo | reclassificar e aplicar o mesmo controle do dado pessoal, não o de dado anônimo |
A leitura do log não pode ser mais ampla que a leitura do banco
É a falha de maior impacto desta lista, e a mais fácil de deixar acontecer: alguém pede acesso ao grupo de logs "só para debug", ninguém questiona porque parece baixo risco, e meses depois esse papel tem acesso a um CPF que nunca teria acesso via o banco de origem. O log virou um banco de dados paralelo com controle mais frouxo que o original — e ninguém tratou a concessão como tal.
Observabilidade: as perguntas que o painel tem de responder
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| Existe CPF em algum log ativo agora? | findings SensitiveData com detecção BRAZIL_CPF_NUMBER, por período | qualquer achado novo é regressão de código, não ruído | > 0 novos achados dispara alerta |
| A exportação e a descoberta realmente rodaram hoje? | status de execução do EventBridge Scheduler + status do job do Macie | silêncio (zero achado) sem execução confirmada não prova nada | ausência de execução em 24h dispara alerta |
| O Macie está lendo conteúdo, ou só metadado? | objetos analisados com classificação completa vs. total de objetos no bucket | divergência indica problema de permissão de chave (KMS), não ausência de dado | qualquer divergência investigada no mesmo dia |
| Quem leu o grupo de logs recentemente? | CloudTrail em `GetLogEvents` / `FilterLogEvents` sobre o log group | identidade fora do papel autorizado | qualquer identidade fora da lista esperada |
| O bucket de exportação está crescendo além do esperado? | tamanho do bucket ao longo do tempo | ciclo de vida não está expirando objetos | crescimento sem estabilização em 7 dias |
| A allowlist do log cobre os campos certos? | revisão de código no PR + teste que falha se um tipo novo divergir | campo de PII adicionado ao domínio sem atualizar a projeção | qualquer PR que adiciona campo ao domínio sem tocar a projeção de log |
A métrica que engana: "zero achado" sozinha
Um painel que mostra só a contagem de achados do Macie, sem ao lado a confirmação de que exportação e descoberta realmente executaram, transforma "não sabemos" em "está tudo bem" na cabeça de quem olha rápido. As duas perguntas — "achou algo?" e "realmente rodou?" — precisam do mesmo destaque visual.
Escala: 10, 10 mil, 1 milhão, e falha de AZ
| Volume | O que acontece | O que passa a doer | O que fazer |
|---|---|---|---|
| 10 cadastros/dia | exportação e descoberta cobrem essencialmente todo o volume | nada; é o cenário de validação deste laboratório | nada |
| 9 mil cadastros/dia (volume atual da Cadência) | mesmo desenho: a allowlist é uma decisão por linha de código, não por volume | o bucket de exportação cresce proporcionalmente; o ciclo de vida de 7 dias já absorve isso | confirmar que o job diário do Macie termina dentro da janela de 24h |
| 1 milhão de eventos/dia | a descoberta automatizada usa amostragem, não lê cada objeto | achado zero deixa de ser garantia estatística tão forte quanto em baixo volume | complementar com job SCHEDULED de profundidade maior sobre um subconjunto, e confiar mais na prevenção (allowlist) que na detecção nesse regime |
| Falha de AZ | nenhum efeito nos serviços deste laboratório | nada — CloudWatch Logs, S3, KMS, IAM, Macie, EventBridge e SNS são serviços regionais, não amarrados a uma AZ | a única dependência de AZ é a aplicação em si (ECS Fargate), já tratada nos L01–L03 |
O gargalo que só aparece em volume alto: amostragem deixa de ser detalhe
A documentação do Macie descreve a descoberta automatizada como amostragem representativa, sem publicar um percentual fixo. Em 10 cadastros por dia isso é irrelevante — a chance de cobrir tudo é alta. Em 1 milhão de eventos, é uma dimensão real de risco que a certificação espera que você saiba nomear: detecção por amostra não substitui prevenção por desenho.
Custo: o que este laboratório acrescenta à fatura
A dimensão que domina aqui não é armazenamento — é GB analisado pelo Macie. É a linha que mais cresce com volume, e a única que realmente muda de cenário para cenário.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 exportação/dia, poucas centenas de linhas | GB desprezível de S3 e de análise do Macie | desprezível | nenhuma |
| Produção pequena | 9 mil cadastros/dia | GB analisado pelo Macie por dia + GB-mês do bucket, mantido baixo pelo ciclo de vida | previsível, escala com volume de log | restringir a exportação apenas ao(s) grupo(s) de log com risco real de PII, não a todos os grupos da conta |
| Alta escala | 1 milhão de eventos/dia | GB analisado cresce proporcionalmente; pode superar o custo da própria infraestrutura de log | linha visível na fatura do Macie | amostragem deliberada (job com profundidade configurada) em vez de analisar 100% do volume diário |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| Macie — avaliação e monitoramento de bucket | bucket avaliado por mês | independe do volume de dado dentro dele |
| Macie — descoberta automatizada | GB de objeto amostrado | a amostragem é o que mantém isso barato em alto volume — é também a limitação da prova nesse regime |
| Macie — job de descoberta agendado/sob demanda | GB efetivamente analisado | cobre mais que a automatizada e custa mais; escolha pelo requisito de auditoria, não por padrão |
| Armazenamento do bucket de exportação | GB-mês | o ciclo de vida curto é o que evita isso virar linha permanente |
| CloudWatch Logs — ingestão e armazenamento | GB ingerido + GB retido | já existe antes deste laboratório; a decisão de retenção aqui é o que o modifica |
| KMS | por chamada de criptografia/descriptografia | reutilizar a CMK do L46 evita pagar por uma chave nova por domínio de dado |
O ganho que não aparece na fatura de infraestrutura
Reduzir o achado do Macie de "centenas por dia" para "zero, comprovadamente" não tem linha na fatura da AWS. Aparece na resposta a uma pergunta que uma auditoria faz uma vez e não avisa antes: "quem teve acesso a este CPF, e por quanto tempo?". Sem a allowlist e a leitura restrita, a resposta honesta seria "não sabemos".
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Segurança | CPF não chega mais ao log; leitura restrita; cifra herdada do L46 | campo novo do domínio pode escapar da allowlist se ninguém lembrar de atualizá-la | teste automatizado que falha quando o domínio ganha campo e a projeção não é tocada | alta |
| Excelência operacional | exportação e descoberta são automáticas, não dependem de lembrete | ausência de execução do agendamento pode passar despercebida se só se alarma sobre achado positivo | alarme dedicado de ausência de execução, separado do alarme de achado | alta |
| Confiabilidade | ciclo diário se recupera sozinho de uma falha pontual (roda de novo no dia seguinte) | um dia sem varredura é uma janela sem detecção — mitigado, não eliminado, pela allowlist | não é prioridade crítica aqui: a prevenção é a defesa primária, a detecção é a segunda camada | média |
| Eficiência de performance | amostragem mantém a descoberta barata em alto volume | amostragem é imprecisão aceita em troca de custo | job agendado com profundidade maior quando a auditoria exige garantia mais forte | média |
| Otimização de custos | ciclo de vida de 7 dias evita acumular GB-mês no bucket de exportação | GB analisado pelo Macie cresce com o volume de log, não é gratuito | restringir exportação a grupos de log com risco real de dado pessoal | média |
| Sustentabilidade | bucket de exportação pequeno e efêmero | nenhum residual real além do que a limpeza cobre | nenhuma ação adicional além da limpeza já configurada | baixa |
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, e a coluna de custo é a que raramente se escreve com honestidade.
Log serializa o objeto de domínio inteiro, sem allowlist, sem retenção explícita. É onde a Cadência estava, e é aceitável em ambiente de desenvolvimento sem dado real.Allowlist na escrita, retenção explícita, leitura restrita ao grupo que acessa o banco, exportação diária e descoberta automatizada com alerta.A mesma disciplina aplicada a todo serviço da conta, não só à API de pedidos: permissão por coluna no lake de dados, tag de classificação obrigatória (L69).Em vez de exportação diária, subscription filter do CloudWatch Logs para um destino de streaming, com verificação de padrão antes mesmo da retenção completar um ciclo.Macie administrado centralmente via AWS Organizations, cobrindo todas as contas; regra de conformidade que verifica cifra e retenção em todo grupo de logs da organização.Identificadores gerenciados do Macie cobrem padrão estruturado bem definido (CPF, cartão). Texto livre — um campo de observação de suporte que menciona um CPF numa frase — pede um classificador de linguagem, com revisão humana no achado de baixa confiança.A ordem não é negociável, e o motivo é concreto
Streaming em tempo real (nível 4) sem allowlist (nível 2) só reduz a janela de um problema que continua acontecendo a cada requisição. E classificação por IA de texto livre (nível 6) sem os identificadores determinísticos já funcionando (nível 2) significa não ter linha de base para saber se o modelo está ajudando ou só gerando ruído.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo, IA não resolve o núcleo do problema, e forçá-la seria o antipadrão que a própria série critica. "CPF não pode chegar ao log" tem resposta determinística: um tipo sem o campo. Nenhum modelo torna essa decisão melhor — ela é desenho de código, revisável em pull request.
A classificação de dado sensível também não precisa de modelo na maior parte do caso: o Macie usa identificadores gerenciados — regra e padrão, não inferência — para reconhecer CPF, cartão de crédito e dezenas de outros tipos estruturados com alta precisão. Onde um modelo agregaria valor real é um caso mais estreito: texto LIVRE, não estruturado, onde um padrão determinístico erra por falta de contexto.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | reconhecer dado pessoal mencionado dentro de texto livre — um campo de observação de suporte, um comentário — onde não há padrão fixo como o de um CPF formatado |
| Por que um identificador gerenciado não bastaria? | identificadores gerenciados casam padrão e proximidade de palavra-chave; frase natural como "ele me passou o CPF por telefone e eu confirmei" não tem formato fixo para casar |
| De onde viriam os dados? | os mesmos campos de texto livre já existentes nos tickets de suporte — nenhum dado novo precisa ser coletado |
| Qual o risco? | falso positivo em massa gera fadiga de quem revisa; falso negativo é o mesmo risco de hoje, só que com custo de modelo por cima |
| Por que não agora? | a Cadência ainda não tem histórico suficiente de achado em texto livre para avaliar se um classificador ajuda mais do que atrapalha — é o nível 6 da escada anterior, não este laboratório |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "ler os logs e decidir se têm dado pessoal" no lugar da allowlist troca um controle determinístico — o campo simplesmente não existe no tipo — por um probabilístico que roda DEPOIS que o dado já foi escrito. Isso é o mesmo erro de ordem que "só Macie, sem allowlist" na seção de decisões, com o agravante de trocar um scanner com identificador testado por um modelo sem avaliação. Quando a decisão quem-decide-o-que-loga for automatizada, a trilha auditável dessa decisão é o L97, que este laboratório evolui para.
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 |
|---|---|---|---|---|---|
| Logar o objeto de domínio inteiro com destructuring | é a linha mais curta que funciona, e loga tudo o que alguém pode um dia precisar depurar | qualquer campo futuro do tipo entra no log sem decisão | achado do Macie meses depois, em código que ninguém tocou desde então | projeção dedicada de log (allowlist) como único caminho até o logger | nunca em tipo que tem ou pode vir a ter campo pessoal |
| Mascarar com regex depois de serializar tudo | parece correção rápida sem tocar o modelo de domínio | é uma lista de bloqueio: cobre só o padrão previsto, e variação de formato ou campo novo passa | CPF com pontuação diferente, ou um campo pessoal novo, aparece sem máscara | allowlist na escrita — o campo nunca chega a existir na string | como camada adicional, nunca como única defesa |
| Tratar "rodamos o Macie" como "estamos protegidos" | o scanner rodou, o relatório saiu limpo, dá sensação de conformidade | Macie é detecção — roda DEPOIS que o dado já foi escrito e possivelmente lido | achado aparece meses depois, com o dado já lido por qualquer leitor autorizado do log | prevenção (allowlist) primeiro; detecção (Macie) como segunda camada, não a única | nunca como substituto de minimização |
| Aumentar a retenção do log "para investigar problema antigo" sem revisar o conteúdo | parece mudança inofensiva de configuração | estende também a retenção de qualquer dado pessoal presente, sem ninguém decidir isso | auditoria encontra CPF retido além do que a expectativa de dado pessoal previa | decidir retenção pelo que o log PODE conter, não só pela conveniência de debug | quando o grupo comprovadamente não contém dado pessoal |
| Permissão ampla de leitura no grupo de logs "para facilitar debug" | reduz fricção do time; ninguém quer abrir chamado para ver um log | o log vira um banco de dados paralelo com controle de acesso mais frouxo que o original | mais pessoas leram o CPF pelo log do que jamais leram pelo banco | papel dedicado, do mesmo tamanho da lista que acessa o banco de origem | ambiente comprovadamente sem dado pessoal |
| Usar dado real de produção como massa de teste em ambiente de desenvolvimento | é a forma mais rápida de ter dado "realista" para testar | o CPF real passa a existir em todo canal que o ambiente de dev também tem — log, export, backup | achado do Macie num bucket de desenvolvimento que ninguém esperava conter PII | massa sintética gerada, nunca cópia direta do banco de produção | nunca |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Macie não encontra nada, mas você sabe que há CPF no log | o log nunca foi exportado para S3 — Macie não lê CloudWatch Logs diretamente | confira se existe objeto recente no bucket de exportação | `aws s3 ls` no bucket; `describe-export-tasks` no grupo de logs | configurar a exportação como rotina agendada, não etapa manual esquecível |
| Job do Macie conclui como COMPLETE, sem achado, e você sabe que há CPF | a CMK que cifra o bucket não concede descriptografia ao papel vinculado ao serviço do Macie | compare objetos totais do bucket com objetos efetivamente classificados, não só o status do job | `describe-classification-job`; política de recurso da CMK | adicionar statement na política da chave autorizando o papel do Macie a usar `kms:Decrypt` |
| Achado aparece só nos logs de exceção, não nos de sucesso | o handler de erro loga o objeto de contexto inteiro, fora da allowlist normal | compare o formatter do caminho feliz com o do catch | código do middleware de exceção / logger de erro | o handler de erro usa a MESMA projeção segura, nunca o objeto bruto |
| Zero achados por semanas, sem saber se é higiene ou silêncio | o agendamento (EventBridge Scheduler) falhou silenciosamente | veja o histórico de execuções do schedule, não só o resultado do último achado | métricas do EventBridge Scheduler / CloudTrail no alvo esperado diariamente | alarme de AUSÊNCIA de execução em 24h, não só de achado positivo |
| Bucket de exportação cresce e nunca esvazia | a regra de ciclo de vida não está ativa, ou o status ficou desabilitado | liste a configuração de ciclo de vida do bucket | console S3 → Gerenciamento → Ciclo de vida | reativar a regra; conferir a métrica de tamanho depois de 7 dias |
| Leitura do grupo de logs autorizada para mais gente do que o esperado | política antiga, escrita antes de o log conter dado pessoal, nunca revisada | liste os principais autorizados a `logs:GetLogEvents` nesse grupo | IAM Access Analyzer / `simulate-principal-policy` | reescrever a política para o mesmo escopo de quem lê o banco de origem |
| Novo campo pessoal aparece no log semanas depois da correção original | alguém adicionou campo ao tipo de domínio sem tocar na projeção de log | compare o tipo de domínio com o tipo de log lado a lado | revisão de código / diff do pull request que adicionou o campo | teste automatizado que falha quando os dois tipos divergem em campo sensível |
A pergunta que resolve metade destes casos
O problema está em o dado NUNCA CHEGAR ao S3, ou em o Macie chegar ao S3 e NÃO CONSEGUIR LER o conteúdo? A primeira é ausência de exportação ou de agendamento; a segunda é quase sempre permissão de chave KMS. As duas produzem o mesmo sintoma — "zero achado" — e apontam para correções completamente diferentes.
Limpeza: o que o destroy não leva
Este laboratório acrescenta pouco recurso, mas um deles é ACCOUNT-WIDE — destruí-lo sem pensar afeta qualquer outro laboratório ou processo que dependa do Macie na mesma conta.
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. ATENCAO: aws_macie2_account desabilita o Macie para a CONTA INTEIRA, nao so
# para este laboratorio. Confira antes se algum outro job ou laboratorio
# (ex.: L69) ainda depende do servico habilitado.
aws macie2 get-macie-session --query "status" --output text
# 3. BUCKET DE EXPORTACAO: o destroy falha se houver objeto dentro, a menos que
# force_destroy esteja ligado. O ciclo de vida de 7 dias normalmente ja
# esvaziou, mas nao garante se voce interrompeu no meio do laboratorio.
aws s3 rm "s3://ffv-lab-export-logs-macie" --recursive 2>/dev/null || true
aws s3api delete-bucket --bucket ffv-lab-export-logs-macie 2>/dev/null || true
# 4. GRUPO DE LOGS: contem 90 dias de historico de diagnostico. Decida se vale
# manter por mais alguns dias antes de apagar de vez.
aws logs describe-log-groups --log-group-name-prefix "/ecs/ffv-lab" \
--query "logGroups[].{nome:logGroupName,retencao:retentionInDays}" --output table
# 5. Prova final: nada com o nome do projeto de pe.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=ffv-lab \
--query "ResourceTagMappingList[].ResourceARN" --output table| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Habilitação da conta no Macie | sim, e é ACCOUNT-WIDE | não | desabilita o Macie para a CONTA INTEIRA — confira dependência de outro laboratório antes de destruir |
| Job de descoberta agendado | sim, via Terraform | não | mas enquanto existiu, gerou custo de GB analisado — isso já foi cobrado, não retroage |
| Bucket de exportação com objetos | só com `force_destroy` | sim, GB-mês | destroy falha se houver objeto dentro; o ciclo de vida de 7 dias normalmente já esvaziou, mas não garante |
| Grupo de logs | depende de `skip_destroy` | sim, por retenção | contém histórico de diagnóstico; decida se vale manter por mais alguns dias |
| Papel e política de leitura restrita | sim | não | nada retido |
| CMK do L46 | não faz parte deste laboratório | sim, por mês | é recurso compartilhado entre laboratórios; não destrua aqui |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| CPF chega ao log sem ninguém decidir | tipo de log dedicado (allowlist) | campo pessoal não existe no tipo, então não pode ser serializado por acidente |
| Log fica retido além do necessário, com o CPF junto | retenção explícita (90 dias, decisão de negócio) | reter para sempre é o padrão da AWS até alguém decidir o contrário |
| Ninguém sabe se há CPF em log já escrito | exportação diária + descoberta automatizada do Macie | Macie não lê CloudWatch Logs; a exportação é a ponte |
| A chave de cifra bloqueia sem avisar | statement na política da CMK autorizando o papel do Macie | sem ela, Macie relata só metadado — "sem achado" que não significa nada |
| Mais gente lê o log do que lê o banco | política de leitura espelhando o acesso ao banco | log não pode ser um banco paralelo com controle mais frouxo |
| Suporte precisa localizar pedido por CPF | CPF mascarado, campo derivado explícito | dado parcial atende o caso de uso real sem expor o valor completo |
| Bucket de exportação vira depósito esquecido | ciclo de vida de 7 dias | ele existe só para o Macie enxergar, não para guardar |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| CPF serializado por um logger genérico | allowlist de campo | campo pessoal que entra por um tipo diferente, fora do caminho já revisado |
| CPF já escrito no log, meses atrás | nada retroativo — não existe apagar um evento isolado | a única "correção" é reter menos tempo daqui pra frente, ou aceitar a exposição já ocorrida |
| Achado silencioso por permissão de chave | comparar objetos analisados vs. total | não protege sozinho — precisa de alguém olhar a métrica |
| Leitura ampla do grupo de logs | política restrita | não protege contra quem já tinha acesso antes da correção |
| Campo novo do domínio | nada automático | só o teste que compara os dois tipos lado a lado pega isso |
- Cliente informa CPF num cadastro de pedido legítimo.
- A aplicação grava log estruturado usando a projeção com allowlist — CPF não é serializado.
- O grupo de logs, cifrado com a CMK do L46, retém por 90 dias — decisão explícita.
- Todo dia, o EventBridge Scheduler aciona a exportação do dia anterior para o bucket S3.
- O bucket, com ciclo de vida de 7 dias, existe só para a descoberta enxergar o conteúdo.
- A descoberta automatizada do Macie amostra o inventário e classifica o que encontra.
- Achado de BRAZIL_CPF_NUMBER, se houver algum, é publicado via EventBridge para um tópico SNS.
- Zero achado é o resultado esperado — e ele só vale alguma coisa porque o job realmente rodou.
- A leitura do grupo de logs permanece restrita ao mesmo grupo que acessa o banco de origem.
- Se o suporte precisar localizar um pedido por CPF, usa o campo mascarado — nunca o completo.
Perguntas frequentes
❓ O Amazon Macie consegue escanear logs diretamente no CloudWatch Logs?
❓ Por que mascarar o CPF no log com uma expressão regular não resolve o problema de origem?
❓ Dá para apagar um evento específico já gravado no CloudWatch Logs?
❓ Qual é a retenção padrão de um grupo de logs no CloudWatch, se ninguém configurar nada?
❓ O Macie detecta CPF brasileiro nativamente, ou exige identificador customizado?
❓ Minimização de dado é a mesma coisa que estar em conformidade com a LGPD?
❓ Por que a leitura do grupo de logs importa tanto quanto o conteúdo?
❓ Um achado do Amazon Macie sempre significa que houve vazamento externo de dado?
Fixando
Uma auditoria confirma que um CPF específico foi gravado há três dias num evento de log, dentro de um grupo com retenção de 90 dias. Qual ação corrige exatamente esse evento, sem afetar o restante do grupo?
A revisão de código de um colega adiciona `Regex.Replace(linhaDeLog, padraoCpf, "***")` sobre a string já montada, depois de o objeto do pedido inteiro ter sido serializado para o log. Qual é o problema de raciocínio nessa correção, mesmo que ela remova o CPF visível hoje?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L46 concluído (CMK própria, envelope encryption, rotação); L01/L03 no ar (aplicação .NET 8 em ECS Fargate) |
| Conhecimentos adquiridos | a ordem minimizar → mascarar → proteger; que o Macie varre S3 e não CloudWatch Logs diretamente; que mascarado não é anonimizado; a impossibilidade de apagar um evento de log isolado; que o Macie precisa de permissão de descriptografia na CMK para analisar conteúdo cifrado |
| Limitação que fica | a allowlist protege o caminho revisado; um endpoint novo pode recriar o problema em outro lugar sem passar pela mesma disciplina — é a governança de todo o acervo, tratada no L69 |
| Próximo módulo recomendado | L97 — risco e conformidade de decisão automatizada. Reutiliza a lógica de trilha auditável quando a decisão sobre um dado (aqui, o que logar) passa a ser automatizada |
| Também habilitado por este módulo | L69 (governança do lake por coluna) reaproveita Macie e IAM sobre um acervo maior; L50 (resposta a incidente) usa a mesma disciplina de CloudTrail sobre acesso a dado sensível |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: What is Amazon Macie — escopo do serviço, que varre S3 e não CloudWatch Logs; Managed data identifiers, quick reference — confirma `BRAZIL_CPF_NUMBER` como identificador gerenciado, na categoria de identificação fiscal, com palavra-chave exigida; Analyzing encrypted Amazon S3 objects — a exigência de permissão de descriptografia na CMK para o papel vinculado ao serviço do Macie; Working with log groups and log streams — retenção padrão indefinida e o atraso de até 72 horas na exclusão após o limite vencer; e a referência da API do CloudWatch Logs para DeleteLogStream, que confirma a ausência de exclusão por evento isolado. 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 percentual de amostragem da descoberta automatizada do Macie não é documentado como número fixo — a AWS descreve a técnica como amostragem de objetos representativos, sem publicar uma fração. Não trate nenhum número específico de cobertura como garantido; se sua auditoria exige cobertura comprovável, use um job `SCHEDULED` com o escopo que você mesmo define, não a descoberta automatizada. Da mesma forma, os volumes de log e a proporção de eventos com CPF citados na história da Cadência são do cenário de exemplo deste laboratório — meça os seus antes de dimensionar retenção e ciclo de vida.
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…