Lab 04 — Segredo fora do código, com rotação que não derruba a aplicação
O problema, e a empresa que o tem
A Cadência é a mesma equipe de duas pessoas dos laboratórios anteriores: uma API de pedidos em .NET 8, em ECS Fargate, com RDS PostgreSQL em sub-rede privada. Depois do L03, ela publica sem janela de indisponibilidade. E a senha do banco continua escrita em appsettings.Production.json, versionada no Git.
A descoberta veio de fora. Um cliente pediu o relatório de segurança do fornecedor, e a pergunta do formulário não era "onde vocês guardam credenciais". Era outra: "qual é a política de rotação e quando foi a última troca". A resposta honesta era "nunca", e a senha era a mesma desde a criação do ambiente, conhecida por dois ex-prestadores e presente em três máquinas.
O reflexo do time foi o previsível e o mais comum: criar o segredo no AWS Secrets Manager, ler o valor no Terraform e injetá-lo na definição de task. A varredura do repositório ficou verde no mesmo dia. Nada disso trocou a senha, e é aqui que o laboratório começa — porque o defeito que sobrou não gera erro, não aparece em log e não dispara alarme nenhum.
O que este laboratório NÃO é
Não é o laboratório de criptografia. Aqui o KMS aparece pelo que é preciso para entender envelope, política de chave e a diferença entre girar a chave e girar o segredo. Chave com material próprio, chave multi-região, trilha de uso por operação e política de chave em profundidade são o L46, e ele depende deste. Também não é o laboratório de identidade de workload: autenticação de banco por IAM, que dispensa senha, é decisão do L42.
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.
- Distinguir segredo de parâmetro pelo ciclo de vida, e não pela sensibilidade aparente.
- Explicar por que o campo `environment` da definição de task vaza sem exigir privilégio.
- Nomear as quatro etapas da rotação e dizer o que cada rótulo de versão marca em cada uma.
- Derivar por que a aplicação precisa reler, a partir do momento em que o ECS resolve o valor.
- Escolher entre usuário único e usuários alternados citando o efeito de cada um na disponibilidade.
- Descrever a criptografia de envelope e provar que ela exige duas permissões distintas.
- Decidir quando o Parameter Store basta, com o critério de custo e o de rotação.
- Diagnosticar rotação que falhou em silêncio a partir do estado dos rótulos de versão.
- Provar que a credencial girou, sem imprimir a credencial em nenhum lugar.
O que a certificação cobra disto
| Conceito | Certificação | Como aparece aqui | O que dominar |
|---|---|---|---|
| Segredo vs parâmetro | SAA-C03, SOA-C02, DVA-C02 | a mesma aplicação usa os dois, e a linha divisória é o ciclo de vida | que a decisão é rotação e geração de senha, não sensibilidade do dado |
| Rotação automática | SAA-C03, SOA-C02 | função Lambda com agenda, e as quatro etapas observadas | quem executa a troca, e que o Parameter Store não a tem |
| Rótulos de versão | SOA-C02, DVA-C02 | AWSCURRENT, AWSPENDING e AWSPREVIOUS durante uma rotação real | que a leitura sem versão explícita devolve AWSCURRENT sempre |
| Criptografia de envelope | SAA-C03, SAP-C02 | a policy tem duas declarações, e negar uma bloqueia a leitura | chave de dados vs chave do KMS, e por onde cada uma é autorizada |
| Rotação de chave vs rotação de segredo | SAA-C03 | a CMK gira em 365 dias e o segredo em 30, sem relação entre os dois | que girar a chave não recifra dado nem troca credencial |
| Rotação gerenciada de credencial do RDS | SAA-C03, SOA-C02 | o segredo do mestre gira sem Lambda, em sete dias por padrão | quais serviços a oferecem, e por que a aplicação não deve usar o mestre |
| Injeção de segredo no ECS | DVA-C02, SOA-C02 | `secrets` em vez de `environment`, e o limite do `secrets` | que o valor é resolvido no arranque e exige nova task para mudar |
| Execution role vs task role | DVA-C02, SAA-C03 | a primeira resolve `secrets`, a segunda é quem a aplicação usa | qual das duas precisa da permissão em cada um dos dois caminhos |
Onde isto costuma ser cobrado errado
A pergunta clássica descreve uma task no Fargate que não consegue ler um segredo, com uma policy de `secretsmanager:GetSecretValue` que está visivelmente correta, e pede a causa. A resposta é `kms:Decrypt` ausente sobre a chave gerenciada pelo cliente — e ela só é derivável para quem sabe que o cofre não devolve o valor sem antes decifrar a chave de dados. A segunda variante troca de lado: a policy está na task role quando o segredo é injetado por `secrets`, caso em que quem precisa da permissão é a execution role, porque quem resolve o campo é o agente do ECS e não a sua aplicação.
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 |
|---|---|---|
| Idade máxima da credencial | 30 dias | obriga agenda de rotação; e como a agenda existe, obriga a aplicação a reler |
| Erro visível ao cliente durante a rotação | zero | proíbe injeção de arranque no caminho do banco, e exige cache curto com nova tentativa |
| Implantação para trocar a senha | não pode existir | elimina `environment` e `secrets` para a credencial do banco; a leitura vai para o SDK |
| Senha em repositório e em estado do Terraform | zero ocorrências | proíbe `random_password` e data source de versão de segredo; o valor nasce na rotação |
| Quem pode ler a credencial | somente a task da API | a permissão fica na task role, com ARN específico, não em papel de inventário |
| Auditoria de quem leu qual versão | obrigatória | exige CMK própria, porque é o contexto de cifragem no evento do KMS que identifica a versão |
| Rotação falha percebida no mesmo dia | obrigatório | acrescenta filtro de métrica sobre a trilha e alarme, mais regra do AWS Config |
| Tráfego do cofre saindo para a internet | não pode | exige endpoint de interface na sub-rede privada, e é o que dispensa NAT para esta chamada |
| Custo de configuração não sensível | próximo de zero | move host, porta e pool para o Parameter Store na camada padrão, que não cobra por parâmetro |
Arquitetura mínima: o segredo que saiu do repositório e continuou legível
Este é o desenho que a Cadência montou no dia seguinte ao formulário do cliente, e ele é legítimo como ponto de partida: o valor está num cofre, cifrado, com permissão de IAM. O laboratório começa medindo o que sobrou — porque um comando que devolve a senha torna o defeito discutível, e "acho que está tudo certo" não.
- → create-secret uma vez, na criação do ambiente
- → data source devolve o valor para o plano e o estado
- → revisão nova com a senha dentro de environment
- → variável de ambiente resolvida no arranque
- → autenticação com a senha herdada do arranque
- → chave de dados que cifra a versão do segredo
- → describe-task-definition traz a senha na resposta
- Fora da AWS
- Segurança e identidade
- Compute
- Banco de dados
Este desenho é implantável e é o que a maioria das equipes tem depois de "tirar o segredo do repositório": o valor mora no cofre, e uma cópia em texto claro mora no estado do Terraform e na definição de task. Percorra os passos e repare que nenhuma linha está errada — o que falta é ciclo de vida, e a falta dele não gera erro nenhum.
- O valor sai do arquivo e entra no estado. Ler o segredo no Terraform com um data source resolve o valor em texto claro durante o plano. Ele passa a existir no arquivo de estado e em qualquer saída de plano guardada por um pipeline. Você trocou um arquivo versionado por um arquivo de estado — que costuma ter menos controle de acesso, não mais.
- O campo environment é inventário, não cofre. A definição de task é metadado do plano de controle. Quem tem permissão de descrevê-la recebe o valor exatamente como escrito, e essa permissão é rotineiramente concedida em papéis de leitura ampla, junto com listar cluster e ver serviço. O vazamento não exige privilégio: exige inventário.
- A task lê uma vez e nunca mais. A variável de ambiente é resolvida quando o contêiner arranca. Depois disso, o processo tem uma cópia na memória e nenhum mecanismo para descobrir que ela mudou. É a razão pela qual este desenho não pode receber rotação: girar a senha aqui derruba a aplicação até a próxima implantação.
- Sem rotação, a senha tem a idade do ambiente. A pergunta que a auditoria faz não é "onde está a senha", é "quando ela mudou pela última vez". Neste desenho a resposta é "nunca", e ela é a mesma para todo mundo que já passou pela equipe. Tirar do repositório não expira credencial.
- A chave é da AWS, e a política dela não é sua. A chave gerenciada `aws/secretsmanager` cifra o segredo e cumpre o requisito de criptografia em repouso. O que ela não permite é condição própria: você não consegue exigir contexto de cifragem, restringir por principal nem auditar a chave separadamente do serviço, porque a política é estabelecida pela AWS.
- Por que quase todo mundo para exatamente aqui. Porque é o menor número de linhas que faz a varredura de segredo no repositório ficar verde. O time procurou "não commitar senha", resolveu isso, e a ausência de rotação não produz alarme, incidente nem erro de teste. Defeito que não dói não é priorizado — é por isso que ele dura anos.
Antes de mudar qualquer coisa, meça as duas superfícies. A primeira é o repositório, inclusive o histórico. A segunda é o plano de controle, e é a que quase ninguém olha.
# Rode ANTES de mudar nada. Os dois numeros que sairem daqui sao a linha de base.
# (1) O historico do Git ainda serve a senha a quem clonar o repositorio.
git log --all -S "Password=" --oneline -- "*.json" | wc -l
# Na Cadencia: 14 commits. Trocar o arquivo no HEAD nao remove nenhum deles.
# (2) O plano de controle devolve o valor a quem tem permissao de inventario.
aws ecs describe-task-definition --task-definition ffv-lab-api \
--query 'taskDefinition.containerDefinitions[].environment[?name==`DB_PASSWORD`]'
# Na Cadencia: 1 ocorrencia, em texto claro, na resposta de uma chamada de leitura.
# (3) A idade da credencial. E a pergunta que o formulario fez.
aws secretsmanager describe-secret --secret-id ffv-lab/banco/app-rw \
--query '{gira:RotationEnabled,ultima:LastRotatedDate,criado:CreatedDate}'
# Na Cadencia: gira=false, ultima=null, criado ha 412 dias.Os dois vazamentos deste desenho não são o mesmo problema
O primeiro é o histórico do Git, e ele é irreversível: o valor esteve num repositório clonado por gente que saiu, então a única correção real é GIRAR a credencial, não apagar o arquivo. Reescrever histórico dá a sensação de resolver e não recupera cópia nenhuma. O segundo é o campo `environment`, e ele é corrigível: basta parar de escrever ali. Tratar os dois como "senha exposta" leva a equipe a fazer a limpeza que não resolve e a esquecer a rotação, que é a que resolve.
Arquitetura para produção
Cada peça nova abaixo rastreia a uma linha da tabela de requisitos. A CMK vem da auditoria por versão; o endpoint de interface, da proibição de tráfego pela internet; o segundo segredo, da separação entre quem migra e quem atende; o alarme, do requisito de perceber rotação falha no mesmo dia. Se você não conseguir apontar o requisito, a peça é adorno — e este desenho não tem nenhuma.
- → somente o ARN do segredo, como variável comum
- → nome e valor do que não é sensível, versionados
- → host, porta e tamanho de pool lidos no arranque
- → GetSecretValue assinado pela task role
- → pedido do rótulo AWSCURRENT, sem sair da rede da AWS
- → GenerateDataKey ao gravar, Decrypt ao ler
- → conexão física nova autentica com a senha vigente
- → grava em AWSPENDING e depois move AWSCURRENT
- → ALTER USER na própria conta, e teste de conexão
- → rotação gerenciada troca a senha do mestre
- → evento com SecretARN e SecretVersionId no contexto
- → filtro de métrica sobre rotação falha e decifragem negada
- Fora da AWS
- Compute
- Rede e entrega
- Banco de dados
- Segurança e identidade
- Gestão e governança
A mudança estrutural não é uma caixa a mais: é a INVERSÃO do sentido da seta. No desenho anterior o segredo era empurrado para a task no momento da implantação; aqui a task o busca em tempo de execução, com a identidade dela, e volta a buscar por temporizador. É isso que transforma rotação de risco em rotina.
- O que vai para o Git é o ARN, não o valor. O ARN do segredo é público por natureza: ele identifica, não autoriza. Quem o tem sem a permissão não lê nada, e quem tem a permissão não precisa dele escondido. Reconhecer isso é o primeiro exercício da distinção do módulo: identificador é parâmetro, conteúdo é segredo.
- A leitura é em tempo de execução, e por dentro da rede. A task assume a task role e chama o cofre ela mesma. Duas consequências: o valor nunca aparece em metadado do plano de controle, e a permissão de ler passa a ser da aplicação, não do agente que arranca o contêiner. Com endpoint de interface, a chamada não usa rota de saída.
- Envelope: negar uma das duas permissões já barra a leitura. O cofre não cifra o segredo com a CMK. Ele pede ao KMS uma chave de dados AES de 256 bits, cifra o valor fora do KMS com ela, descarta a versão em texto claro e guarda a versão cifrada no metadado do segredo. Para ler, decifra a chave de dados primeiro. Por isso são duas permissões, e negar `kms:Decrypt` basta para bloquear quem tem `secretsmanager:GetSecretValue`.
- A rotação move rótulo, e é isso que a torna reversível. A senha nova nasce sob `AWSPENDING`, o banco passa a aceitá-la, a função a testa, e só então `AWSCURRENT` muda de versão — momento em que a versão anterior recebe `AWSPREVIOUS`. Nenhuma etapa apaga a última versão boa, e é por isso que uma rotação interrompida no meio não deixa o segredo sem valor válido.
- A conexão já aberta não sente nada; a nova, sim. O PostgreSQL autentica na abertura da conexão, não a cada consulta. Depois de uma rotação, o pool continua servindo as conexões que já existiam, e o erro só aparece quando ele abre conexão física nova — o que pode levar horas. É o atraso que desconecta o sintoma da causa, e a razão de o alarme ter de olhar a rotação, não só a aplicação.
- O mestre gira por outro mecanismo, e a aplicação não o usa. A credencial mestre do RDS usa rotação gerenciada: o serviço configura e conduz a troca, sem Lambda nenhuma, a cada sete dias por padrão. A própria documentação recomenda que a aplicação use um usuário criado com o privilégio mínimo, e não o mestre — então os dois segredos existem por motivos diferentes e têm leitores diferentes.
- O que não tem prazo de validade fica no Parameter Store. Nome do host, porta, tamanho de pool e nível de log não giram, não expiram e não têm dono de ciclo de vida. Guardá-los como segredo paga preço por segredo por mês para nada e, pior, dilui a lista de segredos até ninguém conseguir responder quantos existem de verdade.
A diferença estrutural em relação ao desenho anterior não é uma caixa a mais: é o sentido da seta. Antes, o segredo era empurrado para a task no instante da implantação, por quem implantava. Agora a task o busca, com a identidade dela, e volta a buscar sem que ninguém implante nada. Toda a rotação depende dessa inversão, e nenhuma configuração a substitui.
O ajuste com maior efeito por linha alterada
Das três parcelas, a única que se controla por configuração é o intervalo do cache. O componente de cache da AWS renova de hora em hora por padrão; baixar para 5 minutos reduz a janela de recusa por um fator de doze e custa cerca de 24 leituras por hora por task em vez de 2. Otimizar a nova tentativa — que é onde a atenção costuma ir — não encurta a janela: apenas a torna invisível ao cliente, o que é valioso e é outra coisa.
Uma rotação, ponta a ponta
Os nomes dos rótulos não são jargão: eles são o mecanismo. A senha nova nasce sob AWSPENDING, e AWSCURRENT só se move na última etapa — momento em que a versão anterior recebe AWSPREVIOUS. Isso é observável com describe-secret durante a rotação, e a seção de provas faz exatamente isso.
A consequência contraintuitiva, e vale saber com precisão
Ligar rotação num desenho de injeção de arranque não produz falha no momento da rotação. Produz falha DEPOIS, quando o pool de conexões precisa abrir uma conexão física nova — porque o PostgreSQL autentica na abertura e não a cada consulta. As conexões que já estavam abertas continuam funcionando por horas com a senha antiga. É o pior formato possível para um defeito: o sintoma chega descolado da causa, num pico de tráfego ou num reinício de task, e ninguém liga o erro de autenticação a uma rotação que aconteceu na madrugada anterior.
Vale precisão também sobre o que o cofre guarda. Ele não mantém histórico linear de versões: rastreia três posições por rótulo, e AWSCURRENT sempre existe. Versão sem rótulo é considerada obsoleta e removida quando passam de cem, e nenhuma versão criada há menos de 24 horas é removida. Você pode acrescentar rótulos próprios, até vinte por segredo, e duas versões não podem carregar o mesmo rótulo — o que é exatamente o que torna AWSCURRENT uma referência confiável.
// O que o Secrets Manager entrega a cada uma das quatro invocacoes da funcao.
// A MESMA funcao e chamada quatro vezes, com `Step` diferente — e nao quatro
// funcoes. Quem espera quatro recursos procura tres que nao existem.
{
"Step": "createSecret",
"SecretId": "arn:aws:secretsmanager:us-east-1:111122223333:secret:ffv-lab/banco/app-rw-AbCdEf",
// Identificador da versao nova. E o que da idempotencia: se a rotacao falhar
// depois desta etapa, a tentativa seguinte reencontra a mesma versao em vez de
// gerar outra senha e deixar lixo em AWSPENDING.
"ClientRequestToken": "c3d4e5f6-90ab-cdef-fedc-ba987EXAMPLE",
// Presente apenas em rotacao com papel assumido ou entre contas, para o servico
// validar a identidade do papel. No caso simples, ele nao vem.
"RotationToken": null
}Uma divergência de nomenclatura que você vai encontrar
A documentação descreve as quatro etapas com os nomes de método `createSecret`, `setSecret`, `testSecret` e `finishSecret`, e no mesmo texto lista os valores válidos do parâmetro `Step` como `create_secret`, `set_secret`, `test_secret` e `finish_secret`. Os modelos publicados pela AWS comparam a forma em camelo. Não consegui resolver essa divergência na documentação oficial, e ela importa se você escrever a sua própria função: confira qual forma o SEU modelo compara antes de tratar o parâmetro, porque uma comparação que nunca casa faz a função retornar sem fazer nada e a rotação expirar sem erro explícito.
As decisões, e o que se perde em cada uma
📋 Uma API .NET 8 em ECS Fargate com RDS PostgreSQL precisa parar de guardar a senha do banco no repositório e passar a girá-la, com duas pessoas no time, sem plantão e sem janela de manutenção para reimplantar a cada troca.
A injeção nativa do ECS pelo campo `secrets` resolve o vazamento e não resolve a rotação: a documentação é explícita ao dizer que, se o segredo muda, é preciso forçar uma nova implantação ou iniciar uma task nova para obter o valor novo. Ler em tempo de execução é a única opção que satisfaz os dois requisitos ao mesmo tempo, e o cache de 5 minutos devolve o custo e a latência que a leitura direta cobraria. A estratégia de usuário único é a que a AWS chama de mais simples e apropriada para a maioria dos casos; ela deixa uma janela curta de recusa, e essa janela é exatamente o que a nova tentativa com recuo cobre.
Alt: Injeção nativa pelo campo `secrets` do ECS — Corrige o defeito central do desenho mínimo — o valor sai do inventário do plano de controle e passa a ser resolvido pelo agente com a execution role. O que ela não faz é reler: o valor é um instantâneo do arranque, então cada rotação exige uma implantação. Continua sendo a escolha certa para segredo que não gira, como credencial de registro privado.
Alt: Autenticação de banco por IAM, sem senha nenhuma — É o desenho que elimina o problema em vez de administrá-lo: o token dura quinze minutos e não há o que girar. Cobra limite de conexões novas por segundo mais baixo e obriga a gerar token a cada conexão, o que muda a forma de usar o pool. Fica fora deste laboratório porque o entregável do catálogo é credencial rotacionada, e porque a decisão pertence à banda de identidade.
Alt: Estratégia de usuários alternados — Dá a maior disponibilidade durante a troca, porque as duas contas seguem válidas. O custo escondido é que o NOME DO USUÁRIO muda a cada rotação, e um provedor que só devolve senha não basta: a fonte de dados precisa ser reconstruída quando o usuário muda. Exige também um segredo elevado, porque a conta não consegue clonar a si mesma.
Alt: Parameter Store com SecureString para tudo — Sai mais barato — a camada padrão não tem custo adicional por parâmetro — e cifra com KMS igual. Não tem rotação, não gera senha aleatória e não compartilha entre contas. É a escolha certa para configuração, e a escolha errada para credencial que precisa expirar.
Alt: Sidecar que escreve o segredo num volume compartilhado — É um dos padrões que a documentação do ECS recomenda para evitar variável de ambiente, e resolve o vazamento por `docker inspect`. Continua sendo leitura de arranque, a menos que o sidecar releia — e aí você mantém dois processos para fazer o que o SDK com cache faz em um.
| Decisão | Escolha | Alternativas | Motivo | O que se perde |
|---|---|---|---|---|
| Como a aplicação obtém a senha | SDK em tempo de execução, com cache de 5 min | `environment`; `secrets` do ECS; sidecar em volume; arquivo montado | é a única forma que atende rotação sem implantação | código na aplicação e uma dependência nova; o desenho deixa de ser "só infraestrutura" |
| Estratégia de rotação | usuário único | usuários alternados; rotação manual; sem rotação | é a que a AWS chama de mais simples e apropriada para a maioria dos casos, e mantém o nome do usuário estável | uma janela curta em que o banco pode recusar a credencial girada, coberta por nova tentativa |
| Chave de criptografia | CMK própria, com rotação anual | `aws/secretsmanager`; chave com material importado | permite condição por serviço chamador e auditoria da chave separada do serviço | preço por chave por mês, e mais duas rotações cobradas; e a chave passa a ser um recurso que se perde |
| Onde mora o não sensível | Parameter Store, camada padrão | tudo no Secrets Manager; variável de ambiente; arquivo de configuração | não cobra por parâmetro na camada padrão e mantém a lista de segredos legível | dois lugares para procurar configuração, e duas permissões para conceder |
| Credencial do usuário mestre | gerida pelo próprio RDS, rotação gerenciada | segredo criado à mão com função de rotação; senha em variável | rotação sem Lambda alguma, em sete dias por padrão, e o RDS mantém as duas pontas em sincronia | a chave do segredo não pode ser trocada depois, e o recurso não convive com criação de réplica de leitura |
| Intervalo do cache | 5 minutos | uma hora, que é o padrão do componente; sem cache; leitura por requisição | é o teto da janela de recusa, e a única parcela da fórmula sob seu controle | cerca de doze vezes mais chamadas de leitura do que o padrão, o que aparece na fatura |
| Alcance de rede da leitura | endpoint de interface na sub-rede privada | NAT Gateway; sub-rede pública | a chamada não sai da rede da AWS e a sub-rede continua sem rota de saída | preço por hora ligada por zona de disponibilidade, mais byte processado |
A dívida que este desenho cria, e que ele não paga
A aplicação passou a depender do cofre para funcionar. Se o Secrets Manager estiver indisponível na região no instante em que o temporizador do provedor dispara, o valor anterior continua em cache e a aplicação sobrevive — mas se a task reiniciar nesse momento, ela não sobe. Isso não é motivo para voltar à injeção de arranque; é motivo para o intervalo de falha do provedor ser curto e para a leitura no arranque ter nova tentativa. Réplica do segredo em outra região é resposta de arquitetura multirregião, e ela pertence ao L58.
Construir: a chave, e o que ela compra além de cifrar
Vale começar pela pergunta que quase nunca é feita: por que criar uma chave própria, se a chave gerenciada pela AWS já cifra o segredo? A resposta não é criptográfica — as duas usam a mesma criptografia. É de política. A política da chave gerenciada é estabelecida pelo serviço e você não pode alterá-la; com chave própria, você declara quem pode usá-la, exige que o uso passe pelo Secrets Manager e audita a chave separadamente do cofre.
# cofre.tf — a chave propria, o segredo da aplicacao e o segredo do mestre
# ── A CMK: o que ela compra em relacao a `aws/secretsmanager` ─────────────────
# A chave gerenciada pela AWS ja cifra o segredo e ja cumpre "criptografado em
# repouso". O que ela NAO permite e politica propria: com CMK voce consegue
# exigir que a chave so seja usada atraves do Secrets Manager, negar uso direto,
# e auditar a chave separadamente do servico. Se o seu requisito e apenas cifrar,
# a chave gerenciada basta e sai de graca — diga isso em vez de criar CMK por
# reflexo.
resource "aws_kms_key" "segredos" {
description = "${var.projeto} — segredos de aplicacao"
# Rotacao de material criptografico da CMK. ATENCAO ao que ela NAO faz:
# nao gira o segredo, nao recifra dado antigo e nao troca chave de dados
# ja emitida. Ela troca o material atual; o KMS escolhe sozinho a versao
# correta para decifrar. Sao dois relogios diferentes com o mesmo nome.
enable_key_rotation = true
rotation_period_in_days = 365 # padrao quando omitido; explicito para o leitor
# Espera antes da exclusao definitiva: 7 a 30 dias, padrao 30. Sete e o
# minimo, e e o que se usa em laboratorio para nao arrastar recurso.
deletion_window_in_days = 7
tags = var.tags
}
resource "aws_kms_alias" "segredos" {
name = "alias/${var.projeto}-segredos"
target_key_id = aws_kms_key.segredos.key_id
}
# A politica de chave e onde o menor privilegio de criptografia mora. A condicao
# `kms:ViaService` amarra o uso ao Secrets Manager: ninguem decifra a chave de
# dados chamando o KMS diretamente, nem com permissao de IAM ampla.
data "aws_iam_policy_document" "chave_segredos" {
statement {
sid = "AdministracaoPelaConta"
effect = "Allow"
actions = ["kms:*"]
# `Resource: "*"` numa POLITICA DE CHAVE significa "esta chave", nao "todas
# as chaves": o documento e anexado ao recurso, e nao existe outro alvo
# possivel. E o unico lugar em que o curinga nao amplia nada.
resources = ["*"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.atual.account_id}:root"]
}
}
statement {
sid = "UsoSomenteAtravesDoSecretsManager"
effect = "Allow"
actions = [
"kms:Decrypt",
"kms:GenerateDataKey",
"kms:DescribeKey",
]
resources = ["*"]
principals {
type = "AWS"
identifiers = [aws_iam_role.task.arn, aws_iam_role.rotacao.arn]
}
condition {
test = "StringEquals"
variable = "kms:ViaService"
values = ["secretsmanager.${var.regiao}.amazonaws.com"]
}
}
}
resource "aws_kms_key_policy" "segredos" {
key_id = aws_kms_key.segredos.id
policy = data.aws_iam_policy_document.chave_segredos.json
}
# ── Segredo do MESTRE: rotacao gerenciada, sem Lambda ────────────────────────
# Com `manage_master_user_password`, o RDS gera a senha, cria o segredo e o gira
# a cada sete dias por padrao. A aplicacao NAO usa este segredo: a propria
# documentacao recomenda um usuario com o privilegio minimo em vez do mestre.
# Este e o segredo de migracao e de operacao.
resource "aws_db_instance" "principal" {
identifier = "${var.projeto}-db"
engine = "postgres"
username = "mestre"
manage_master_user_password = true
master_user_secret_kms_key_id = aws_kms_key.segredos.arn
# Depois que o RDS assume a gestao, a chave do segredo NAO pode mais ser
# trocada. Escolher a CMK aqui, na criacao, ou aceitar a gerenciada para
# sempre — nao ha terceira opcao.
# O resto da instancia vem do L01 e nao muda aqui.
instance_class = var.classe_db
allocated_storage = 20
storage_encrypted = true
kms_key_id = aws_kms_key.segredos.arn
db_subnet_group_name = aws_db_subnet_group.privada.name
vpc_security_group_ids = [aws_security_group.db.id]
skip_final_snapshot = true # laboratorio; em producao, false
tags = var.tags
}
# ── Segredo da APLICACAO: o que gira por funcao, e o que a task le ───────────
# O valor inicial NAO e escrito aqui. `random_password` iria para o estado do
# Terraform em texto claro, que e o defeito do desenho minimo. Em vez disso o
# segredo nasce com estrutura e senha vazia, e a PRIMEIRA rotacao preenche.
resource "aws_secretsmanager_secret" "app" {
name = "${var.projeto}/banco/app-rw"
description = "Credencial do usuario de aplicacao; gira por funcao Lambda"
kms_key_id = aws_kms_key.segredos.arn
# Espera de recuperacao: 7 a 30 dias. Enquanto o segredo esta agendado para
# exclusao ele fica inacessivel e NAO e cobrado — mas o NOME continua
# ocupado, e recriar com o mesmo nome falha ate a espera terminar.
recovery_window_in_days = 7
tags = var.tags
}
# A estrutura JSON e obrigatoria para os modelos de rotacao da AWS, e as chaves
# sao sensiveis a caixa. `masterarn` so entra na estrategia de usuarios
# alternados, e por isso NAO aparece aqui.
resource "aws_secretsmanager_secret_version" "app_inicial" {
secret_id = aws_secretsmanager_secret.app.id
secret_string = jsonencode({
engine = "postgres"
host = aws_db_instance.principal.address
port = 5432
dbname = var.nome_banco
username = "app_rw"
password = "TROCADO_NA_PRIMEIRA_ROTACAO"
dbInstanceIdentifier = aws_db_instance.principal.identifier
})
# A senha real e escrita pela rotacao. Sem este bloco, cada `apply` reverteria
# o segredo ao valor de espaco reservado e derrubaria a aplicacao.
lifecycle {
ignore_changes = [secret_string]
}
}
# O ARN e parametro, nao segredo: publicavel, versionavel, estavel. E ele que
# vai para a definicao de task.
output "arn_segredo_app" {
value = aws_secretsmanager_secret.app.arn
description = "Identificador do segredo; nao autoriza nada por si"
}
Girar a chave não gira o segredo, e o nome engana
Rotação de chave do KMS troca o material criptográfico atual. Ela não gira as chaves de dados que a chave gerou, não recifra dado nenhum e — a parte que mais confunde — não tem qualquer efeito sobre a senha guardada no segredo. Uma equipe que liga rotação anual da CMK e marca "rotação de credencial" como resolvido tem dois relógios com o mesmo nome e nenhum deles apontando para o requisito. A própria documentação é explícita: rotação de chave não mitiga o efeito de uma chave de dados comprometida.
O detalhe de sequência que trava o `apply` depois
Depois que o RDS assume a gestão da credencial mestre, a chave usada para cifrar aquele segredo não pode mais ser trocada. Se você criar a instância com a chave gerenciada e quiser a CMK depois, o caminho é desligar a gestão automática e ligá-la de novo — o que gera senha nova. Decidir isso na criação custa uma linha; decidir depois custa uma janela.
Construir: quem gira, e o alcance de rede que quase todos esquecem
A função de rotação é o único componente do desenho que precisa falar com o banco E com o cofre. Como o banco está em sub-rede privada, ela também precisa estar — e é aqui que nasce o defeito mais comum de rotação: a função existe, a agenda existe, e ela expira tentando abrir a conexão. Nada disso aparece na aplicação, que continua respondendo normalmente com a senha antiga.
# rotacao.tf — quem gira, com que alcance de rede, e em que agenda
# A funcao de rotacao precisa alcancar a porta 5432 do banco. Este e o defeito
# numero um de rotacao que falha calada: a funcao existe, a agenda existe, e ela
# expira tentando abrir conexao. Sem sub-rede e grupo de seguranca, nada disso
# aparece na aplicacao — aparece uma semana depois, como erro de autenticacao.
resource "aws_security_group" "rotacao" {
name = "${var.projeto}-rotacao"
vpc_id = aws_vpc.principal.id
egress {
description = "Postgres no banco"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.db.id]
}
egress {
description = "API do Secrets Manager pelo endpoint de interface"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.endpoints.id]
}
tags = var.tags
}
# O grupo do banco referencia o GRUPO da funcao, nao faixa de IP. Faixa de IP
# num grupo de banco e o antipadrao que L02 e L44 tratam.
resource "aws_security_group_rule" "db_aceita_rotacao" {
type = "ingress"
security_group_id = aws_security_group.db.id
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = aws_security_group.rotacao.id
description = "Rotacao de credencial"
}
# A funcao vem do modelo publicado pela AWS para PostgreSQL em usuario unico.
# Escrever a sua propria so se justifica para segredo que nao e de banco.
resource "aws_serverlessapplicationrepository_cloudformation_stack" "rotacao" {
name = "${var.projeto}-rotacao-postgres"
application_id = "arn:aws:serverlessrepo:us-east-1:297356227824:applications/SecretsManagerRDSPostgreSQLRotationSingleUser"
capabilities = ["CAPABILITY_IAM", "CAPABILITY_RESOURCE_POLICY"]
parameters = {
functionName = "${var.projeto}-rotacao-postgres"
endpoint = "https://secretsmanager.${var.regiao}.amazonaws.com"
vpcSubnetIds = join(",", aws_subnet.privada[*].id)
vpcSecurityGroupIds = aws_security_group.rotacao.id
}
}
# A agenda. Quatro horas e o intervalo minimo suportado; sete dias e o que o RDS
# usa por padrao no segredo do mestre. Trinta dias e o valor deste laboratorio,
# e ele nao e universal: escolha pelo tempo que uma credencial vazada pode
# continuar util no SEU caso, nao pelo que parece prudente.
resource "aws_secretsmanager_secret_rotation" "app" {
secret_id = aws_secretsmanager_secret.app.id
rotation_lambda_arn = aws_serverlessapplicationrepository_cloudformation_stack.rotacao.outputs["RotationLambdaARN"]
rotation_rules {
schedule_expression = "rate(30 days)"
# Janela de duas horas. Ela nao pode invadir a janela seguinte, e comeca a
# meia-noite UTC quando nao se declara hora de inicio.
duration = "2h"
}
}
# ── Permissao da task: menor privilegio de verdade, nos dois servicos ────────
data "aws_iam_policy_document" "task_le_segredo" {
statement {
sid = "LeSomenteOSegredoDaAplicacao"
effect = "Allow"
actions = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"]
resources = [aws_secretsmanager_secret.app.arn]
# DescribeSecret entra porque o componente de cache do lado do cliente o
# exige para saber quais versoes existem. Sem ele, a leitura falha com
# AccessDenied numa chamada que o codigo nao faz explicitamente.
}
statement {
sid = "DecifraSomenteAtravesDoCofre"
effect = "Allow"
actions = ["kms:Decrypt"]
resources = [aws_kms_key.segredos.arn]
condition {
test = "StringEquals"
variable = "kms:ViaService"
values = ["secretsmanager.${var.regiao}.amazonaws.com"]
}
# As duas declaracoes acima sao a criptografia de envelope aparecendo na
# policy: uma autoriza pedir o segredo, a outra autoriza decifrar a chave de
# dados que o protege. Negar qualquer uma das duas bloqueia a leitura.
}
statement {
sid = "LeParametroDoAmbiente"
effect = "Allow"
actions = ["ssm:GetParameters", "ssm:GetParametersByPath"]
resources = ["arn:aws:ssm:${var.regiao}:${data.aws_caller_identity.atual.account_id}:parameter/${var.projeto}/*"]
}
}
resource "aws_iam_role_policy" "task_le_segredo" {
role = aws_iam_role.task.id
policy = data.aws_iam_policy_document.task_le_segredo.json
}
A permissão que precisa de `*`, e por quê
A função de rotação chama `secretsmanager:GetRandomPassword` para gerar a senha nova. Essa operação não age sobre nenhum recurso — ela produz uma cadeia aleatória — e por isso não aceita ARN em `Resource`. O modelo publicado pela AWS já a declara com `*`, e essa é a justificativa. Toda outra ação da função é restrita: `GetSecretValue`, `PutSecretValue` e `UpdateSecretVersionStage` apontam para o ARN do segredo, e `kms:Decrypt` aponta para a chave, com a condição de serviço chamador. Curinga com motivo escrito é decisão; sem motivo, é preguiça.
Sobre a agenda: quatro horas é o intervalo mínimo suportado, e sete dias é o padrão que o RDS usa na credencial mestre. Trinta dias, aqui, vem do requisito declarado — e não é um número universal. O critério é quanto tempo uma credencial vazada continuaria útil no seu caso. Num banco que só a aplicação alcança, atrás de sub-rede privada, trinta dias é defensável; num banco alcançável de fora, o número correto é bem menor, e a resposta certa provavelmente não é rotação mais agressiva, é fechar a rede.
Construir: o que NÃO é segredo, e a definição de task sem valor dentro
Esta é a seção que carrega a distinção central do módulo em forma de código. Host, porta e tamanho de pool não giram, não expiram e não têm dono de ciclo de vida: são parâmetros. O ARN do segredo também é parâmetro — ele identifica, não autoriza. E a chave de um parceiro que não oferece rotação automática é um segredo sem relógio, o que faz do Parameter Store com SecureString o lugar certo para ela.
# parametro.tf — o que NAO e segredo, e a definicao de task sem valor dentro
# Camada padrao: 10.000 parametros por conta e regiao, valor de ate 4 KB, sem
# custo adicional. A camada avancada sobe para 100.000 e 8 KB, habilita politica
# de parametro e compartilhamento entre contas — e cobra. Passar de padrao para
# avancada e possivel; voltar nao e.
resource "aws_ssm_parameter" "host" {
name = "/${var.projeto}/banco/host"
type = "String" # nao e sensivel: o host nao autoriza nada
value = aws_db_instance.principal.address
tags = var.tags
}
resource "aws_ssm_parameter" "pool_maximo" {
name = "/${var.projeto}/banco/pool-maximo"
type = "String"
value = "20"
tags = var.tags
}
# SecureString existe e cifra com KMS igual ao Secrets Manager. Ele e a escolha
# certa para segredo que NAO gira: chave de API de terceiro sem rotacao
# automatica, por exemplo. Nao ha rotacao nem geracao de senha aleatoria aqui.
resource "aws_ssm_parameter" "chave_parceiro" {
name = "/${var.projeto}/integracao/chave-parceiro"
type = "SecureString"
key_id = aws_kms_key.segredos.arn
value = var.chave_parceiro # variavel sensivel, vinda do cofre do pipeline
tags = var.tags
lifecycle {
ignore_changes = [value]
}
}
# ── A definicao de task: repare no que NAO esta aqui ────────────────────────
resource "aws_ecs_task_definition" "api" {
family = "${var.projeto}-api"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = 512
memory = 1024
# Duas roles, dois papeis. A execution role e do AGENTE: puxar imagem,
# escrever log e resolver o campo `secrets`. A task role e da SUA APLICACAO:
# e com ela que o processo chama o Secrets Manager em tempo de execucao.
# Trocar as duas e o erro mais comum da banda 1.
execution_role_arn = aws_iam_role.execution.arn
task_role_arn = aws_iam_role.task.arn
container_definitions = jsonencode([{
name = "api"
image = "${aws_ecr_repository.api.repository_url}:${var.sha}"
environment = [
# Parametro, nao segredo: identificador estavel e nao sensivel. Este e o
# unico "endereco" que a aplicacao precisa saber.
{ name = "SEGREDO_BANCO_ARN", value = aws_secretsmanager_secret.app.arn },
{ name = "PARAMETRO_PREFIXO", value = "/${var.projeto}" },
{ name = "AWS_REGION", value = var.regiao },
# NAO existe DB_PASSWORD aqui. E o criterio de aceite do laboratorio, e a
# prova 1 confere exatamente esta ausencia.
]
# `secrets` seria o caminho nativo, e ele resolve o vazamento: o valor nao
# aparece em describe-task-definition. O que ele NAO resolve e rotacao — a
# documentacao diz que, se o segredo muda, e preciso forcar nova implantacao
# ou iniciar task nova. Por isso ele fica reservado ao que nao gira.
secrets = [
{ name = "CHAVE_PARCEIRO", valueFrom = aws_ssm_parameter.chave_parceiro.arn },
]
portMappings = [{ containerPort = 8080, protocol = "tcp" }]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.api.name
"awslogs-region" = var.regiao
"awslogs-stream-prefix" = "api"
}
}
}])
tags = var.tags
}
# Endpoint de interface do Secrets Manager: a leitura em tempo de execucao passa
# a nascer e morrer dentro da rede da AWS. Sem ele, a task em sub-rede privada
# precisa de NAT para falar com o cofre — e paga hora ligada mais byte
# processado por um trafego que nao precisava sair.
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.principal.id
service_name = "com.amazonaws.${var.regiao}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.privada[*].id
security_group_ids = [aws_security_group.endpoints.id]
private_dns_enabled = true
tags = var.tags
}
| Pergunta | Se a resposta for sim | Se a resposta for não |
|---|---|---|
| O valor precisa expirar ou ser trocado por política? | Secrets Manager | Parameter Store — a rotação é o que você está pagando |
| Alguém precisa gerar o valor aleatoriamente? | Secrets Manager, que tem geração de senha | Parameter Store — o valor vem de fora e é copiado |
| O valor precisa ser compartilhado com outra conta? | Secrets Manager, ou Parameter Store avançado | camada padrão do Parameter Store basta |
| O valor passa de 4 KB? | Parameter Store avançado, ou Secrets Manager | camada padrão do Parameter Store, que vai até 4 KB |
| Vazar o valor causa dano por si? | cifre: segredo, ou SecureString | texto simples serve, e deixa a lista de segredos legível |
A camada padrão não é a versão fraca
A camada padrão do Parameter Store aceita 10.000 parâmetros por conta e região, valor de até 4 KB, e não tem custo adicional. A avançada sobe para 100.000 e 8 KB, habilita política de parâmetro e compartilhamento entre contas, e cobra. Um detalhe operacional que surpreende: dá para subir de padrão para avançada a qualquer momento, e não dá para voltar — mudar de volta truncaria o valor de 8 KB para 4 KB e removeria as políticas anexadas. Se você não precisa de política nem de valor grande, escolher avançada é pagar por um caminho de mão única.
Construir: a aplicação que relê sem reiniciar
Tudo até aqui é infraestrutura, e infraestrutura sozinha não fecha o requisito. O que transforma este laboratório de "cofre configurado" em "credencial rotacionada em uso" são quatro linhas de aplicação: um cache com intervalo declarado, um provedor de senha chamado por temporizador, poda de conexão ociosa e nova tentativa para o código de erro de senha inválida.
// Program.cs — a aplicacao que rele o segredo sem reiniciar o processo
//
// O ponto do arquivo inteiro: NADA aqui conhece a senha em tempo de compilacao,
// e o processo e capaz de descobrir que ela mudou sem ser reiniciado. Sem a
// segunda metade, a primeira nao vale nada — o segredo saiu do repositorio e a
// rotacao virou indisponibilidade agendada.
using System.Text.Json;
using Amazon.SecretsManager;
using Amazon.SecretsManager.Extensions.Caching;
using Npgsql;
using Polly;
var builder = WebApplication.CreateBuilder(args);
// ── 1. O que e parametro entra por variavel de ambiente comum ────────────────
// O ARN identifica; nao autoriza. Quem o obtiver sem a permissao de IAM nao le
// nada, e quem tiver a permissao nao precisa dele escondido. E por isso que ele
// pode estar no Terraform, no Git e no log.
var arnSegredo = builder.Configuration["SEGREDO_BANCO_ARN"]
?? throw new InvalidOperationException("SEGREDO_BANCO_ARN ausente na definicao de task");
// ── 2. Cache do lado do cliente ─────────────────────────────────────────────
// O componente da AWS renova de hora em hora por padrao e a politica de descarte
// e "menos usado recentemente". Cinco minutos aqui NAO e gosto: e o teto da
// janela em que a aplicacao ainda pediria a senha anterior depois do
// finish_secret. Baixar custa chamada de API; subir custa janela de falha.
var cache = new SecretsManagerCache(
new AmazonSecretsManagerClient(),
new SecretCacheConfiguration { CacheItemTTL = 300_000 }); // milissegundos
// A estrutura JSON e a que os modelos de rotacao da AWS esperam, e as chaves sao
// sensiveis a caixa. Ler campo por campo, em vez de montar cadeia de conexao no
// cofre, mantem host e porta como PARAMETRO e so a senha como segredo.
static async Task<(string usuario, string senha)> LerCredencialAsync(
SecretsManagerCache cache, string arn)
{
var json = await cache.GetSecretString(arn);
using var doc = JsonDocument.Parse(json);
var raiz = doc.RootElement;
return (raiz.GetProperty("username").GetString()!,
raiz.GetProperty("password").GetString()!);
}
var (usuarioInicial, _) = await LerCredencialAsync(cache, arnSegredo);
// ── 3. Uma fonte de dados por processo, com provedor periodico de senha ──────
// O pool vive na fonte de dados; uma conexao por requisicao sem pool e o defeito
// que L07 mede. O provedor periodico e o mecanismo que fecha o laco deste
// modulo: o retorno de chamada e invocado por TEMPORIZADOR, nao na abertura da
// conexao, entao ele nao entra no caminho da latencia.
var fonte = new NpgsqlDataSourceBuilder(new NpgsqlConnectionStringBuilder
{
Host = builder.Configuration["BANCO_HOST"],
Port = 5432,
Database = builder.Configuration["BANCO_NOME"],
Username = usuarioInicial,
SslMode = SslMode.VerifyFull,
MaxPoolSize = int.Parse(builder.Configuration["BANCO_POOL_MAXIMO"] ?? "20"),
// Conexao ociosa e podada, e conexao podada e conexao que sera reaberta com
// a senha vigente. Sem poda, uma conexao criada antes da rotacao pode viver
// por dias e esconder que o provedor esta devolvendo valor errado.
ConnectionIdleLifetime = 300,
}.ConnectionString);
fonte.UsePeriodicPasswordProvider(
async (_, ct) => (await LerCredencialAsync(cache, arnSegredo)).senha,
successRefreshInterval: TimeSpan.FromMinutes(5),
failureRefreshInterval: TimeSpan.FromSeconds(10));
// O segundo intervalo e muito menor que o primeiro de proposito: falha ao ler o
// segredo tem de ser reexaminada em segundos, nao em minutos.
var origem = fonte.Build();
builder.Services.AddSingleton(origem);
// ── 4. A nova tentativa que cobre a janela de recusa ────────────────────────
// Na estrategia de usuario unico existe um intervalo curto entre a troca no
// banco e a atualizacao percebida pelo cliente. A propria AWS descreve o risco
// como baixo e a mitigacao como estrategia apropriada de nova tentativa. O
// jitter e obrigatorio: sem ele, todas as tasks tentam no mesmo instante e a
// recuperacao vira um segundo pico.
builder.Services.AddResiliencePipeline("banco", p => p
.AddRetry(new()
{
ShouldHandle = new PredicateBuilder()
.Handle<PostgresException>(e => e.SqlState == "28P01"), // senha invalida
MaxRetryAttempts = 4,
Delay = TimeSpan.FromMilliseconds(400),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
}));
var app = builder.Build();
// ── 5. Vivacidade e prontidao respondem a perguntas diferentes ──────────────
// Vivacidade nao toca o banco: se tocasse, uma queda do banco marcaria todas as
// tasks como mortas e o ECS as substituiria em laco. Prontidao toca — e agora
// tambem responde se o segredo esta legivel, porque credencial ilegivel e um
// jeito novo de a task estar de pe e nao poder atender.
app.MapGet("/health/live", () => Results.Ok(new { estado = "vivo" }));
app.MapGet("/health/ready", async (NpgsqlDataSource origem) =>
{
await using var conexao = await origem.OpenConnectionAsync();
await using var cmd = conexao.CreateCommand();
cmd.CommandText = "select 1";
await cmd.ExecuteScalarAsync();
return Results.Ok(new { estado = "pronto" });
});
// Rota de diagnostico do laboratorio. Ela devolve o USUARIO e o instante da
// ultima releitura — nunca a senha. Uma rota que devolvesse a senha seria o
// mesmo vazamento que o modulo existe para fechar, com o carimbo de "debug".
app.MapGet("/diagnostico/credencial", async () =>
{
var (usuario, senha) = await LerCredencialAsync(cache, arnSegredo);
return Results.Ok(new
{
usuario,
impressao = Convert.ToHexString(
System.Security.Cryptography.SHA256.HashData(
System.Text.Encoding.UTF8.GetBytes(senha)))[..12],
lido_em = DateTimeOffset.UtcNow,
});
});
app.Run();
Por que o provedor é chamado por temporizador, e não ao abrir conexão
Se o retorno de chamada rodasse na abertura de cada conexão, ele entraria no caminho da latência: uma leitura de cofre, mesmo em cache, antes de cada conexão física. O provedor periódico do Npgsql resolve isso invocando o retorno num temporizador, fora do caminho da requisição, e mantendo o último valor obtido para uso. O efeito colateral é exatamente o termo da fórmula: entre o `finishSecret` e o próximo disparo do temporizador, o valor em uso é o anterior.
O que aqui é biblioteca de terceiro, e o que você deve verificar
O componente de cache do Secrets Manager é publicado pela AWS e o padrão dele é renovar de hora em hora, com descarte do menos usado recentemente; a própria documentação avisa que ele não implementa invalidação de cache e não é endurecido para segurança — se você precisar cifrar itens em memória, o caminho é implementar as interfaces oferecidas. O provedor periódico de senha é do Npgsql, não da AWS: confirme a assinatura na versão que você usa, porque ela mudou entre versões maiores, e há discussão aberta no projeto sobre o comportamento em falha. Nada disso invalida o desenho; muda o que você tem de testar.
A armadilha da estratégia de usuários alternados, em uma frase
Na estratégia de usuários alternados o NOME DO USUÁRIO muda a cada rotação, alternando entre a conta original e o clone. Um provedor que só devolve senha não dá conta disso: a cadeia de conexão continua apontando para o usuário anterior, e a autenticação falha com a senha certa. Para usar essa estratégia, a fonte de dados precisa ser reconstruída quando o usuário muda — o que é perfeitamente factível e é trabalho a mais. É a razão pela qual este laboratório escolhe usuário único e paga a janela curta com nova tentativa.
Implantar, e provar que a credencial girou de verdade
Cinco provas, e cada uma tem um número. Prova sem número é opinião com terminal.
# Prova 1 — nenhum segredo no repositorio, e nenhum na definicao de task.
# Duas superficies diferentes: uma e o Git, a outra e o plano de controle.
# (a) O repositorio, incluindo o historico. `git grep` no HEAD nao basta:
# commit antigo continua servindo a senha a quem clona.
git log --all -p -S 'Password=' -- '*.json' '*.cs' '*.tf' | head -40
# Esperado: nada. Se aparecer, o segredo esta no historico e trocar o arquivo
# NAO resolve — e preciso girar a credencial, porque ela vazou.
# (b) O plano de controle. Este comando e o antipadrao central do modulo em
# forma de uma linha: ele devolve tudo o que esta em `environment`.
aws ecs describe-task-definition \
--task-definition ffv-lab-api \
--query 'taskDefinition.containerDefinitions[].environment' --output json
# Esperado: SEGREDO_BANCO_ARN, PARAMETRO_PREFIXO e AWS_REGION. Nenhuma senha.
# Antes deste laboratorio, na Cadencia, a mesma consulta devolvia 1 par
# DB_PASSWORD com a senha em texto claro. A medida e binaria: 1 -> 0.
# (c) O que sobrou em `secrets` sao referencias, nao valores.
aws ecs describe-task-definition \
--task-definition ffv-lab-api \
--query 'taskDefinition.containerDefinitions[].secrets' --output json
# Esperado: apenas ARN. ARN identifica; nao autoriza.
# Prova 2 — a rotacao aconteceu, e os rotulos provam a ordem.
# Este e o comando que torna o mecanismo OBSERVAVEL em vez de decorado.
# Antes: uma versao com AWSCURRENT (e possivelmente uma com AWSPREVIOUS).
aws secretsmanager describe-secret --secret-id ffv-lab/banco/app-rw \
--query 'VersionIdsToStages'
# Dispara a rotacao agora, sem esperar a agenda.
aws secretsmanager rotate-secret --secret-id ffv-lab/banco/app-rw
# Durante: se voce for rapido, ve TRES rotulos ao mesmo tempo.
aws secretsmanager describe-secret --secret-id ffv-lab/banco/app-rw \
--query 'VersionIdsToStages'
# Exemplo de saida no meio da rotacao:
# { "a1b2...": ["AWSCURRENT"], "c3d4...": ["AWSPENDING"] }
# Depois: AWSPENDING desapareceu e AWSPREVIOUS apareceu na versao antiga.
# { "a1b2...": ["AWSPREVIOUS"], "c3d4...": ["AWSCURRENT"] }
#
# APROVA se, ao final, nenhuma versao carrega AWSPENDING.
# REPROVA — e este e o diagnostico que vale memorizar — se AWSPENDING continua
# preso numa versao diferente da AWSCURRENT. Nesse estado, qualquer rotacao
# seguinte supoe que a anterior ainda esta em andamento e devolve erro. A
# rotacao para de acontecer em silencio, e o segredo envelhece com a agenda
# ativa no console.
# Prova 3 — a aplicacao atravessa a rotacao sem implantacao e sem erro.
# Sem esta prova, o laboratorio entregou um cofre e uma promessa.
URL="https://$(terraform output -raw dominio)"
ruins=0; total=0
( while true; do
c=$(curl -s -o /dev/null -w "%{http_code}" --max-time 3 "$URL/health/ready")
total=$((total+1)); [ "$c" = "200" ] || ruins=$((ruins+1))
echo "$(date +%T) $c"; sleep 0.5
done ) &
LACO=$!
# A impressao da senha ANTES. Doze caracteres de um resumo SHA-256: identifica a
# troca sem revelar valor. Nunca imprima a senha, nem em laboratorio.
curl -s "$URL/diagnostico/credencial"
# {"usuario":"app_rw","impressao":"9F2C41A0B7D3","lido_em":"..."}
aws secretsmanager rotate-secret --secret-id ffv-lab/banco/app-rw
sleep 420 # 5 min de cache + folga; nao ha implantacao nenhuma no meio
curl -s "$URL/diagnostico/credencial"
# {"usuario":"app_rw","impressao":"41E0C8B92AF7","lido_em":"..."}
# A impressao MUDOU e o usuario nao — e a assinatura de usuario unico.
kill $LACO
echo "$ruins de $total"
# Medido na aplicacao de exemplo: 1 de 842. A unica falha foi um 503 na janela
# entre o set_secret e a releitura, absorvido pela nova tentativa do cliente
# seguinte. Com o cache em uma hora, o mesmo teste devolveu 137 de 851 — a
# diferenca inteira e o valor do intervalo de cache, e nada mais.
# Prova 4 — o envelope existe, e negar UMA das duas permissoes basta.
# Esta prova transforma "criptografia de envelope" de definicao em experimento.
# (a) A cadeia aparece no CloudTrail. Repare no contexto de cifragem: ele amarra
# a operacao do KMS ao segredo e a versao especificos.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=Decrypt \
--max-results 5 \
--query 'Events[].CloudTrailEvent' --output text | python3 -m json.tool | \
grep -A3 encryptionContext
# Esperado:
# "SecretARN": "arn:aws:secretsmanager:...:secret:ffv-lab/banco/app-rw-AbCdEf"
# "SecretVersionId": "c3d4..."
# A chamada e invocada por secretsmanager.amazonaws.com em seu nome. Voce nunca
# ve a chave de dados: o cofre a usa fora do KMS e a remove da memoria.
# (b) Agora quebre so a metade criptografica. Mantenha
# secretsmanager:GetSecretValue intacto e remova kms:Decrypt da task role.
aws iam put-role-policy --role-name ffv-lab-task \
--policy-name le-segredo --policy-document file://sem-kms-decrypt.json
# Force uma task nova e leia a rota de diagnostico.
curl -s -o /dev/null -w '%{http_code}\n' "$URL/diagnostico/credencial"
# Esperado: 500, com AccessDeniedException do KMS no log da aplicacao.
#
# APROVA porque prova a afirmacao: a permissao de ler o segredo NAO e suficiente.
# O cofre precisa decifrar a chave de dados antes de devolver o valor, e essa
# decifragem e autorizada separadamente. Quem entende isso responde corretamente
# a questao classica de certificacao em que "a policy do Secrets Manager esta
# correta e a leitura falha".
# Prova 5 — o que e parametro nao paga preco de segredo, e da para medir.
# Quantos segredos existem, e quantos deles giram?
aws secretsmanager list-secrets \
--query 'SecretList[].{nome:Name,gira:RotationEnabled,ultima:LastRotatedDate}' \
--output table
# APROVA se todo segredo listado tem gira=True. Segredo sem rotacao e uma de
# duas coisas: parametro no lugar errado, ou divida declarada com data.
#
# Na Cadencia, a primeira execucao listou 9 segredos: 2 giravam. Dos 7 restantes,
# 5 eram nivel de log, nome de fila e URL de webhook — parametro puro, movido
# para o Parameter Store na camada padrao, que nao tem custo adicional.
# A contagem de leituras, que e a segunda dimensao de custo. Sem cache, cada
# task nova e cada requisicao que abrisse conexao geraria uma chamada.
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
--start-time "$(date -u -v-1H +%Y-%m-%dT%H:%M:%SZ)" \
--query 'length(Events)'
# Com cache de 5 min e 2 tasks: cerca de 24 por hora. Sem cache, na mesma hora
# de trafego, foram 1.106. E a mesma funcionalidade, com duas ordens de grandeza
# de diferenca na dimensao que a fatura cobra.
A prova que fecha o entregável do catálogo
O entregável é "aplicação lendo credencial rotacionada; nenhum segredo no repo", e são as provas 1 e 3 juntas que o fecham. A 1 sozinha prova apenas que você escondeu o valor — foi o que o desenho mínimo já fazia. A 3 é a que prova que a senha mudou e a aplicação continuou atendendo sem implantação, e ela só é possível porque a impressão criptográfica permite comparar antes e depois sem revelar nenhum dos dois valores.
O que NÃO foi verificado nestes números
As contagens de 1 em 842, 137 em 851 e 24 leituras por hora são medições da aplicação de exemplo, com duas tasks e p99 de 400 ms, e servem como ordem de grandeza. O que é derivável e vale para você é a relação: a janela de recusa é proporcional ao intervalo do cache, e o número de leituras é inversamente proporcional a ele. Meça os seus dois números antes de escolher o intervalo, porque a razão entre eles depende do seu perfil de abertura de conexão, que não é o desta aplicação.
Quebrar de propósito: três falhas e o diagnóstico
As três falhas abaixo são as que aparecem de verdade, e as três têm em comum a pior característica possível: nenhuma delas produz erro no momento em que a causa acontece.
| Falha provocada | Sintoma que aparece | Onde olhar | Correção |
|---|---|---|---|
| Remova o grupo de segurança da função de rotação | nada, por 30 dias; depois, erro de autenticação intermitente sem mudança de código | `describe-secret` mostra `LastRotatedDate` parado e AWSPENDING preso; o log da função termina em tempo esgotado ao abrir conexão | devolva sub-rede e grupo de segurança com saída para a porta 5432, e limpe o rótulo AWSPENDING antes de tentar de novo |
| Fixe o intervalo do cache em uma hora e gire | erro 500 esporádico por até uma hora depois da rotação, só em conexão física nova | código de estado 28P01 no log da aplicação, com horário posterior ao evento de rotação na trilha | baixe o intervalo para minutos e confirme que a nova tentativa trata 28P01; o valor certo é o teto de janela que você aceita |
| Troque a CMK e não atualize a policy da task role | a task sobe, responde na rota de vivacidade e falha na de prontidão | `AccessDeniedException` do KMS no log, numa chamada que o seu código não faz explicitamente | inclua a chave nova em `kms:Decrypt` com a condição de serviço chamador; a permissão no cofre não substitui a da chave |
O padrão comum às três, e é ele que vale levar
Todas as três falham em componentes que não estão no caminho da requisição — a função de rotação, o temporizador do cache, a política da chave. Componente fora do caminho da requisição não é coberto por health check, por teste de fumaça nem por alarme de 5xx, porque nada nele é exercitado por tráfego. É exatamente por isso que a observabilidade deste módulo tem de vigiar a rotação em si, e não a saúde da aplicação.
Uma task no Fargate lê um segredo pelo SDK. A task role tem `secretsmanager:GetSecretValue` sobre o ARN correto, e a leitura falha com `AccessDeniedException`. O segredo usa uma chave gerenciada pelo cliente. Qual é a causa?
Segurança: o que muda quando o segredo passa a girar
| Risco | Probabilidade | Impacto | Prevenção | Detecção | Resposta |
|---|---|---|---|---|---|
| Credencial no histórico do Git, lida por quem clonou | alta em base antiga | alto | varredura no gancho de commit e no CI, e nunca escrever valor em arquivo versionado | varredura do histórico completo, não só do HEAD | girar a credencial de imediato; apagar o arquivo não recupera cópia |
| Senha em `environment` lida por permissão de inventário | alta | alto | proibir o campo para valor sensível, e revisar quem tem descrever definição de task | consulta periódica sobre todas as definições em uso | nova revisão sem o campo, e girar a credencial que esteve exposta |
| Senha em texto claro no estado do Terraform | média | alto | não usar data source de versão de segredo; deixar o valor nascer na rotação | busca por padrão de senha no arquivo de estado e nos artefatos de plano | cifrar o estado, restringir o bucket e girar a credencial |
| Rotação parada com a agenda ligada no console | média | médio | alarme sobre rotação falha, mais regra do AWS Config sobre rotação bem-sucedida | `secretsmanager-scheduled-rotation-success-check` e evento na trilha | destravar o rótulo AWSPENDING e corrigir o alcance de rede da função |
| Leitura do segredo por principal que não deveria | baixa | alto | permissão por ARN específico, e condição de serviço chamador na chave | trilha de `GetSecretValue` e de `Decrypt` com contexto de cifragem | remover a permissão e girar, porque o valor pode ter sido lido |
| Segredo lido no arranque e nunca mais, com rotação ligada | alta | médio | proibir injeção de arranque no caminho de credencial que gira | comparar `LastRotatedDate` com o horário do último erro de autenticação | passar a leitura para o SDK com cache; forçar nova implantação é remendo |
| Segredo aparecendo em log da aplicação | média | alto | nunca registrar o valor; usar impressão criptográfica quando precisar comparar | busca no grupo de logs por padrão de senha e por prefixo do valor | girar, e reduzir a retenção do grupo afetado |
A regra que resume a seção
Segredo que esteve exposto está comprometido, e a única correção é girar. Isso vale para o histórico do Git, para o arquivo de estado, para a resposta de uma chamada de inventário e para um log com retenção de 90 dias. Toda vez que a equipe se pergunta "precisa girar mesmo? ninguém viu", a resposta é que a ausência de evidência de leitura não é evidência de ausência — e este laboratório existe justamente para que girar tenha deixado de ser um evento.
Observabilidade: as perguntas que o painel tem de responder
O painel deste módulo é diferente do painel do L03. Lá as perguntas eram sobre tráfego; aqui, sobre um relógio que corre fora do caminho da requisição. Um painel que só mostra latência e erro não perceberia nada do que pode dar errado.
- Quando cada segredo girou pela última vez, e quantos dias faz.
- Existe algum segredo com a agenda ligada e a última rotação atrasada?
- Existe versão presa em AWSPENDING agora?
- Quantas leituras por hora cada task faz, e o número bate com o intervalo do cache?
- Houve decifragem negada nas últimas 24 horas, e por qual principal?
- Existe segredo sem rotação configurada, e ele é segredo ou parâmetro no lugar errado?
- Algum erro de autenticação de banco ocorreu depois do último evento de rotação?
| Alarme | Fonte | Limiar inicial | Por que este limiar |
|---|---|---|---|
| Rotação não concluída | filtro de métrica sobre a trilha, evento de rotação com falha | 1 ocorrência em 24 h | rotação falha é binária e não tem versão tolerável; uma é uma |
| Versão presa em AWSPENDING | consulta agendada a `describe-secret` | qualquer versão fora da AWSCURRENT por mais de 2 h | a janela de rotação deste laboratório é de 2 h; acima disso a rotação não terminou |
| Decifragem negada | filtro sobre `AccessDenied` em evento de Decrypt | 1 ocorrência | com policy correta esse número é zero; qualquer valor acima indica mudança de permissão |
| Erro de senha inválida na aplicação | filtro sobre o código 28P01 no log | 3 em 5 min | um ou dois são a janela esperada e a nova tentativa os absorve; três indicam que a releitura não aconteceu |
| Leituras por hora acima do esperado | contagem de `GetSecretValue` na trilha | mais de 4× o número de tasks por hora | com cache de 5 min, cada task faz cerca de 12 por hora; muito acima disso significa cache não compartilhado no processo |
| Idade da credencial | regra do AWS Config sobre frequência de rotação | acima do intervalo declarado mais 2 dias | a avaliação dessa regra pode atrasar até dois dias em relação à data perdida |
As duas regras gerenciadas do AWS Config que cobrem isto
A regra `secretsmanager-rotation-enabled-check` verifica se o segredo tem rotação configurada e, quando o parâmetro de frequência máxima é informado, compara o intervalo declarado com o permitido. A regra `secretsmanager-scheduled-rotation-success-check` verifica se as rotações realmente aconteceram na data calculada, e ela devolve não conforme quando a data passou sem rotação. As duas se complementam e não se substituem: a primeira pega o segredo sem agenda, a segunda pega o segredo com agenda que não gira. É a distinção entre intenção declarada e efeito observado, e é a mesma distinção que este laboratório inteiro persegue.
Escala: 10, 10 mil, 1 milhão
A dimensão que escala neste desenho não é tráfego de requisição: é número de leituras do cofre e número de segredos. As duas crescem por motivos diferentes, e a segunda é a que sai de controle sem ninguém perceber.
| Ordem de grandeza | O que muda | O que quebra primeiro | O que fazer |
|---|---|---|---|
| 10 requisições por dia, 1 task | nada; o cache praticamente não é exercitado | nada, e o custo é dominado pelo preço fixo do segredo e da chave | considere a chave gerenciada pela AWS e o Parameter Store, se auditoria por versão não for requisito |
| 9 mil por dia, 2 tasks — este laboratório | cerca de 24 leituras por hora, e a janela de recusa aparece na medição | a janela de rotação, se a nova tentativa não tratar o código de senha inválida | cache de 5 min, poda de conexão ociosa e nova tentativa com recuo e jitter |
| 10 milhões por dia, 40 tasks | cerca de 480 leituras por hora só de temporizador, e o custo por chamada passa a ser visível | a quota de leitura por segundo do cofre, num evento em que a frota reinicia inteira | mantenha o cache por processo, escalone o arranque e considere um provedor local com cache compartilhado por host |
| Centenas de serviços, mesma conta | a lista de segredos deixa de ser legível e a pergunta "quantos existem" perde resposta | a governança, antes do técnico: segredo sem dono e sem rotação se acumula | convenção de nome por serviço e ambiente, regra do Config obrigatória, e parâmetro fora do cofre |
| Falha de uma zona de disponibilidade | o cofre e o KMS são regionais e não moram na zona; o endpoint de interface, sim | a leitura, se o endpoint existir em uma zona só | crie o endpoint de interface nas mesmas zonas das sub-redes privadas — é uma linha e é a única correção necessária |
O erro de escala que este desenho pode cometer
Instanciar o cache por requisição em vez de por processo. Em .NET isso acontece com uma linha errada de injeção de dependência, e o efeito não é erro: é a contagem de leituras saltar de uma dúzia por hora para uma por requisição. O sintoma é a fatura e, em volume, o estrangulamento pela quota. É a razão de o cache e a fonte de dados serem registrados como instância única no exemplo, e de a prova 5 medir leituras por hora em vez de confiar no código.
Custo: o que este laboratório acrescenta à fatura
Nenhum preço absoluto aparece aqui, de propósito: preço varia por região e envelhece mais rápido que o conteúdo. O que vale decorar são as dimensões, porque é delas que a decisão sai. Calcule no AWS Pricing Calculator.
| Serviço | Dimensão que cobra | Como ela cresce aqui |
|---|---|---|
| AWS Secrets Manager | por segredo por mês, e por lote de chamadas de API | a primeira é fixa e some no ruído; a segunda é proporcional a 1 dividido pelo intervalo do cache |
| AWS KMS | por chave por mês, mais lote de requisições; e mensalidade adicional pela primeira e pela segunda rotação de material | a cobrança adicional por rotação para na segunda e não cresce depois; cada leitura de segredo gera uma decifragem |
| Lambda de rotação | invocações e tempo de execução | quatro invocações curtas a cada 30 dias — é a menor linha desta tabela |
| SSM Parameter Store | camada padrão sem custo adicional; camada avançada cobra por parâmetro | é justamente o motivo de mover configuração para cá |
| Endpoint de interface da VPC | por hora ligada por zona de disponibilidade, mais byte processado | cobra parado, e é a maior surpresa deste laboratório para quem vem do desenho mínimo |
| CloudWatch Logs e trilha | ingestão, armazenamento e retenção | a trilha de gestão já registra as chamadas; o custo aparece se você mandar tudo para um grupo de logs sem retenção |
| Cenário | Configuração | O que domina a conta |
|---|---|---|
| Protótipo | 1 segredo, chave gerenciada pela AWS, sem endpoint, sem rotação | praticamente nada além do preço fixo do segredo; é o cenário em que criar CMK é gasto sem requisito |
| Produção pequena — este laboratório | 2 segredos, 1 CMK, endpoint em 2 zonas, cache de 5 min | o endpoint de interface, que cobra por hora por zona mesmo sem tráfego |
| Alta escala | dezenas de segredos, 40 tasks, cache de 5 min | as chamadas de leitura e as decifragens correspondentes, que crescem com a frota e não com o tráfego |
O custo oculto, e ele não é o segredo
A linha que surpreende não é o preço por segredo: é o endpoint de interface, que cobra por hora ligada em cada zona de disponibilidade, esteja passando tráfego ou não. Num ambiente de não produção que fica ligado o mês inteiro para receber algumas dezenas de leituras por hora, ele pode custar mais que todos os segredos somados. A alternativa honesta em não produção é aceitar a saída pelo NAT que já existe, ou desligar o ambiente. O segundo custo oculto é o oposto do que se espera: um segredo agendado para exclusão não é cobrado, mas continua ocupando o NOME — e recriar com o mesmo nome falha até a espera terminar.
Well-Architected nos seis pilares
| Pilar | Situação após este laboratório | Risco que permanece | Melhoria | Prioridade |
|---|---|---|---|---|
| Excelência operacional | a rotação é automática e observável, com alarme sobre falha e regra do Config | a rotação nunca foi ensaiada num dia útil de pico; a janela é de madrugada | forçar uma rotação em horário comercial, com a medição da prova 3 ligada | alta |
| Segurança | nenhum valor em repositório, estado ou inventário; leitura por task role com ARN específico | o histórico do Git ainda contém a credencial antiga, que já foi girada mas ficou registrada | varredura no gancho de commit para impedir a reincidência, que é o risco real | alta |
| Confiabilidade | a aplicação atravessa a rotação sem implantação, com nova tentativa e poda de conexão | a leitura no arranque depende do cofre; indisponibilidade regional dele impede task nova de subir | nova tentativa com recuo na leitura de arranque, e teste de falha injetada na leitura | média |
| Eficiência de desempenho | o provedor é chamado por temporizador e não entra no caminho da requisição | a primeira leitura, no arranque, soma latência ao tempo de partida da task | medir o efeito no período de graça do health check antes de baixá-lo | baixa |
| Otimização de custo | configuração no Parameter Store padrão, e o número de leituras é derivado do cache | o endpoint de interface cobra por hora por zona em ambiente que fica ligado sem uso | desligar o ambiente de não produção, ou aceitar a saída pelo NAT existente nele | média |
| Sustentabilidade | as leituras caíram duas ordens de grandeza com o cache, e a rotação são quatro invocações por mês | segredo abandonado continua sendo um recurso mantido, cifrado e replicado, sem uso | revisão trimestral da lista, com a consulta da prova 5 como entrada | 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 segunda coluna que raramente se escreve.
Senha em arquivo de configuração, ou em variável de ambiente na máquina de quem desenvolve. É onde a Cadência estava, e continua legítimo num ambiente local que não toca dado real.Segredo no Secrets Manager, injetado pelo campo `secrets` do ECS, com a chave gerenciada pela AWS. Sem rotação.Leitura em tempo de execução pela task role, cache curto, rotação agendada por função, CMK própria, alarme sobre rotação falha e configuração no Parameter Store.Provedor local de credenciais como sidecar ou processo do host, com cache compartilhado, para uma frota grande não multiplicar leituras. Usuários alternados quando a janela de recusa deixar de ser aceitável.Conta por ambiente, segredo criado por quem opera e não por quem desenvolve, política de recurso no segredo, réplica entre regiões para continuidade, e regra do Config obrigatória em toda a organização.Autenticação de banco por IAM, com token de quinze minutos, e o histórico de acesso a segredos virando dado: correlacionar principal, versão lida e horário para detectar leitura fora do padrão.A ordem não é negociável, e o motivo é concreto
O nível 4 depende de a aplicação já saber reler, que é o nível 3: um cache compartilhado por host não serve a um processo que lê o valor uma vez no arranque. E o nível 6 depende de o time já ter passado pela rotação, porque autenticação por IAM muda a forma de usar o pool — quem nunca administrou credencial que muda vai descobrir o efeito no pool e a expiração do token ao mesmo tempo, sem saber qual dos dois está falhando.
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 "como fazer a credencial expirar sem derrubar a aplicação" tem resposta determinística: agenda de rotação, leitura em tempo de execução, cache com intervalo declarado e nova tentativa para um código de erro conhecido. Um modelo não melhora nenhuma das quatro, e nenhuma delas envolve juízo.
Há um lugar em que IA acrescentaria valor real, e ele é modesto: reduzir o falso positivo da varredura de segredo. Uma varredura por expressão regular acusa toda cadeia de alta entropia, e num repositório com dados de teste, hashes e identificadores isso produz centenas de achados que ninguém revisa — e varredura que ninguém revisa é varredura desligada. Classificar o achado pelo contexto ao redor é tarefa em que um modelo bate expressão regular.
| Pergunta | Resposta honesta para este módulo |
|---|---|
| Qual problema a IA resolveria? | triar achados de varredura de segredo, para o time revisar dez em vez de trezentos |
| Por que uma regra não bastaria? | uma regra bastaria para começar, e deve vir primeiro: lista de exclusão por caminho e por padrão conhecido resolve a maior parte. IA só se justifica depois que a regra simples estiver no lugar e mostrar seu limite |
| De onde viriam os dados? | os próprios achados históricos, com o rótulo que o revisor humano já deu a cada um |
| Qual o risco? | classificar como falso positivo um segredo verdadeiro, que é o erro assimétrico deste caso; exige que o achado de alta confiança nunca seja suprimido automaticamente |
| Por que não agora? | porque o problema deste laboratório é rotação, e nenhuma varredura substitui girar a credencial que já vazou |
Onde a série trata isto de verdade
Classificação e descoberta de dado sensível em escala são o L49, com Macie e minimização de log. Criptografia com chave própria, envelope e trilha de uso em profundidade são o L46. Se o que você quer é IA sobre segurança, o lugar em que ela agrega nesta série é detecção, não gestão de credencial.
Anti-padrões deste laboratório
| Erro | Por que alguém faz | Sintoma em produção | Forma correta |
|---|---|---|---|
| Segredo no campo `environment` da definição de task | porque é o menor número de linhas que funciona, e porque a varredura do repositório ficou verde — o time acredita ter resolvido | nenhum, até alguém com permissão de inventário rodar `describe-task-definition` e receber a senha em texto claro | campo `secrets` para o que não gira, e leitura pelo SDK com a task role para o que gira |
| Ler o segredo com data source no Terraform | porque é a forma mais direta de levar o valor para a definição de task, e o exemplo de muitos módulos faz assim | a senha passa a existir em texto claro no arquivo de estado e em toda saída de plano guardada pelo pipeline | deixar o valor nascer na rotação, e o Terraform conhecer só o ARN |
| Ligar rotação sem tocar na aplicação | porque a rotação é uma caixa marcada no console, e a aplicação continua funcionando quando ela é marcada | erro de autenticação intermitente dias depois, num pico ou num reinício, sem nenhuma mudança de código para explicá-lo | passar a leitura para tempo de execução ANTES de ligar a agenda; provar com uma rotação forçada |
| Usar a credencial mestre do banco na aplicação | porque ela já existe, já gira sozinha com a gestão do RDS e economiza criar usuário e conceder privilégio | nada visível, e um privilégio total no banco disponível a quem alcançar a aplicação; e criar réplica de leitura deixa de ser possível | usuário de aplicação com privilégio mínimo, em segredo próprio, girado por função |
| Girar a chave do KMS e considerar a credencial rotacionada | porque os dois se chamam rotação, e o console mostra a rotação da chave como ativa | a auditoria pergunta a data da última troca de senha e a resposta continua sendo "nunca", com um painel dizendo que a rotação está ligada | entender que são dois relógios: a chave troca material e não recifra nada; o segredo troca valor e altera o banco |
| Guardar configuração no Secrets Manager | porque é um lugar só, com um SDK só, e parece organizado | preço por segredo por mês para nível de log e nome de fila, e uma lista de segredos em que ninguém acha os segredos de verdade | Parameter Store na camada padrão para o que não gira, e o cofre só para o que tem ciclo de vida |
| `Resource: "*"` em `secretsmanager:GetSecretValue` | porque a policy específica exige saber o ARN, que só existe depois de criar o segredo, e o curinga desbloqueia o trabalho agora | qualquer segredo da conta legível pela aplicação, inclusive os de outros serviços; e o achado aparece na primeira auditoria | ARN específico, e a condição de serviço chamador na policy da chave; o curinga fica só onde a ação não aceita recurso |
| Registrar a senha em log para depurar | porque o erro de autenticação é urgente e ver o valor é o caminho mais rápido para saber se ele é o esperado | a credencial passa a existir num grupo de logs com retenção, legível a quem tem leitura de log, que é muito mais gente | imprimir uma impressão criptográfica curta do valor, que permite comparar sem revelar |
Quando algo não funciona
| Sintoma | Causa provável | Como investigar | Onde olhar | Correção |
|---|---|---|---|---|
| A task não sobe, e o erro cita inicialização de recurso | a execution role não tem permissão sobre o segredo ou sobre a chave, no caminho do campo `secrets` | ver o motivo da parada da task, que traz o serviço e a ação negada | `describe-tasks` no campo de razão de parada, e o evento negado na trilha | conceder a permissão à EXECUTION role, não à task role; quem resolve `secrets` é o agente |
| A rota de prontidão falha com acesso negado do KMS | a task role tem permissão no cofre e não tem `kms:Decrypt` na chave | ler a exceção completa: ela nomeia o serviço KMS, não o Secrets Manager | log da aplicação, e o evento de decifragem negada na trilha | acrescentar `kms:Decrypt` sobre o ARN da chave, com a condição de serviço chamador |
| Erro de senha inválida horas depois da rotação | a aplicação não releu: cache longo, ou valor lido uma vez no arranque | comparar o horário do erro com a data da última rotação, e chamar a rota de diagnóstico | código 28P01 no log, e `LastRotatedDate` no `describe-secret` | baixar o intervalo do cache e confirmar que o provedor é chamado por temporizador |
| A rotação não acontece, e o console mostra a agenda ligada | a função não alcança o banco, ou uma versão ficou presa em AWSPENDING | olhar os rótulos e, em seguida, o log da função — a ordem importa | `VersionIdsToStages` no `describe-secret`, e o grupo de logs da função | corrigir sub-rede e grupo de segurança, remover o rótulo preso e disparar de novo |
| A rotação falha logo na primeira execução | a estrutura JSON do segredo está incompleta ou com chave em caixa errada | validar campo por campo contra a estrutura que o modelo espera | log da função, na etapa de conexão, e o valor atual do segredo | preencher `engine`, `host`, `port`, `dbname` e `username` exatamente como esperado |
| A leitura funciona local e falha na sub-rede privada | não há caminho de rede até o cofre: nem endpoint de interface, nem rota de saída | tentar a chamada de dentro da task e observar se ela expira em vez de negar | tempo esgotado no log, e a existência do endpoint na zona da sub-rede | criar o endpoint de interface nas mesmas zonas das sub-redes, com DNS privado ligado |
| Depois de trocar a chave, versões antigas continuam legíveis | é comportamento esperado, e não defeito | entender que a troca de chave recifra as três versões rotuladas e mantém as antigas legíveis com a chave anterior | a documentação de troca de chave de cifragem do segredo | se você precisa que só a chave nova sirva, crie uma versão nova do segredo depois da troca |
| Recriar o segredo com o mesmo nome falha | o segredo anterior está agendado para exclusão e o nome continua ocupado | listar incluindo os agendados para exclusão | o campo de data de exclusão no `describe-secret` | restaurar e reaproveitar, ou excluir sem recuperação quando for laboratório |
Limpeza: o que o destroy não leva
A ordem importa em dois pontos: a agenda de rotação tem de sair antes do segredo, e a pilha da função de rotação sai por último, porque ela criou papéis que o segredo referencia.
# 1. Desligue a agenda ANTES de mexer no segredo. Rotacao disparando durante a
# destruicao deixa versao presa em AWSPENDING e o destroy falha em seguida.
aws secretsmanager cancel-rotate-secret --secret-id ffv-lab/banco/app-rw
# 2. A infraestrutura declarada.
terraform destroy -auto-approve
# 3. O segredo fica INACESSIVEL e agendado para exclusao, nao excluido. Em
# laboratorio, exclua sem recuperacao — senao o NOME continua ocupado por 7 dias
# e recriar o ambiente falha. Em producao, NUNCA use esta forma.
aws secretsmanager delete-secret --secret-id ffv-lab/banco/app-rw \
--force-delete-without-recovery
# 4. A chave do KMS entra em espera de 7 a 30 dias. Sete e o minimo, e foi o que o
# Terraform declarou; nao existe exclusao imediata de chave.
aws kms describe-key --key-id alias/ffv-lab-segredos \
--query 'KeyMetadata.{estado:KeyState,exclusao:DeletionDate}'
# 5. O endpoint de interface cobra por hora por zona. Confirme que ele sumiu.
aws ec2 describe-vpc-endpoints --query 'VpcEndpoints[].ServiceName'
# 6. O grupo de logs da funcao de rotacao NAO e do Terraform: a pilha do repositorio
# de aplicacoes o criou, e ele sobrevive com a retencao que tiver.
aws logs delete-log-group --log-group-name /aws/lambda/ffv-lab-rotacao-postgres
# 7. Por ultimo, a pilha da funcao.
aws cloudformation delete-stack --stack-name ffv-lab-rotacao-postgres| Recurso | O `destroy` leva? | O que acontece se ficar |
|---|---|---|
| Segredo do Secrets Manager | agenda a exclusão, não exclui | não é cobrado enquanto está agendado, mas o NOME continua ocupado e recriar falha |
| Segredo do mestre, gerido pelo RDS | sai junto com a instância | ele e o metadado dele são excluídos com o banco, o que é conveniente e surpreende quem esperava mantê-lo |
| Chave do KMS | agenda a exclusão, com espera de 7 a 30 dias | continua existindo na conta durante a espera, não gira nesse período, e não pode ser usada |
| Endpoint de interface da VPC | sim, se declarado | cobra por hora por zona de disponibilidade mesmo sem tráfego nenhum — é o que mais dói |
| Grupo de logs da função de rotação | não, porque quem o criou foi a pilha | armazenamento cobrado indefinidamente se a retenção não estiver definida |
| Pilha da função de rotação | não, é recurso do CloudFormation | deixa a função, os papéis e o grupo de segurança dela órfãos na conta |
| Usuário `app_rw` dentro do PostgreSQL | não existe fora do banco | desaparece com a instância; se você restaurar de instantâneo, ele volta com a senha do momento do instantâneo |
| Parâmetros do Parameter Store | sim, se declarados | camada padrão não cobra por parâmetro, então o custo de esquecer é zero e a bagunça não |
A pegadinha da exclusão, e ela custa uma tarde
Excluir o segredo com a espera padrão e tentar recriar o ambiente no mesmo dia falha, porque o nome continua reservado. O erro não diz isso com clareza — ele reclama que o segredo já existe, e listar não o mostra, porque agendados para exclusão ficam ocultos por padrão. Em laboratório, use a exclusão sem recuperação. Em produção, jamais: é irreversível, e a espera existe exatamente para o caso em que alguém excluiu o segredo errado.
Resumo: problema, serviço e motivo
| Problema | Peça | Por que ela, e não outra |
|---|---|---|
| A senha precisa expirar | AWS Secrets Manager com agenda de rotação | é o único dos dois cofres que gira; o Parameter Store guarda e não expira |
| A aplicação não pode ser reimplantada a cada troca | SDK com cache curto e provedor periódico | a injeção do ECS resolve o valor no arranque, e a documentação exige task nova para vê-lo mudar |
| O valor não pode aparecer em inventário | task role em vez de `environment` | quem descreve definição de task recebe o campo como escrito, e essa permissão é comum |
| O valor não pode aparecer no estado do Terraform | senha nascendo na primeira rotação | data source de versão de segredo resolve em texto claro durante o plano |
| A auditoria precisa saber quem leu qual versão | CMK própria | o contexto de cifragem no evento do KMS identifica segredo e versão; a chave gerenciada não tem política sua |
| A leitura não pode sair para a internet | endpoint de interface na sub-rede privada | mantém a sub-rede sem rota de saída, e é o que dispensa NAT para esta chamada |
| Configuração não pode custar preço de segredo | Parameter Store, camada padrão | não cobra por parâmetro e mantém a lista de segredos legível |
| Rotação falha não pode passar em silêncio | filtro sobre a trilha, alarme e regra do Config | a rotação corre fora do caminho da requisição, então nenhum health check a cobre |
| Falha | O que protege | Como você provou |
|---|---|---|
| Credencial vazada continua útil para sempre | agenda de 30 dias | prova 2: os rótulos mudaram de versão |
| Rotação derruba a aplicação | leitura em tempo de execução com cache de 5 min | prova 3: a impressão mudou e o laço mediu 1 falha em 842 |
| Permissão no cofre sem permissão na chave | as duas declarações da policy | prova 4: negar a decifragem bloqueou a leitura |
| Senha em inventário do plano de controle | ausência do campo `environment` | prova 1: a consulta devolveu zero ocorrências |
| Segredo que é parâmetro inflando a lista | Parameter Store na camada padrão | prova 5: de 9 segredos para 4, todos girando |
- A aplicação arranca e lê o segredo pela task role, através do endpoint de interface.
- O cofre decifra a chave de dados no KMS e devolve o valor da versão AWSCURRENT.
- A fonte de dados abre o pool com aquela senha; host e pool vieram do Parameter Store.
- Trinta dias depois, a agenda invoca a função de rotação.
- A função grava a senha nova em AWSPENDING, altera o banco, testa e move AWSCURRENT.
- A versão anterior recebe AWSPREVIOUS e continua existindo.
- Em até cinco minutos, o temporizador do provedor relê e passa a devolver a senha nova.
- Conexões já abertas seguem servindo; conexões físicas novas autenticam com a nova.
- Se alguma cair na janela, a nova tentativa com recuo e jitter a absorve.
- Nada foi implantado, e a trilha registra quem leu qual versão de qual segredo.
Desafio — sem roteiro
O requisito
Uma segunda função Lambda, de um time diferente, também precisa ler a MESMA credencial de banco — sem que ninguém tenha de coordenar redeploy quando ela rotacionar.
Critério de aceite — executável, não "verifique se funciona"
Force uma rotação manual (`aws secretsmanager rotate-secret`) e, sem tocar em nenhum código, confirme que a segunda Lambda consegue se conectar ao banco na PRÓXIMA invocação, usando a senha nova.
- Dica 1: A segunda Lambda busca o segredo pelo ARN em CADA invocação (ou com cache curto) — nunca embuta a senha em variável de ambiente, que ficaria congelada na versão antiga.
- Dica 2: As duas Lambdas precisam da mesma permissão `secretsmanager:GetSecretValue` no seu próprio papel de execução — permissão não é compartilhada automaticamente entre funções.
- Dica 3: Se a segunda Lambda falhar na primeira tentativa após a rotação, o culpado costuma ser cache de conexão mantido vivo entre invocações (reuso de container) — force um cold start para confirmar.
Lembrete de limpeza
A segunda Lambda e o papel dela entram no mesmo `terraform destroy` — nenhum recurso novo fica de fora do estado gerenciado.
Perguntas frequentes
❓ Quando usar Secrets Manager e quando usar Parameter Store?
❓ Por que a aplicação usa a senha antiga depois da rotação?
❓ O que são AWSCURRENT, AWSPENDING e AWSPREVIOUS?
❓ Preciso de CMK própria ou a chave aws/secretsmanager basta?
❓ Rotação de usuário único ou usuários alternados: qual escolher?
❓ Girar a chave do KMS gira a senha guardada no segredo?
❓ Por que a task falha com AccessDenied do KMS se a policy está certa?
❓ Posso usar o campo secrets do ECS com um segredo que gira?
Fixando
Você ligou rotação de 30 dias num serviço ECS que recebe a senha pelo campo `secrets` da definição de task. Ninguém implantou nada. O que acontece?
Um segredo tem a agenda de rotação ligada há três meses e `LastRotatedDate` de três meses atrás. O `describe-secret` mostra uma versão em `AWSCURRENT` e outra, diferente, em `AWSPENDING`. Qual é o diagnóstico?
Conhecimentos, próximo módulo e documentação
| Item | Conteúdo |
|---|---|
| Conhecimentos anteriores necessários | L01 no ar (ECS Fargate, RDS PostgreSQL em sub-rede privada, ALB), Terraform e .NET 8 básicos; L02 ajuda a entender por que a função de rotação precisa de sub-rede |
| Conhecimentos adquiridos | a distinção entre segredo e parâmetro pelo ciclo de vida; as quatro etapas da rotação e o que cada rótulo marca; por que a injeção de arranque e a rotação são incompatíveis; criptografia de envelope como duas permissões; a diferença entre girar a chave e girar o segredo; quando o Parameter Store basta |
| Limitação que fica | a aplicação passou a depender do cofre para subir; a janela curta de recusa da estratégia de usuário único é absorvida por nova tentativa e não eliminada; e o histórico do repositório continua contendo a credencial antiga, já girada |
| Próximo exemplo recomendado | L46 — criptografia com KMS, envelope, chave gerenciada pelo cliente e rotação. Ele parte da CMK criada aqui e leva a política de chave, a trilha de uso por operação e o dado em repouso |
| Também habilitado por este módulo | L42 (identidade de workload e credencial temporária) reaproveita a separação entre execution role e task role feita aqui; L41 (da policy curinga à policy que passa auditoria) usa as duas declarações desta policy como caso; L49 (dado pessoal em log) parte do hábito de não registrar valor |
| Data da última validação técnica | 7 de agosto de 2026 |
Documentação oficial consultada: Rotate AWS Secrets Manager secrets e Managed rotation for AWS Secrets Manager secrets — as duas formas de rotação e quais serviços dispensam função Lambda; Lambda rotation functions — as quatro etapas e o que cada uma faz com os rótulos; Lambda function rotation strategies — usuário único e usuários alternados, com o efeito de cada um na disponibilidade; What's in a Secrets Manager secret? — versões, rótulos e o limite de cem versões obsoletas; Secret encryption and decryption in AWS Secrets Manager — a criptografia de envelope, a chave de dados de 256 bits e o contexto de cifragem; Pass sensitive data to an Amazon ECS container — a instrução explícita de forçar nova implantação quando o segredo muda; Choosing parameter tiers in Parameter Store — os limites das duas camadas e a impossibilidade de voltar da avançada; Password management with Amazon RDS and AWS Secrets Manager — a rotação gerenciada da credencial mestre em sete dias e a impossibilidade de trocar a chave depois; Rotate AWS KMS keys — o período de 365 dias, a cobrança até a segunda rotação e a afirmação de que girar a chave não recifra dado; Delete an AWS KMS key e Delete an AWS Secrets Manager secret — as duas esperas de exclusão e o fato de segredo agendado para exclusão não ser cobrado. Nenhum preço absoluto aparece 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 no seu ambiente
Três coisas. Primeiro, as medições de 1 falha em 842 requisições, 137 em 851 e 24 leituras por hora são da aplicação de exemplo, com duas tasks e p99 de 400 ms: o que vale para você é a relação entre intervalo de cache, janela de recusa e número de leituras, não os números. Segundo, o provedor periódico de senha é do Npgsql e não da AWS — a assinatura mudou entre versões maiores e há discussão aberta no projeto sobre o comportamento em falha, então valide na versão que você usa. Terceiro, a documentação descreve as etapas de rotação com nomes em camelo e lista os valores do parâmetro `Step` com sublinhado, e não consegui resolver essa divergência: se você escrever a sua própria função, confira qual forma o seu modelo compara, porque uma comparação que nunca casa faz a rotação expirar sem erro explícito.
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…