Isso aconteceu no meu laboratório de estudo entre 14 e 21 de outubro de 2026. Os prints são reais; IDs de conta e de recursos ficaram de fora.
01 / PONTO DE PARTIDA
Um carro bom, sem cinto nem airbag.
O repositório já tinha coisa boa: módulos com versão fixa, acesso à AWS por OIDC, state no S3 com lock. Mas ele não tinha nenhuma esteira. O plan e o apply rodavam do meu PC, e ninguém conferia nada antes do merge.
Apply do PC
A versão do Terraform, as credenciais e o que foi aplicado dependiam de quem rodou e de quando.
Nada conferia o PR
Formatação, variável esquecida, bucket público: só se eu lembrasse de olhar.
Credencial de admin local
Para aplicar, o meu usuário precisava de permissão para tudo.
IA sem regra escrita
Pedir Terraform para uma IA sem regras claras é aceitar o padrão dela, não o do time.
02 / REGRAS ANTES DO CÓDIGO
A IA lê as mesmas regras que eu.
O primeiro arquivo da esteira não foi YAML, foi um AGENTS.md na raiz do repo. Ele diz o que vale ali dentro: acesso só por OIDC, S3 sempre privado, nada de 0.0.0.0/0 em entrada, IAM com permissão mínima, tudo que vem de fora fixado por hash, apply só pela esteira.
Serve para quem entra no time e serve para qualquer assistente de IA que for escrever Terraform no repo. A diferença é que, sozinho, o arquivo é só um pedido. Quem garante é o próximo passo.
03 / A ESTEIRA CONFERE
O primeiro PR achou o que eu não via.
Todo PR passa por terraform fmt, tflint nas quatro stacks e Checkov. Liguei tudo primeiro em modo aviso, para ver o tamanho do problema antes de bloquear qualquer coisa:
4 arquivos fora do padrão formatação, corrigida em um commit.
9 variáveis declaradas e nunca usadas sobra de módulos comentados que nem existiam mais.
Módulos presos por tag tag pode ser movida para outro código. Hash não.
Uma config no lugar errado o Checkov reclamou de um secret sem KMS. O problema real era outro, e aparece na seção seguinte.
Tudo que vem de fora, preso pelo hash
Troquei 23 referências de módulo e todas as actions da esteira por hash de commit, com a versão em comentário para continuar legível. O Dependabot abre PR quando sai versão nova.
module "vpc" {
source = "git::https://github.com/minha-org/module-vpc.git?ref=8aca4962827ae44de7f20d0694051eed24b0f79b" # v1.2.2
}04 / PLAN SEM CHAVE
O plan aparece no PR, e a esteira não guarda senha nenhuma.
Para rodar o plan, a esteira precisa de duas coisas: ler os módulos, que estão em repositórios privados, e ler a AWS. Nenhuma das duas usa chave guardada.
- 01
Módulos privados
um GitHub App só com leitura de código, instalado só nos repos de módulo. A esteira pede um token que vale uma hora.
- 02
AWS
uma role com ReadOnlyAccess, assumida por OIDC, que só aceita token de pull request deste repositório.
- 03
Resultado
o plan de cada stack sai comentado no próprio PR. Quem revisa vê o que muda sem abrir terminal.
A role só leitura foi barrada, e estava certa
No primeiro plan real, uma stack falhou: a role de leitura não tinha permissão para abrir um secret do Secrets Manager. Fui ver o que tinha dentro. Nome de bucket e ID de CloudFront, nada secreto.
Em vez de dar à role de leitura acesso ao cofre, tirei a config de lá: foi para o SSM Parameter Store, sem custo e sem KMS. Precisei mexer em três repos, o módulo de IAM, o workflow do front e a infra, mas a correção ficou do jeito certo. O aviso do Checkov sumiu junto.
05 / DOIS BOTÕES
O plan que você leu é o plan que vai ser aplicado.
A ideia era ter um botão de aprovar no GitHub. Só que revisor obrigatório em environment, em repo privado, exige plano pago. Em vez de abrir mão da revisão, separei o apply em dois botões:
- 01
Botão 1
roda o plan com a role só leitura, mostra o resultado no resumo e guarda o arquivo do plan por um dia.
- 02
Uma pessoa lê
o que vai ser criado, alterado e destruído.
- 03
Botão 2
recebe o número da execução do botão 1 e aplica exatamente aquele arquivo. Se o state mudou no meio do caminho, o Terraform recusa.

A role de escrita só existe dentro do environment dev, que só aceita a branch main. E ela não tem AdministratorAccess: é PowerUserAccessmais IAM apenas nos nomes do ambiente, com uma regra que impede a esteira de alterar as próprias roles.
statement {
sid = "NaoMexerNaPropriaEsteira"
effect = "Deny"
actions = ["iam:Create*", "iam:Delete*", "iam:Update*", "iam:Put*", "iam:Attach*", "iam:PassRole"]
resources = [
aws_iam_role.iac_plan.arn,
"arn:aws:iam::<conta>:role/dev-iac-apply",
aws_iam_openid_connect_provider.github.arn,
]
}
06 / EKS PELA ESTEIRA
68 recursos, nenhum terminal aberto.
Com o fluxo testado numa mudança pequena, fui para a de verdade: a stack da raiz, com VPC, NAT, cluster EKS e node group. O botão 1 mostrou 68 recursos para criar e zero para destruir. Li, conferi que havia um NAT só (laboratório paga por hora) e apertei o botão 2.

O teste que mais me interessava era esse: criar um EKS inteiro com uma role que não pode tudo. Se faltasse permissão, o apply ia parar no meio com AccessDenied. Não parou.

07 / APRENDIZADOS
O que eu levo dessa.
- 01
Primeiro avisar, depois bloquear
ligar a esteira em modo aviso mostrou o tamanho do problema sem travar ninguém.
- 02
Erro de permissão também é achado
a role só leitura barrada no cofre revelou uma config guardada no lugar errado.
- 03
Limitação vira desenho
sem revisor obrigatório no plano gratuito, os dois botões mantiveram a revisão e o plan exato.
- 04
Bootstrap é a única exceção
as roles da própria esteira foram criadas uma vez, pelo CloudShell. Daí em diante, tudo pelo GitHub.
A frase que me guiou nesse laboratório foi de um anúncio de workshop: ferramenta não é diferencial, entender o que chega à produção é. Agora, o que chega à minha AWS passa por um PR, por um plan lido por alguém e por um apply que não depende do PC de ninguém.
william@devops:~$ echo $LESSON_LEARNED
"Se o apply depende do PC de alguém, a infraestrutura depende da memória dessa pessoa."