Lab 41 — Da policy `*` à policy que passa auditoria
O problema, e a empresa que o tem
A Cadência, a mesma equipe de duas pessoas do L01 e do L03, precisou lançar upload de nota fiscal e uma fila de fulfillment antes da campanha de fim de ano. No dia do deploy, sem tempo para descobrir exatamente quais permissões a aplicação precisava, alguém anexou AdministratorAccess à task role "para não travar" — com a promessa de estreitar depois.
Oito meses depois, ninguém lembra quais chamadas a aplicação realmente faz. A role continua com acesso irrestrito à conta inteira, e uma auditoria de segurança já tem data marcada. O medo do dia do deploy virou dívida permanente: reescrever a policy manualmente significa ler toda a documentação da SDK torcendo para não esquecer nenhuma chamada.
A resposta certa não é adivinhar. É deixar o CloudTrail — que já registra cada chamada desde o primeiro dia — dizer o que a aplicação de fato usa, e transformar isso numa policy revisada antes de aplicar. É o que o IAM Access Analyzer faz quando aponta para a atividade real de uma role.
O que este laboratório NÃO é
Não é sobre identidade de workload em si — task role vs. execution role vs. IRSA é o L42, e este laboratório assume que a task role já existe. Também não é sobre transformar achado em ticket com prazo de resposta — isso é o L48, que consome os achados que o Access Analyzer produz aqui. Este módulo entrega a policy derivada e os dois analisadores; o que se faz com o achado depois é outro laboratório.
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 revisado o suficiente.
- Explicar por que "funciona" e "está correto" não são a mesma coisa numa policy IAM.
- Gerar uma policy candidata a partir da atividade real de CloudTrail de uma role, via IAM Access Analyzer.
- Distinguir informação de nível de ação e de nível de serviço no resultado da geração, e completar a segunda manualmente.
- Garantir que a janela de observação cobre os caminhos raros do código, não só o tráfego do dia a dia.
- Aplicar um permission boundary como teto independente da policy anexada à role.
- Testar a policy candidata em staging antes de promovê-la, forçando de propósito o caminho de erro.
- Configurar um analisador de acesso externo e interpretar um achado sem depender de log de tráfego.
- Provar, com CloudTrail, que a policy final não gera nenhum AccessDenied inesperado depois da troca.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Menor privilégio | SAA-C03, SOA-C02 | derivar a policy do CloudTrail em vez de escrever à mão | por que "funciona" não é o mesmo que "está correto" |
| Geração de policy por Access Analyzer | SAA-C03, SOA-C02 | StartPolicyGeneration/GetGeneratedPolicy sobre a trilha | a diferença entre informação de nível de ação e de nível de serviço |
| Zona de confiança e achado de acesso externo | SAA-C03, SOA-C02 | Access Analyzer varre a resource policy, não o tráfego | ele avalia o que é PERMITIDO, não o que foi usado |
| Permission boundary | SAA-C03, DVA-C02 | teto aplicado à task role, além da policy anexada | boundary não concede nada; só limita o que a policy anexada já concede |
| Execution role vs. task role | DVA-C02, SAA-C03 (herdado do L03) | Access Analyzer aponta para a task role, que é quem a aplicação de fato assume | confundir os dois gera policy derivada para o papel errado |
| Condition como refinamento | SAA-C03 | mencionado como próximo passo, fora do escopo deste laboratório | Condition restringe COMO a ação é usada, não SE ela é permitida |
| CloudTrail trail vs. Event History padrão | SOA-C02 | Access Analyzer pede uma trilha configurada, não os 90 dias padrão | os 90 dias da conta não bastam como fonte para geração de policy |
| Analisador de acesso não utilizado | SOA-C02 | mencionado, mas o entregável deste laboratório usa os outros dois recursos | são três analisadores diferentes dentro da mesma ferramenta |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve uma role com policy ampla e pede qual serviço reduz o escopo sem reescrever a policy manualmente, a partir do uso real. A resposta é geração de policy do IAM Access Analyzer sobre o CloudTrail — não o AWS Trusted Advisor, que sinaliza policy ampla mas não deriva uma nova, e não o AWS Config, que audita conformidade contra uma regra, não gera permissão a partir de atividade observada.
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 |
|---|---|---|
| Nenhuma ação real pode falhar com a policy estreita | zero AccessDenied inesperado | exige observar TODOS os caminhos de código antes de aplicar, incluindo os raros |
| Sem indisponibilidade na troca de policy | promoção sem downtime | candidata testada primeiro em staging, e só então promovida por referência à produção |
| Nenhuma credencial estática | herdado do L01/L42 | Access Analyzer aponta para a ROLE assumida pela task, não para um usuário IAM à parte |
| Achado de acesso externo não pode ficar sem dono | roteado, não perdido | Security Hub concentra o achado; sem isso ele expira numa tela que ninguém abre |
| Auditoria precisa provar quando a policy foi revisada | rastreável | policy versionada no IAM (até 5 versões) mais o registro do PR que a aprovou |
| Nenhum segredo, chave ou credencial nos exemplos | regra da casa | a policy referencia ARN do segredo do gateway, nunca o valor dele |
| Custo desprezível | sem recurso novo relevante | nenhuma peça além do bucket de trilha e do Access Analyzer, que é gratuito |
Arquitetura mínima: a role que pode fazer qualquer coisa
Este é o estado real da Cadência hoje, e ele é legítimo como ponto de partida: resolveu o problema do dia do deploy, com poucas linhas. O laboratório começa por medir exatamente o que está concedido — porque um número torna o risco discutível, e "está ampla demais" não.
- → anexa AdministratorAccess "para não travar"
- → assume a task role em toda chamada à AWS
- → s3:GetObject e s3:PutObject nas notas fiscais
- → sqs:SendMessage para o fulfillment
- → secretsmanager:GetSecretValue no segredo do gateway
- → sns:Publish só quando um pedido falha
- → permite também s3:DeleteBucket e s3:PutBucketPolicy — nunca chamados
- → toda chamada, real ou não, fica gravada
- Fora da AWS
- Compute
- Segurança e identidade
- Armazenamento
- Integração de apps
- Gestão e governança
Este desenho publica, e por isso ele sobrevive: uma policy anexada resolve o "não travar" do dia do deploy. O risco não está em nenhuma configuração errada — está no espaço entre o que a role PERMITE e o que a task CHAMA de fato, e esse espaço é invisível até o incidente. Percorra os passos e repare que o CloudTrail já grava a diferença; só ninguém lê.
- Anexada por medo, não por medição. A policy entrou no dia de um deploy urgente, como remendo temporário que ninguém revisitou. Não houve decisão técnica sobre o que a aplicação precisa — houve a decisão de não deixar nada bloquear o prazo.
- A role não distingue chamada de chamada. A task assume a mesma role para as quatro ações reais e para qualquer outra ação de qualquer outro serviço da conta. Do ponto de vista do IAM, um s3:GetObject e um iam:DeleteRole custam a mesma verificação: nenhuma.
- Os dois caminhos do dia a dia. Upload de nota fiscal e envio para a fila de fulfillment acontecem em toda requisição de pedido — são o tráfego que qualquer janela curta de observação já captura sem esforço.
- O segredo que não é o do banco. A credencial do banco vem do execution role, injetada pela definição da task (L03). Esta é outra: a chave do gateway de pagamento, lida diretamente pelo código da aplicação via SDK — e por isso pertence à task role, não à execution role.
- O caminho raro que só aparece no erro. sns:Publish só é chamado quando um pagamento é recusado. Numa janela de observação sem nenhum erro real, esta é a ação que desaparece silenciosamente de qualquer policy derivada — o problema central deste laboratório.
- O excedente que ninguém vê até o incidente. A mesma role que permite os quatro usos reais também permite apagar o próprio bucket de documentos. Nada no dia a dia distingue as duas coisas — só um incidente, ou uma auditoria, torna essa diferença visível.
- Grava tudo, e ninguém deriva nada disso. O CloudTrail já está registrando as quatro chamadas reais desde o primeiro dia. Ter o registro não é proteção — é matéria-prima parada até que algo o leia e derive uma policy dele. É exatamente o que a arquitetura de produção faz.
# medir-a-superficie.sh — antes de mudar, veja exatamente o que esta concedido
aws iam list-attached-role-policies --role-name cadencia-api-task-role \
--query 'AttachedPolicies[].PolicyName' --output table
# AdministratorAccess
VERSAO=$(aws iam get-policy --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--query 'Policy.DefaultVersionId' --output text)
aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/AdministratorAccess \
--version-id "$VERSAO" --query 'PolicyVersion.Document' --output json
# {
# "Version": "2012-10-17",
# "Statement": [{ "Effect": "Allow", "Action": "*", "Resource": "*" }]
# }
# Uma unica statement. Action "*". Nao e "muitas permissoes" — e a UNIAO de
# toda acao de todo servico, presente ou futuro. Nao ha numero para comparar
# com "quatro acoes reais": e literalmente irrestrito, e e isso que o resto
# do laboratorio substitui.
O raio de explosão de uma role com AdministratorAccess
Não é uma questão de "muita permissão": é ausência de limite. Um SSRF na aplicação, uma dependência comprometida, ou um bug que aceita entrada não validada numa chamada à SDK da AWS — qualquer um desses vira controle total da conta, porque a credencial que o processo carrega pode fazer literalmente qualquer coisa, incluindo apagar o próprio CloudTrail que provaria o que aconteceu. Este laboratório não é sobre conveniência: é sobre o tamanho do pior dia possível.
O CloudTrail já grava tudo — isso não é detecção
O CloudTrail desta arquitetura já registra as quatro chamadas reais e todas as que a policy ampla permitiria mas nunca são feitas. Ter o registro não é a mesma coisa que USAR o registro: sem uma etapa que leia esse log e derive algo dele, ele é matéria-prima parada, não proteção.
Arquitetura para produção: a policy derivada, revisada e vigiada
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.
- → entrega o log de eventos de gerenciamento
- → trilha apontada como fonte da análise (StartPolicyGeneration)
- → policy candidata, aplicada primeiro aqui — geração não é aplicação
- → sns:Publish confirmado só ao forçar o caminho de erro
- → s3:GetObject/PutObject confirmado pelo teste de aceitação
- → sqs:SendMessage confirmado pelo teste de aceitação
- → secretsmanager:GetSecretValue confirmado pelo teste
- → policy revisada, promovida por referência, sem reconstrução
- → s3:GetObject/PutObject com Resource restrito ao bucket
- → sqs:SendMessage com Resource restrito à fila
- → secretsmanager:GetSecretValue com Resource restrito ao segredo
- → sns:Publish com Resource restrito ao tópico
- → varre a resource policy do bucket por principal externo
- → varre a resource policy do segredo por principal externo
- → achado de acesso externo não intencional
- Gestão e governança
- Armazenamento
- Segurança e identidade
- Compute
- Integração de apps
A troca deixa de ser "escrever a policy que parece certa" e passa a ser "ler o que aconteceu, revisar, testar e só então aplicar". O que muda no desenho não é uma caixa a mais: é a existência de um PIPELINE de evidência antes da role final, e de uma segunda pergunta — o que ficou exposto para fora — rodando ao lado da primeira. Percorra os passos: cada peça rastreia a um requisito da seção anterior.
- A evidência já existia; Access Analyzer lê a trilha, não a suposição de alguém. A trilha configurada é o mesmo CloudTrail que já gravava tudo na arquitetura mínima. A diferença é que agora algo aponta para ela: o assistente de geração pede explicitamente a trilha a analisar — os 90 dias de Event History padrão da conta não bastam como fonte.
- A candidata vira role só depois de revisão humana. StartPolicyGeneration devolve um job assíncrono; GetGeneratedPolicy devolve um RASCUNHO, não uma policy pronta. Anexar sem revisar reproduz o problema original com aparência de rigor, porque "veio da AWS".
- Provar o caminho raro, de propósito, antes do dia a dia. Forçar um pagamento recusado em staging é o que garante que sns:Publish está na policy final. Sem este passo, a ausência só aparece na primeira falha real em produção — e falha dobrada: o pedido falha, e o alerta do erro também falha.
- Confirmar os caminhos do dia a dia. Upload de nota fiscal, envio para fulfillment e leitura do segredo do gateway são exercitados por um teste de aceitação comum, sem precisar de nenhum artifício — são o tráfego normal da aplicação.
- A produção herda por referência, sem reconstrução. A mesma policy validada em staging é anexada à role de produção. Não há novo rascunho, nova geração nem novo teste de mecanismo — só a promoção de algo já provado.
- A policy final alcança o mesmo tanto, com Resource restrito. As quatro ações continuam as mesmas; o que muda é que cada uma aponta para um ARN específico em vez de "*". As quatro ações deste laboratório suportam recurso específico — nenhum `*` sobrevive na policy final.
- A mesma ferramenta também vigia o que passou a apontar para fora. Access Analyzer de acesso externo não lê CloudTrail: ele avalia a resource policy do bucket e do segredo com raciocínio lógico, e reporta o que ela PERMITE — independente de qualquer chamada externa ter realmente acontecido.
A diferença estrutural em relação ao desenho anterior não é uma caixa a mais: é a existência de um PIPELINE de evidência — CloudTrail, geração, revisão, teste, promoção — antes de qualquer role final, e de uma segunda pergunta rodando ao lado da primeira: o que ficou exposto para fora da conta, independente do que a role permite.
O eixo que mais reduz risco por linha alterada
Das duas primeiras dimensões da fórmula, a que domina é sempre "ações_permitidas": sair de "*" (União de tudo) para quatro verbos nomeados não é uma redução incremental, é uma mudança de ordem de grandeza. Restringir Resource depois disso ainda importa — evita que a mesma ação alcance o bucket errado —, mas o salto maior já aconteceu na primeira troca.
Da evidência à role final: o processo, ponta a ponta
O nome de cada etapa não é jargão: é a prova de que a policy final veio de algo observado, não de uma suposição bem-intencionada. StartPolicyGeneration lê a trilha; GetGeneratedPolicy devolve um rascunho — nunca uma policy pronta para aplicar.
// Saida real de `aws accessanalyzer get-generated-policy`, resumida. Note a
// diferenca entre as duas statements: a primeira tem a ACAO especifica
// encontrada no CloudTrail (informacao de nivel de acao); a segunda,
// adaptada da documentacao oficial, mostra o padrao que aparece quando um
// servico so retorna informacao de nivel de SERVICO — "isto foi tocado",
// sem dizer qual acao.
{
"generatedPolicyResult": {
"generatedPolicies": [{
"policy": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AcoesDeNivelDeAcao",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "sns:Publish", "sqs:SendMessage", "s3:GetObject", "s3:PutObject"],
"Resource": "*"
// Access Analyzer encontrou a ACAO exata no CloudTrail. Resource
// ainda vem "*" — restringir ao ARN certo e trabalho da revisao
// humana, nao algo que a geracao resolve sozinha.
},
{
"Sid": "FullAccessToSomeServices",
"Effect": "Allow",
"NotAction": ["ec2:*", "iam:*", "organizations:*"],
"Resource": "*"
// Padrao de nivel de SERVICO (exemplo adaptado da documentacao
// oficial): "permita tudo, exceto o que sei que voce nao usou".
// Na pratica isto ainda e amplo — e e exatamente o statement que
// a revisao humana precisa reescrever acao por acao.
}
]
}
}]
}
}
O padrão que ainda é amplo, mesmo "gerado"
Um statement com NotAction e Resource "*" não é menor privilégio só porque saiu de uma ferramenta da AWS. Para serviços sem suporte a informação de nível de ação, o Access Analyzer devolve "isto foi tocado", e cabe à revisão humana — lendo o Event History do CloudTrail para aquele serviço específico, ou o próprio código — escrever a ação exata antes de aplicar.
As decisões, e o que se perde em cada uma
📋 A task role da Cadência tem AdministratorAccess há oito meses. A aplicação chama quatro ações reais, ninguém documentou quais, e a próxima auditoria de segurança já tem data marcada. Duas pessoas no time, sem tempo para reescrever a policy lendo toda a documentação da SDK.
A evidência já existe — o CloudTrail grava desde o primeiro dia — então derivar reaproveita dado que já foi pago, em vez de exigir que alguém leia toda chamada de SDK possível na documentação e adivinhe qual delas o código realmente usa. O processo resolve o problema declarado (auditoria com prazo marcado) sem recurso novo relevante na fatura: Access Analyzer é gratuito, e o único custo é o armazenamento da trilha. A revisão humana continua obrigatória — a ferramenta lê o que aconteceu, não decide o que deveria ser permitido.
Alt: Escrever a policy manualmente lendo a documentação da SDK — Exige mapear toda chamada de API que o código pode fazer, incluindo transitivas (paginação, retry, chamadas internas do SDK), com alto risco de esquecer um caminho raro — exatamente o que este laboratório mostra que a evidência resolve melhor.
Alt: Analisador de acesso NÃO UTILIZADO sobre a role já ampla — Identifica permissão ociosa numa role que já existe, depois de dias de inatividade medida — é reativo e útil para higiene contínua (L48), mas não gera um rascunho de policy pronto para revisão como a geração de policy gera.
Alt: Permission boundary sozinho, sem derivar a policy — Um teto sem uma policy estreita por baixo ainda deixa a role tão permissiva quanto o teto permitir. Boundary e policy derivada resolvem perguntas diferentes: o que NUNCA pode ser permitido, e o que É permitido hoje.
Alt: Ferramenta de terceiro para recomendação de IAM — Mais uma integração e outra conta externa com acesso de leitura à sua conta, para resolver algo que a própria AWS já lê do mesmo CloudTrail, nativamente e sem custo.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Fonte da policy | CloudTrail via IAM Access Analyzer | documentação da SDK lida manualmente; ferramenta de terceiro | reaproveita evidência que já existe, em vez de exigir leitura exaustiva de documentação | não cobre chamada que nunca aconteceu na janela — a policy fica tão boa quanto a observação |
| Janela de observação | 35 dias, cobrindo um ciclo de fechamento mensal | 7 dias (exemplo padrão da documentação); 1 dia | inclui o job mensal, que é um caminho de código real e raro | geração mais lenta para revisar, e mais eventos de gerenciamento acumulados na trilha |
| Restrição de recurso | ARN específico por statement | Resource "*" mantido; Condition por prefixo de objeto | as quatro ações suportam recurso; usar isso é a maior redução de superfície disponível | nada perdido aqui — é estritamente melhor quando a API suporta |
| Teste antes de produção | staging, com o caminho de erro forçado | aplicar direto em produção; testar só o caminho feliz | garante que a ação rara (sns:Publish) está confirmada antes de depender dela | exige um ambiente de staging funcional e um jeito de simular o erro |
| Defesa adicional | permission boundary sobre a task role | só a policy anexada, sem teto | protege contra reanexação futura de policy ampla, mesmo por engano | mais uma peça para manter sincronizada quando o escopo do produto mudar |
| Condition (IP, tag, hora) | fora do escopo — próximo refinamento | implementar Condition já neste laboratório | Action/Resource é o eixo que resolve o problema declarado; Condition refina depois | a policy final não restringe COMO a ação é usada, só SE ela é permitida e onde |
A dívida que este laboratório não paga
A policy derivada reflete o código de HOJE. A próxima feature que chamar uma ação nova — um quinto verbo, um quinto recurso — vai bater em AccessDenied em produção, porque nada aqui reexecuta a geração automaticamente. Regenerar como parte do pipeline de release, e não como evento único, é o Nível 3 da evolução mais adiante; enquanto isso não existe, é responsabilidade de quem revisa o PR lembrar de checar se a chamada nova precisa de permissão nova.
Construir: a trilha que a geração de policy precisa encontrar
Sem uma trilha configurada, apontando para um bucket S3, a geração de policy não tem onde ler — os 90 dias de Event History padrão da conta não servem como fonte para esta operação.
# trilha.tf — a evidencia que a geracao de policy precisa encontrar
#
# O assistente de geracao de policy pede para apontar uma TRILHA do
# CloudTrail — nao os 90 dias de Event History que toda conta AWS ja tem de
# graca. Se a Cadencia nunca configurou uma trilha, a geracao nao tem onde
# ler, e essa dependencia so aparece na hora de tentar usar a ferramenta.
resource "aws_s3_bucket" "trilha" {
bucket = "${var.projeto}-cloudtrail-${data.aws_caller_identity.atual.account_id}"
}
resource "aws_s3_bucket_lifecycle_configuration" "trilha" {
bucket = aws_s3_bucket.trilha.id
rule {
id = "expira-em-1-ano"
status = "Enabled"
# O horizonte de revisao deste laboratorio e mensal; um ano de log cobre
# qualquer investigacao retroativa razoavel sem crescer para sempre.
expiration { days = 365 }
}
}
resource "aws_s3_bucket_policy" "trilha" {
bucket = aws_s3_bucket.trilha.id
policy = data.aws_iam_policy_document.trilha_permite_cloudtrail.json
}
data "aws_iam_policy_document" "trilha_permite_cloudtrail" {
statement {
sid = "AWSCloudTrailWrite"
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.trilha.arn}/AWSLogs/${data.aws_caller_identity.atual.account_id}/*"]
condition {
test = "StringEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}
}
statement {
sid = "AWSCloudTrailAclCheck"
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
actions = ["s3:GetBucketAcl"]
resources = [aws_s3_bucket.trilha.arn]
}
}
resource "aws_cloudtrail" "principal" {
name = "${var.projeto}-trilha"
s3_bucket_name = aws_s3_bucket.trilha.id
include_global_service_events = true
is_multi_region_trail = false # a Cadencia opera numa unica regiao
enable_log_file_validation = true
# Eventos de GERENCIAMENTO bastam para gerar a policy da task role: eles ja
# registram qual ACAO foi chamada. Eventos de DADO (leitura/gravacao de
# objeto S3, item do DynamoDB) cobram por evento e so valem a pena se voce
# tambem quiser auditar QUAL objeto foi lido — fora do escopo deste laboratorio.
}
resource "aws_accessanalyzer_analyzer" "acesso_externo" {
analyzer_name = "${var.projeto}-acesso-externo"
type = "ACCOUNT"
# "Gerar policy do CloudTrail" e "achar acesso externo nao intencional" sao
# USOS diferentes da mesma ferramenta. O analisador de acesso externo roda
# continuamente sobre resource policy; a geracao de policy e uma chamada
# sob demanda (start-policy-generation), sem recurso persistente equivalente.
# Nao confundir com o analisador de ACESSO NAO UTILIZADO — outro `type`, e o L48.
}
data "aws_caller_identity" "atual" {}
Eventos de gerenciamento bastam; eventos de dados custam por evento
A trilha acima registra apenas eventos de GERENCIAMENTO — chamadas de API como PutObject e SendMessage, sem o conteúdo do objeto. É suficiente para gerar a policy, porque o que importa é qual AÇÃO foi chamada. Ligar eventos de DADO (S3 no nível de objeto, item do DynamoDB) cobra por evento e só compensa se você também quiser auditar QUAL objeto foi tocado — uma pergunta diferente da que este laboratório responde.
Construir: gerar a policy candidata, e não confiar nela de olhos fechados
A chamada que gera a policy é assíncrona e não tem webhook: o script consulta o status até concluir, e o resultado é sempre um rascunho — a revisão humana que vem depois é parte do processo, não um passo opcional.
#!/usr/bin/env bash
# gerar-policy.sh — a candidata vem do CloudTrail, a decisao continua sua
set -euo pipefail
ROLE_ARN="${ROLE_ARN:?defina ROLE_ARN da task role}"
TRILHA_ARN="${TRILHA_ARN:?defina TRILHA_ARN (arn do aws_cloudtrail.principal)}"
SERVICE_ROLE_ARN="${SERVICE_ROLE_ARN:?defina o service role que o Access Analyzer usa para ler a trilha}"
REGIAO="${REGIAO:-us-east-1}"
# A janela E a decisao mais importante deste script. Curta demais, a
# candidata nasce sem o job de fim de mes e sem o caminho de erro — os dois
# caminhos raros que este laboratorio existe para lembrar. 35 dias cobre um
# ciclo de fechamento mensal inteiro; ajuste ao SEU calendario de negocio.
INICIO="$(date -u -v-35d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '35 days ago' +%Y-%m-%dT%H:%M:%SZ)"
FIM="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
JOB_ID=$(aws accessanalyzer start-policy-generation \
--policy-generation-details "principalArn=${ROLE_ARN}" \
--cloud-trail-details "trails=[{cloudTrailArn=${TRILHA_ARN},region=${REGIAO},allRegions=false}],accessRole=${SERVICE_ROLE_ARN},startTime=${INICIO},endTime=${FIM}" \
--query 'jobId' --output text)
echo "geracao iniciada: ${JOB_ID}"
# A geracao roda em segundo plano. Nao existe webhook: espere e consulte.
STATUS="IN_PROGRESS"
while [ "$STATUS" = "IN_PROGRESS" ]; do
sleep 15
STATUS=$(aws accessanalyzer get-generated-policy --job-id "$JOB_ID" \
--query 'jobDetails.status' --output text)
echo "status: ${STATUS}"
done
if [ "$STATUS" != "SUCCEEDED" ]; then
echo "geracao terminou como ${STATUS} — leia jobDetails.jobError antes de repetir" >&2
exit 1
fi
# Duas categorias de retorno: para alguns servicos vem a ACAO exata
# encontrada no CloudTrail; para outros, so o SERVICO que foi tocado, com um
# NotAction que na pratica ainda concede quase tudo dele. Isto NAO e a
# policy final — e o rascunho que a revisao humana estreita.
aws accessanalyzer get-generated-policy --job-id "$JOB_ID" \
--include-resource-placeholders --include-service-level-template \
--query 'generatedPolicyResult.generatedPolicies[0].policy' --output text \
> candidata.json
echo "candidata em candidata.json — REVISE antes de qualquer aws iam create-policy"
# O validador pega erro de sintaxe e sugestao de seguranca, mas nao decide
# por voce se uma acao vista uma unica vez merece virar permissao permanente.
aws accessanalyzer validate-policy \
--policy-document "file://candidata.json" \
--policy-type IDENTITY_POLICY \
--query 'findings[].{tipo:findingType,msg:findingDetails}' --output table
Aplicar sem revisar é a mesma decisão, só que documentada
Anexar o rascunho do Access Analyzer direto, sem abrir o statement de nível de serviço e sem restringir Resource, produz uma policy que PARECE derivada de evidência mas ainda concede quase tudo daquele serviço. É pior do que parece: um auditor futuro vê "policy customizada, não managed policy da AWS" e presume que foi revisada. A ferramenta gerou o rascunho; a decisão de aplicar continua sendo sua.
Construir: os quatro caminhos que o CloudTrail vai registrar
Ler o código é o segundo jeito de saber o que a task role precisa — a evidência do CloudTrail é o primeiro, e os dois devem concordar. Se não concordarem, um dos dois está desatualizado: ou o código mudou depois da última geração, ou a janela de observação não cobriu um caminho que o código de fato tem.
// PedidoService.cs — os quatro caminhos reais, e a acao IAM que cada um pede
//
// Isto nao e uma classe nova: sao os metodos que ja existiam no L01/L03, e
// que a geracao de policy vai encontrar no CloudTrail. Ler o codigo e o
// segundo jeito de saber o que a task role precisa — a evidencia do
// CloudTrail e o primeiro, e os dois devem concordar.
public class PedidoService
{
private readonly IAmazonS3 _s3;
private readonly IAmazonSQS _sqs;
private readonly IAmazonSecretsManager _secrets;
private readonly IAmazonSimpleNotificationService _sns;
private string? _chaveGatewayCache;
// Caminho do dia a dia (1 de 4): grava a nota fiscal. Precisa de
// s3:PutObject sobre o prefixo notas-fiscais/ do bucket.
public async Task UploadNotaFiscalAsync(Guid pedidoId, Stream pdf)
{
await _s3.PutObjectAsync(new PutObjectRequest
{
BucketName = "cadencia-documentos",
Key = $"notas-fiscais/{pedidoId}.pdf",
InputStream = pdf,
});
}
// Caminho do dia a dia (2 de 4): enfileira para o fulfillment. Precisa de
// sqs:SendMessage sobre a fila cadencia-fulfillment.
public async Task EnfileirarFulfillmentAsync(Guid pedidoId)
{
await _sqs.SendMessageAsync(new SendMessageRequest
{
QueueUrl = _filaFulfillmentUrl,
MessageBody = JsonSerializer.Serialize(new { pedidoId }),
});
}
// Startup e a cada 12h: le a chave do gateway de pagamento. Precisa de
// secretsmanager:GetSecretValue sobre ESTE segredo — nao o do banco, que
// e injetado pelo execution role via `secrets` na task definition (L03).
public async Task<string> ObterChaveGatewayAsync()
{
if (_chaveGatewayCache is not null) return _chaveGatewayCache;
var resp = await _secrets.GetSecretValueAsync(new GetSecretValueRequest
{
SecretId = "cadencia/gateway-pagamento",
});
_chaveGatewayCache = resp.SecretString;
return _chaveGatewayCache;
}
// Caminho RARO (3 de 4): so executa quando o pagamento falha. Se a janela
// de observacao do CloudTrail nunca incluiu um dia de erro, esta acao nao
// aparece na policy candidata — e o primeiro erro real em producao falha
// ao tentar avisar alguem, exatamente quando o alerta mais importa.
public async Task PublicarAlertaErroAsync(Guid pedidoId, string motivo)
{
await _sns.PublishAsync(new PublishRequest
{
TopicArn = _topicoAlertasArn,
Message = $"Falha no pedido {pedidoId}: {motivo}",
});
}
// Job mensal (RARO, 4 de 4): mesmo bucket, prefixo diferente. Ja coberto
// pelo s3:PutObject acima, porque a policy final restringe por BUCKET,
// nao por prefixo — decisao registrada na secao de decisoes.
public async Task GerarRelatorioMensalAsync(Stream relatorio)
{
await _s3.PutObjectAsync(new PutObjectRequest
{
BucketName = "cadencia-documentos",
Key = $"relatorios-mensais/{DateOnly.FromDateTime(DateTime.UtcNow):yyyy-MM}.csv",
InputStream = relatorio,
});
}
}
Por que cada método mapeia a uma ação, e não a um wildcard
Nenhum destes quatro métodos chama um serviço "por via das dúvidas". Cada um existe para resolver exatamente um problema de negócio, e por isso cada um pede exatamente uma ação — isso não é disciplina de segurança adicional, é a estrutura natural de um código bem separado por responsabilidade. A policy larga escondia essa clareza; a policy derivada só a expõe.
Construir: a policy final, com Resource restrito e um teto por cima
A policy candidata revisada vira estas quatro statements. O permission boundary é a peça nova: ele não concede nada — só limita o que qualquer policy futura, anexada a esta role, poderá permitir.
# task-role-final.tf — a policy candidata, revisada, com Resource restrito
#
# Compare com a arquitetura minima: la era um managed policy da AWS
# (AdministratorAccess) anexado direto. Aqui sao quatro acoes, cada uma com
# Resource especifico — nenhum "*" sobrou, porque as quatro suportam ARN de
# recurso. Isso nem sempre e verdade (o ecr:GetAuthorizationToken do L03 nao
# suporta), mas aqui e, e por isso a policy final nao precisa de nenhuma
# frase de justificativa de wildcard.
data "aws_iam_policy_document" "task_role_final" {
statement {
sid = "NotasFiscaisEDadosMensais"
actions = ["s3:GetObject", "s3:PutObject"]
resources = ["${aws_s3_bucket.documentos.arn}/notas-fiscais/*",
"${aws_s3_bucket.documentos.arn}/relatorios-mensais/*"]
# Restringir por PREFIXO em vez de por bucket inteiro seria o proximo
# refinamento, feito aqui com `Resource`, nao com `Condition`. Uma
# condicao por tag de recurso, IP de origem ou hora do dia fica fora do
# escopo deste laboratorio — e o passo seguinte, nao o entregavel dele.
}
statement {
sid = "FilaDeFulfillment"
actions = ["sqs:SendMessage"]
resources = [aws_sqs_queue.fulfillment.arn]
}
statement {
sid = "SegredoDoGateway"
actions = ["secretsmanager:GetSecretValue"]
resources = [aws_secretsmanager_secret.gateway.arn]
}
statement {
sid = "AlertaDeErro"
actions = ["sns:Publish"]
resources = [aws_sns_topic.alertas_erro.arn]
}
}
resource "aws_iam_policy" "task_role_final" {
name = "${var.projeto}-task-role-derivada"
policy = data.aws_iam_policy_document.task_role_final.json
}
# O limite de permissao (permissions boundary): um TETO, nao uma concessao.
# Mesmo que alguem anexe amanha uma policy mais ampla "para nao travar de
# novo", o boundary impede que ela valha alem dos servicos aqui listados. Ele
# NAO SUBSTITUI a policy estreita — as duas juntas fecham o cerco: a policy
# diz o que E permitido hoje; o boundary diz o que NUNCA pode ser permitido,
# mesmo por engano futuro.
data "aws_iam_policy_document" "limite_de_permissao" {
statement {
actions = ["s3:*", "sqs:*", "secretsmanager:GetSecretValue", "sns:Publish"]
resources = ["*"]
# O boundary pode ser mais largo que a policy do dia (aqui usa s3:* e
# sqs:*, nao as acoes exatas) porque ele e o TETO de mudancas futuras, nao
# a permissao do dia a dia — essa quem restringe e a policy acima. Um
# boundary tao apertado quanto a policy atual quebraria a primeira
# mudanca legitima de escopo do produto.
}
}
resource "aws_iam_policy" "limite_de_permissao" {
name = "${var.projeto}-limite-task-role"
policy = data.aws_iam_policy_document.limite_de_permissao.json
}
resource "aws_iam_role" "task_final" {
name = "${var.projeto}-task-role"
permissions_boundary = aws_iam_policy.limite_de_permissao.arn
assume_role_policy = data.aws_iam_policy_document.task_assume.json
}
resource "aws_iam_role_policy_attachment" "task_final" {
role = aws_iam_role.task_final.name
policy_arn = aws_iam_policy.task_role_final.arn
}
# A troca em si: aponte a task definition do L03 para esta role e reaplique.
# Nenhum outro campo da task definition muda — mesma imagem, mesmo servico,
# so a identidade que ele assume fica mais estreita.
O boundary não substitui a policy estreita
É um erro comum tratar o permission boundary como "já resolvi a segurança". Sem uma policy anexada estreita, a role continua tão permissiva quanto o próprio boundary permitir — e o boundary costuma ser mais largo que o necessário do dia a dia, de propósito, para não bloquear a próxima mudança legítima. As duas camadas são complementares, não alternativas.
Implantar, e provar que nada quebrou
Cinco provas. Nenhuma delas aceita "parece que funcionou" como resultado — cada uma tem um comando e um resultado esperado, e a quinta é a que mais gente pula.
# provas.sh — cinco medicoes; nenhuma conclusao vem de "parece que funcionou"
PROJETO=cadencia; REGIAO=us-east-1
# ── Prova 1: a candidata gerada contem exatamente os servicos esperados ──────
aws accessanalyzer get-generated-policy --job-id "$JOB_ID" \
--query 'generatedPolicyResult.generatedPolicies[0].policy.Statement[].Action' \
--output json
# Esperado: secretsmanager, sns, sqs e s3 aparecem. Se sns estiver ausente, a
# janela de observacao nao capturou nenhum erro — volte e amplie o periodo,
# ou force o caminho de erro antes de gerar de novo.
# ── Prova 2: zero AccessDenied durante o teste completo em staging ───────────
aws logs filter-log-events --log-group-name "/ecs/${PROJETO}-api-staging" \
--filter-pattern "AccessDenied" --query 'events[].message' --output text
# Esperado: saida vazia. Qualquer linha aqui e uma acao que a policy candidata
# nao cobre — identifique-a e volte para a revisao antes de promover.
# ── Prova 3: o caminho de erro foi de fato exercitado no teste ───────────────
aws cloudtrail lookup-events --lookup-attributes \
AttributeKey=EventName,AttributeValue=Publish \
--start-time "$(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
--query 'Events[].EventName' --output text
# Esperado: pelo menos um "Publish". Se vazio, o teste de staging nao forcou
# o pagamento recusado, e a prova 2 nao provou o que precisa provar.
# ── Prova 4: producao roda 24h sem AccessDenied depois da promocao ───────────
aws logs filter-log-events --log-group-name "/ecs/${PROJETO}-api" \
--filter-pattern "AccessDenied" \
--start-time "$(date -u -v-24H +%s000 2>/dev/null || date -u -d '24 hours ago' +%s000)" \
--query 'events[].message' --output text
# Esperado: saida vazia. Diferente da prova 2 (staging, teste dirigido), esta
# e producao com trafego real — e o criterio de aceite do laboratorio.
# ── Prova 5: nenhum achado ativo de acesso externo nos recursos tocados ──────
aws accessanalyzer list-findings-v2 --analyzer-arn "$ANALISADOR_ARN" \
--filter '{"status":{"eq":["ACTIVE"]}}' \
--query 'findings[].{recurso:resource,tipo:findingType}' --output table
# Esperado: tabela vazia para o bucket e o segredo deste laboratorio. Um
# achado ativo significa resource policy permitindo principal fora da conta,
# independente de a role da task ter sido estreitada.
| Prova | Comando | Resultado que aprova | O que reprova, e o que significa |
|---|---|---|---|
| 1 · A candidata tem os serviços certos | get-generated-policy → Statement[].Action | secretsmanager, sns, sqs e s3 presentes | sns ausente indica que a janela não capturou nenhum erro — amplie o período |
| 2 · Staging não gera AccessDenied | filtro de logs em "AccessDenied" | saída vazia durante o teste completo | qualquer linha é uma ação faltante — volte para a revisão antes de promover |
| 3 · O caminho de erro foi exercitado | CloudTrail lookup-events em Publish | pelo menos um evento Publish no período do teste | vazio significa que a prova 2 não provou o caminho que mais importa |
| 4 · Produção roda 24h sem AccessDenied | filtro de logs de produção, 24h | saída vazia com tráfego real | é o critério de aceite do laboratório — qualquer AccessDenied aqui é incidente |
| 5 · Nenhum acesso externo ativo | list-findings-v2, status ACTIVE | tabela vazia para bucket e segredo | achado ativo é resource policy permitindo acesso de fora, independente da role da task |
A prova que, se pulada, promove direto para incidente
Aplicar a candidata em produção sem antes validá-la em staging é apostar que a janela de observação cobriu tudo. Quando não cobriu — e a próxima seção mostra exatamente como isso acontece — o efeito é indisponibilidade real, para cliente real, na primeira chamada à ação que faltou. As provas 2 e 3 existem para transformar essa aposta em fato verificado antes de expor alguém a ela.
Quebrar de propósito: três falhas e o diagnóstico
As três acontecem de verdade, e as três nascem do mesmo lugar: a janela de observação não cobriu um caminho de código que existe.
| Falha | Como provocar | Sintoma | Onde olhar | Correção |
|---|---|---|---|---|
| Janela sem o ciclo mensal | gere a policy com janela de 5 dias, sem incluir um fechamento de mês | job mensal falha com AccessDenied semanas depois da troca, sem relação óbvia com o deploy | CloudTrail filtrado por errorCode=AccessDenied na task role, eventName=PutObject | acrescentar a ação por leitura de código, reabrir revisão e reaplicar em staging |
| Janela sem nenhum erro | gere a policy sem forçar nenhum pagamento recusado no período observado | primeiro erro real em produção falha ao processar E falha ao publicar o alerta do erro | comparar a policy anexada com os métodos do código que chamam Publish | forçar o caminho de erro em staging antes de promover — é a prova 3 desta seção anterior |
| Candidata aplicada direto em produção | pule a validação em staging e anexe o rascunho direto na role de produção | estatement de nível de serviço, ainda amplo, ou ação faltante, expostos sem rede de segurança | ordem do runbook: geração → revisão → staging → produção | sempre promover por staging; nunca aplicar direto o resultado de get-generated-policy |
A falha que apaga a si mesma
Quando a janela de observação nunca capturou um dia de erro, a ação de alerta (sns:Publish) não entra na policy candidata. O resultado não é só "o pagamento falha" — é "o pagamento falha, E o alerta que deveria avisar alguém desse erro também falha", porque a mesma lacuna de permissão atinge os dois. É uma falha que se esconde de quem deveria descobri-la, e é exatamente por isso que a seção de provas força esse caminho de propósito em staging.
A Cadência observou o CloudTrail da task role por 5 dias antes de gerar a policy candidata. Nesses 5 dias não houve nenhum pedido com pagamento recusado. O que acontece com sns:Publish na policy gerada, e qual o efeito prático?
Segurança: o que muda quando a role deixa de ser irrestrita
Estreitar a role não elimina risco — desloca onde ele mora. O que era "qualquer coisa pode acontecer" vira um conjunto pequeno e nomeado de formas concretas de dar errado.
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Role com AdministratorAccess esquecida em produção | alta (é o estado inicial) | alto | derivar e aplicar a policy estreita deste laboratório | varredura periódica por managed policy AdministratorAccess anexada | remover a policy ampla assim que a estreita for validada |
| Candidata aplicada sem revisão vira permissão permanente | média | alto | revisão obrigatória antes de anexar, nunca auto-apply | diff entre a policy candidata e a anexada, revisado por outra pessoa | reabrir revisão, remover ação não intencional |
| Bucket ou segredo com resource policy aberta para fora | baixa | alto | external access analyzer sobre os recursos alcançados pela policy estreita | achado de acesso externo do Access Analyzer, status ACTIVE | corrigir a resource policy, confirmar resolução do achado |
| Permission boundary mais largo que a policy anexada | baixa | médio | revisar o boundary com o mesmo rigor da policy | Access Analyzer também avalia o boundary na análise de política | apertar o boundary ao escopo real |
| Service role da própria geração de policy com acesso além da trilha | baixa | baixo/médio | criar o service role pelo próprio assistente, sem reaproveitar um existente | CloudTrail sobre o service role usado na geração | revisar e restringir se necessário |
| Janela curta demais some com ação rara na policy final | média | médio (indisponibilidade) | cobrir pelo menos um ciclo completo de todos os caminhos de código | teste do caminho raro forçado em staging antes de promover | reabrir geração com janela maior, ou adicionar ação por leitura de código |
O achado que a policy estreita, sozinha, nunca resolve
Uma role com quatro ações e Resource restrito ainda não protege contra um bucket cuja resource policy permite leitura para uma conta externa — são superfícies diferentes. É por isso que este laboratório aplica o Access Analyzer duas vezes: uma sobre a identidade (a role), outra sobre o recurso (a resource policy).
Observabilidade: as perguntas que o painel tem de responder
Um painel de menor privilégio tem uma função estreita: dizer se a policy atual ainda reflete o uso real, e se algo ficou exposto para fora. Métrica que não ajuda nessas duas perguntas pertence a outro painel.
| Pergunta | Métrica ou consulta | O que significa mudar | Limiar inicial |
|---|---|---|---|
| A task está recebendo AccessDenied? | filtro de CloudTrail em errorCode=AccessDenied | a policy ficou mais estreita do que o código realmente usa | qualquer valor > 0 após a promoção |
| Existe achado de acesso externo ativo? | contagem de findings ACTIVE do analisador externo | um recurso passou a estar alcançável de fora da conta | qualquer valor > 0 |
| Quando foi a última geração de policy desta role? | idade da última StartPolicyGeneration bem-sucedida | o código evoluiu e a policy pode estar desatualizada | acima do ciclo de release do time |
| Alguém reanexou uma policy ampla? | CloudTrail em AttachRolePolicy com managed policy amplo | drift de volta ao estado inicial deste laboratório | qualquer ocorrência |
| Quantas roles da conta ainda têm managed policy amplo? | inventário periódico (Config ou script) | tamanho do trabalho de derivação que ainda falta | tendência deve cair, não subir |
| A ação rara (sns:Publish) está sendo exercida em produção? | contagem de chamadas reais | confirma que o caminho de erro realmente existe fora de staging | ao menos uma vez por trimestre, se o app tem erros reais |
| O boundary foi revisado desde a última mudança de escopo? | data da última revisão do boundary | boundary desatualizado pode bloquear uma ação nova e legítima | acima do ciclo de release combinado |
A métrica que engana sozinha
Zero AccessDenied não é zero risco: é zero risco de INDISPONIBILIDADE por permissão faltante. A pergunta de EXPOSIÇÃO — o que ficou alcançável de fora da conta — não aparece nessa métrica nenhuma, e exige olhar os achados do external access analyzer separadamente.
Escala: uma role, dezenas de roles, uma organização inteira
IAM é um serviço regional/global, sem noção de zona de disponibilidade — o eixo de escala real aqui não é tráfego, é NÚMERO DE ROLES E DE CONTAS que precisam passar pelo mesmo processo.
| Volume | O que acontece com o processo | O que passa a doer | O que fazer |
|---|---|---|---|
| 1 role (este laboratório) | geração roda em minutos; revisão cabe numa sessão | nada | nada |
| 9 mil chamadas/dia, 1 role | geração ainda roda rápido; a variável é cobrir os caminhos raros | garantir que o job mensal e o erro aconteçam dentro da janela | escolher a janela pelo CICLO DE NEGÓCIO, não por dias corridos |
| Dezenas de roles na conta | revisar cada candidata manualmente não escala | tempo do time de segurança vira o gargalo | pipeline repetível que gera e abre PR para revisão, em vez de gerar uma a uma — ver o padrão de referência da AWS com Step Functions |
| Centenas de contas na organização | cada conta tem seu próprio Access Analyzer e sua própria trilha | inconsistência entre contas sobre o que já foi revisado | Access Analyzer delegado a nível de organização, com achados agregados centralmente (L48) |
| Aplicação evolui rápido, várias releases por semana | a policy derivada envelhece a cada deploy | AccessDenied em produção toda vez que o código ganha uma chamada nova | reexecutar a geração como parte do pipeline de release, não como evento único |
O padrão de referência para quando "uma de cada vez" não escala mais
A AWS documenta um padrão de geração dinâmica de policy usando Step Functions para orquestrar StartPolicyGeneration/GetGeneratedPolicy em lote sobre várias roles. O valor não é pular a revisão humana — é automatizar a PARTE mecânica (chamar a API, esperar, coletar o resultado) para que a revisão humana seja o único gargalo, não a geração em si.
Custo: o que este laboratório acrescenta à fatura
É um dos laboratórios mais baratos da série: IAM Access Analyzer — geração de policy, acesso externo e acesso não utilizado — não cobra pela análise em si. O que cobra é o que já existia antes dele: o CloudTrail por trás.
| Cenário | Volume | O que acrescenta | Tendência | Otimização |
|---|---|---|---|---|
| Protótipo | 1 role, 1 geração manual | poucos MB de log de eventos de gerenciamento na trilha | desprezível | nenhuma; otimizar aqui é gastar atenção onde não há dinheiro |
| Produção pequena | 5 a 10 roles, gerações mensais | armazenamento S3 da trilha crescendo linearmente; eventos de gerenciamento são gratuitos até um volume generoso | baixa e previsível | lifecycle no bucket da trilha (já configurado para 1 ano) |
| Alta escala | centenas de roles, dezenas de contas, unused access analyzer também ligado | volume de CloudTrail cresce com o número de contas; Security Hub cobra por achado avaliado | passa a ser linha visível, ainda pequena frente ao resto da fatura | eventos de DADO só onde realmente necessário — são o item que mais surpreende na conta |
| Dimensão | Cobra por | Cuidado |
|---|---|---|
| IAM Access Analyzer (os três analisadores) | nada — a análise em si é gratuita | não confundir com o custo do CloudTrail que ele lê |
| CloudTrail — eventos de gerenciamento | gratuito até um volume generoso, depois por evento | o cenário deste laboratório fica dentro da faixa gratuita na prática |
| CloudTrail — eventos de dados | por evento processado, sempre, desde o primeiro | só ligue se precisar auditar QUAL objeto foi acessado — não é necessário para gerar a policy |
| Armazenamento S3 da trilha | GB-mês retido | lifecycle evita crescimento indefinido; ajuste os 365 dias ao seu horizonte de auditoria |
| Security Hub (se habilitado) | por achado avaliado por mês | só relevante se você consolidar os achados de acesso externo ali, como sugerido na arquitetura de produção |
O ganho de custo que não está na fatura de infraestrutura
O valor real deste laboratório não aparece em nenhuma linha da AWS: é o tempo de auditoria que deixa de ser "reconstruir de memória o que a aplicação faz" e passa a ser "ler uma policy de quatro statements com Resource explícito". Isso é custo de engenharia evitado, e ele estava na folha de pagamento antes deste laboratório existir.
Well-Architected nos seis pilares
| Pilar | Situação ao fim deste laboratório | Risco que fica | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | policy derivada de evidência, revisada e testada, com runbook repetível | revisão ainda é manual, não escala além de poucas roles | pipeline com Step Functions que gera e abre PR (padrão de referência da AWS) | média |
| Segurança | "*" eliminado da policy da role, boundary como teto, acesso externo monitorado | nenhuma automação impede reanexar AdministratorAccess amanhã | SCP em nível de organização proibindo o managed policy amplo (L43) | alta |
| Confiabilidade | candidata testada em staging, inclusive o caminho de erro forçado | a policy envelhece a cada release que chama algo novo | regeneração como parte do pipeline de deploy, não evento único | alta |
| Eficiência de performance | nenhuma mudança de performance — é puramente IAM | nenhum | nenhuma necessária | baixa |
| Otimização de custos | Access Analyzer gratuito; custo é só armazenamento da trilha | ligar eventos de dados sem necessidade infla o CloudTrail | eventos de gerenciamento bastam para este caso de uso | baixa |
| Sustentabilidade | nenhum recurso computacional novo além do bucket de log | log crescendo sem ciclo de vida | lifecycle já configurado (365 dias); revisar o número com o horizonte real de auditoria | 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 processo. Cada nível resolve um risco e compra outro, e é a segunda coluna que raramente se escreve.
AdministratorAccess anexado direto, sem revisão. É onde a Cadência estava.Policy derivada do CloudTrail, revisada, testada em staging, com permission boundary e monitoramento de acesso externo.Regeneração de policy como etapa do pipeline de release (padrão com Step Functions), não evento manual único.SCP na organização proibindo anexar managed policies amplos (AdministratorAccess, PowerUserAccess) fora de contas de exceção explícita.Access Analyzer delegado a nível de organização: um analisador por conta, achados agregados centralmente, unused access analyzer ligado (L48).Um assistente que resume o diff entre a policy candidata e a anterior, sinalizando ação sobre recurso nunca visto antes, para quem revisa dezenas de roles na mesma tarde.A ordem não é negociável, e o motivo é concreto
SCP no Nível 4 depende de já existir um processo de derivação (Nível 2) para as exceções legítimas terem um caminho — proibir o atalho sem oferecer a alternativa apenas move a pressão de prazo para outro lugar. E o resumo por IA do Nível 6 só faz sentido depois de existir histórico de gerações para comparar; é a decoração que os níveis anteriores evitam.
Onde IA entra nesta arquitetura, e onde não entra
Neste módulo, IA não resolve o problema central, e forçá-la seria o antipadrão que a própria série critica. A pergunta "quais ações e recursos a task role realmente usa" tem resposta determinística: o CloudTrail registrou, e o Access Analyzer leu. Um modelo de linguagem não melhora a precisão dessa leitura — lê o mesmo log sem a garantia de completude que a ferramenta da AWS tem sobre seus próprios eventos, e tem mais chance de inventar uma ação que nunca foi chamada.
Há um lugar onde IA acrescentaria valor real, e ele é modesto: ajudar um humano a revisar MUITAS policies candidatas mais rápido, sinalizando o que mudou de incomum — não gerando a policy no lugar da ferramenta que já faz isso com precisão.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | resumir o diff entre a policy candidata e a anterior, sinalizando o que é novo, para quem revisa muitas roles na mesma tarde |
| Por que uma regra não bastaria? | um diff estrutural (JSON diff determinístico) já cobre a maior parte; IA ajudaria no caso semântico — a MESMA ação sobre um ARN novo é mais arriscada que sobre o de sempre, e um diff de texto não distingue isso sozinho |
| De onde viriam os dados? | o histórico de gerações anteriores da mesma role, que o próprio Access Analyzer já guarda, e os achados de acesso externo anteriores |
| Qual o risco? | falso negativo — o resumo "parece calmo" e o revisor pula a leitura do diff completo; a aprovação continua exigindo abrir o documento, nunca a IA aplicando a policy sozinha |
| Por que não agora? | a Cadência tem uma role e uma geração — não há histórico suficiente para o resumo comparar com nada; o valor só aparece a partir de dezenas de revisões acumuladas |
O uso de IA que parece atraente e é armadilha aqui
Pedir a um modelo para "gerar a policy final direto, sem passar pelo Access Analyzer" troca uma fonte de evidência determinística — o CloudTrail, que registra o que de fato aconteceu — por uma suposição plausível do que o modelo ACHA que o código chama. O Access Analyzer já faz a parte que exige precisão, que é ler o log; o lugar de um modelo de linguagem é resumir para humano decidir mais rápido, nunca substituir a evidência.
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 |
|---|---|---|---|---|---|
| AdministratorAccess "para não travar" | pressão de prazo e medo de bloquear o próprio deploy | qualquer credencial vazada ou bug de SSRF vira controle total da conta | nenhum sintoma até o incidente — é o ponto: o risco é invisível | derivar do CloudTrail, revisar, testar, aplicar | nunca em produção; tolerável por horas num protótipo isolado, sem dado real |
| Aplicar a candidata direto em produção, sem staging | "já revisei, deve estar certo" | ação faltante quebra produção na primeira chamada real | AccessDenied inesperado, sem relação óbvia com o deploy que o causou | sempre promover por staging, com o caminho raro forçado | nunca |
| Aceitar o rascunho de nível de serviço sem revisar | "a ferramenta da AWS deve estar certa" | statement de nível de serviço ainda concede quase tudo daquele serviço | auditoria encontra Resource "*" numa policy que deveria ser estreita | completar ação e Resource manualmente antes de aplicar | nunca |
| Janela de observação de 1 dia | "quero terminar isso hoje" | omite caminho raro (mensal, de erro) | falha semanas depois, sem relação óbvia com a mudança de policy | derivar a janela do ciclo de negócio real | ambiente efêmero, sem job periódico nem tratamento de erro |
| Achar que permission boundary substitui a policy estreita | "coloquei um teto, já tá seguro" | a role continua tão permissiva quanto o boundary permitir, que costuma ser mais largo | auditoria encontra ações amplas ainda ativas dentro do teto | as duas camadas juntas: policy estreita E boundary | nunca — são complementares, não alternativos |
| Restringir por Condition antes de restringir Action/Resource | Condition parece "mais avançado", então parece o passo certo a dar primeiro | a superfície de ação/recurso continua ampla; Condition só filtra COMO, não O QUE | achado de menor privilégio continua alto mesmo com condições elaboradas | primeiro Action e Resource; Condition é refinamento por cima, não substituto | nunca como primeiro passo; sempre bem-vindo depois |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| Job de fim de mês falha com AccessDenied após a troca | janela de observação não cobriu o ciclo mensal | confira se a geração incluiu uma data de fechamento de mês | CloudTrail: errorCode=AccessDenied, eventName=PutObject | acrescentar a ação por leitura de código, reabrir em staging |
| Erro de aplicação não gera alerta | sns:Publish nunca apareceu no período observado | compare a policy candidata com os métodos do código que chamam Publish | task role policy vs. PedidoService.cs | forçar o caminho de erro em staging antes de promover |
| StartPolicyGeneration só devolve informação de nível de serviço | o serviço chamado não está entre os que suportam nível de ação | consulte a lista de serviços da documentação de geração de policy | "IAM Access Analyzer policy generation services" na doc oficial | revisar o CloudTrail Event history manualmente para aquele serviço específico |
| Achado de acesso externo não desaparece após a correção | pode levar até 30 min para reavaliar, e o rescan completo é em até 24h | compare o horário do achado com o horário da correção | timestamp do finding vs. timestamp da mudança de policy | aguardar a janela documentada antes de escalar |
| Geração de policy falha por permissão | o service role criado para o Access Analyzer não tem acesso à trilha/bucket | revise a policy do service role de geração | IAM → role usada em accessRole no start-policy-generation | recriar seguindo o passo "criar e usar um novo service role" |
| Policy final ainda tem Resource "*" numa ação que devia ser restrita | statement de nível de serviço copiado sem completar manualmente | confira se a revisão humana foi de fato executada | diff entre candidata.json e a policy aplicada | reabrir a policy, restringir Resource, revalidar com validate-policy |
| Candidata aplicada em produção quebra na hora | pulou a etapa de teste em staging | confira a ordem do runbook seguida | histórico de deploy vs. o processo documentado | sempre promover por staging, nunca aplicar direto o resultado bruto |
A pergunta que resolve metade destes casos
Antes de investigar qualquer sintoma, pergunte: a janela de observação cobriu este caminho de código? A maioria das falhas deste laboratório tem a mesma raiz — um caminho raro (mensal, de erro, de exceção) que não aconteceu dentro do período usado para gerar a policy. Ampliar a janela ou completar por leitura de código resolve a maior parte antes de qualquer outra investigação.
Limpeza: o que o destroy não leva
Este laboratório acrescenta pouco recurso, mas alguns deles sobrevivem ao terraform destroy — e um versionamento de policy que ninguém limpa pode bloquear a exclusão da role mais tarde.
# 1. Derrube o que o Terraform administra.
terraform destroy -auto-approve
# 2. VERSOES DA POLICY: o IAM guarda ate 5 versoes por policy gerenciada pelo
# cliente. O destroy remove a policy inteira, mas se voce for reaproveitar o
# nome, versoes antigas atrapalham a criacao de versao nova.
aws iam list-policy-versions --policy-arn "$POLICY_ARN" \
--query "Versions[?IsDefaultVersion==\`false\`].VersionId" --output text
# 3. BUCKET DA TRILHA: tem retencao propria (lifecycle de 1 ano) e nao pertence
# ao ciclo do laboratorio. Precisa estar vazio antes do delete.
aws s3 rm "s3://${PROJETO}-cloudtrail-${CONTA}" --recursive
aws s3api delete-bucket --bucket "${PROJETO}-cloudtrail-${CONTA}"
# 4. ANALISADOR DE ACESSO EXTERNO: nao cobra parado, mas continua avaliando
# recursos que talvez ja nao existam mais no seu ambiente de teste.
aws accessanalyzer delete-analyzer --analyzer-name "${PROJETO}-acesso-externo"
# 5. JOBS DE GERACAO DE POLICY: nao sao recurso persistente, mas o candidata.json
# local pode conter Resource com ARN da sua conta — nao deixe em repositorio publico.
rm -f candidata.json
# 6. CUIDADO: NAO apague a trilha de CloudTrail "de verdade" da conta se ela
# ja existia antes deste laboratorio. O script acima assume uma trilha CRIADA
# para o laboratorio; remover a trilha de producao da organizacao e outro raio
# de explosao completamente diferente.
# 7. Prova final: nada com o nome do projeto de pe.
aws resourcegroupstaggingapi get-resources \
--tag-filters Key=Projeto,Values=cadencia \
--query "ResourceTagMappingList[].ResourceARN" --output table| Recurso | Sai no destroy? | Cobra parado? | Por que fica |
|---|---|---|---|
| Policy final e boundary | sim | não | gerenciados pelo Terraform, sem estado externo |
| Versões antigas da policy | só a versão default | não | IAM guarda até 5 versões; as antigas continuam existindo até serem removidas explicitamente |
| Bucket da trilha do CloudTrail | só se vazio | sim, GB-mês | o destroy falha com bucket não vazio; log acumulado ao longo do laboratório fica para trás |
| Analisador de acesso externo | sim, se em Terraform | não — Access Analyzer é gratuito | analisador criado à mão no console não aparece no estado do Terraform |
| candidata.json local | não é recurso AWS | não | arquivo local com ARNs da conta; não deve ir para repositório público |
| Trilha de CloudTrail "de produção" pré-existente | não se aplica | sim, sempre existia | não confundir com a trilha CRIADA para este laboratório — nunca remova a de produção por engano |
Resumo: problema, peça e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| Ninguém sabe quais ações a task role realmente usa | geração de policy do IAM Access Analyzer | é evidência do CloudTrail, não suposição |
| Rascunho de nível de serviço ainda é amplo | revisão humana, ação por ação | NotAction sobre um serviço inteiro não é menor privilégio |
| Caminho raro pode faltar na candidata | janela de observação dimensionada pelo ciclo de negócio | é o único jeito de a evidência cobrir o que quase nunca roda |
| Promover direto arrisca indisponibilidade | validação em staging, com o caminho raro forçado | prova antes de expor o cliente ao AccessDenied |
| Alguém pode reanexar policy ampla amanhã | permission boundary como teto | limita mesmo o que uma concessão futura poderia permitir |
| Bucket pode expor dado a conta externa, mesmo sem IAM amplo | external access analyzer | avalia o que a resource policy permite, não o tráfego |
| Achado sem dono não vira correção | Security Hub concentrando o achado | achado sem lugar central expira sem ninguém tratar |
| "*" em Resource quando a ação não precisa | Resource restrito ao ARN específico | as quatro ações deste laboratório suportam recurso — nenhum "*" sobrevive |
| Falha | O que a protege | O que ela NÃO protege |
|---|---|---|
| Ação faltante quebra produção | teste em staging com caminho raro forçado | mudança de código futura que chame algo novo — exige regeneração periódica |
| Policy ampla reanexada por engano | permission boundary | não impede a tentativa, só o efeito — a tentativa ainda aparece em CloudTrail |
| Bucket exposto para fora da conta | external access analyzer | não cobre exposição por identidade assumível de fora — é outro tipo de achado |
| Rascunho aceito sem revisão | revisão humana obrigatória antes de aplicar | não impede erro humano NA revisão — só a ausência de revisão |
| Janela de observação curta | dimensionar pela cadência de negócio | não cobre feature nova lançada depois da janela — regeneração é contínua |
| Credencial estática vazada | task role sem chave de acesso, só STS via assume-role | não é o foco deste laboratório — é o L42 |
- A task role está com AdministratorAccess, herdado de um deploy sob pressão.
- Uma trilha do CloudTrail é criada, com destino em S3.
- A janela de observação roda por semanas, cobrindo o ciclo de fechamento mensal.
- StartPolicyGeneration lê a trilha e devolve um job assíncrono.
- GetGeneratedPolicy traz o rascunho — ação exata para alguns serviços, só o serviço para outros.
- A revisão humana restringe Resource e completa ação nos statements de nível de serviço.
- A candidata é aplicada primeiro numa task role de staging.
- O teste força de propósito o caminho de erro, além dos caminhos do dia a dia.
- A policy é promovida para produção por referência, com permission boundary como teto.
- O external access analyzer, rodando continuamente, confirma que nada ficou exposto para fora.
Desafio — sem roteiro
O requisito
O CloudTrail/Access Analyzer mostra uma ação nova sendo usada que a policy atual permite só por um wildcard amplo demais (`s3:*` quando só `s3:GetObject` é usado, por exemplo). Restrinja a policy à ação real e prove que a antiga permissão ampla não é mais aceita.
Critério de aceite — executável, não "verifique se funciona"
Depois da mudança, uma tentativa de executar a ação REMOVIDA (a que era coberta pelo wildcard mas nunca foi de fato usada) recebe `AccessDenied` — e a aplicação continua funcionando normalmente com a permissão restrita.
- Dica 1: IAM Access Analyzer tem uma funcionalidade específica de "geração de policy" a partir do histórico de uso do CloudTrail — ela sugere exatamente as ações que foram chamadas de verdade num período.
- Dica 2: Restrinja em duas etapas: primeiro ADICIONE a policy nova restrita ao lado da antiga e monitore por um tempo (nenhum erro de permissão aparecendo), só depois REMOVA a antiga — trocar direto é o jeito de descobrir em produção o que você esqueceu de cobrir.
- Dica 3: O teste que prova a restrição funcionou é uma chamada à ação REMOVIDA vindo do MESMO papel — se você testar com suas próprias credenciais de admin, o teste não prova nada sobre o papel da aplicação.
Lembrete de limpeza
Todo recurso que este desafio criar entra no mesmo `terraform destroy` do laboratório — nada fica para trás cobrando sozinho.
Perguntas frequentes
❓ IAM Access Analyzer gera a policy pronta para aplicar em produção?
❓ Preciso ter uma trilha do CloudTrail configurada, ou o histórico de 90 dias já basta?
❓ Quanto tempo de observação é suficiente para gerar a policy?
❓ O analisador de acesso externo é a mesma ferramenta que gera a policy?
❓ Permission boundary substitui a policy da role?
❓ O que acontece se eu aplicar a policy candidata direto em produção?
❓ Toda ação precisa de Resource específico, ou "*" às vezes é inevitável?
❓ Access Analyzer funciona para usuário IAM, ou só para role?
Fixando
GetGeneratedPolicy devolve um statement com NotAction: ["ec2:*", "iam:*", ...] e Resource "*" para um serviço que a role tocou. O que esse formato significa, e qual é a ação correta antes de aplicar a policy?
Um bucket S3 da Cadência tem uma resource policy que permite leitura para a conta 999988887777, mas o CloudTrail não mostra nenhuma chamada dessa conta nos últimos seis meses. O external access analyzer gera um finding mesmo assim. Por quê?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L01 no ar (task role existente), CLI da AWS, noção básica de policy JSON do IAM |
| Conhecimentos adquiridos | geração de policy a partir do CloudTrail; a diferença entre informação de nível de ação e de serviço; permission boundary como teto independente; achado de acesso externo como avaliação de policy, não de tráfego |
| Limitação que fica | a revisão continua manual — não escala além de poucas roles sem um pipeline (Nível 3 da evolução); a policy não se atualiza sozinha quando o código ganha uma chamada nova |
| Próximo exemplo recomendado | L42 — identidade de workload sem chave estática (task role, execution role, IRSA), que aprofunda a distinção que este laboratório já assumiu ao apontar a geração direto para a role |
| Também habilitado por este módulo | L48 (achado de segurança virando ticket automático) consome os achados do external access analyzer daqui; L43 (multi-conta com SCP) é o próximo nível de impedir o atalho, não só corrigi-lo depois |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: Using AWS Identity and Access Management Access Analyzer — a visão geral dos três tipos de análise (externo, interno, não utilizado) e da geração de policy; Generating a policy based on access activity — o procedimento exato de StartPolicyGeneration/GetGeneratedPolicy, a distinção entre informação de nível de ação e de serviço, e o fluxo de revisão antes de anexar; Understand how IAM Access Analyzer findings work — o mecanismo Zelkova do analisador de acesso externo, que avalia a policy e não o tráfego. Os valores de preço não aparecem neste módulo por decisão: consulte a página de preços do CloudTrail e o AWS Pricing Calculator, porque o custo real depende de quantos eventos de dados você decidir habilitar.
O que não foi verificado, e você deve conferir na sua conta
A lista exata de quais serviços retornam informação de nível de ação versus nível de serviço na geração de policy não foi fixada neste módulo com um número fechado — a documentação oficial ("IAM Access Analyzer policy generation services") mantém essa lista, e ela muda conforme a AWS adiciona suporte a mais serviços. Confirme antes de planejar sua revisão. Da mesma forma, os 35 dias usados como exemplo cobrem o ciclo de fechamento mensal DESTA aplicação fictícia; derive o seu da sua própria cadência de negócio, não copie o número.
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…