← Voltar para o blog

william@devops:~$ cat ./laboratorio/terraform-pela-esteira.md

Terraform pela esteira:
ninguém mais roda apply do PC.

Meu repositório de infraestrutura tinha módulos versionados, OIDC e plan revisado. Só que tudo isso dependia de uma coisa: eu lembrar de fazer certo, no meu PC. Nesta semana eu tirei o apply da minha máquina e coloquei numa esteira que confere, mostra o plan e só aplica depois que alguém lê.

DE ONDE VEIO

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.

01

Apply do PC

A versão do Terraform, as credenciais e o que foi aplicado dependiam de quem rodou e de quando.

02

Nada conferia o PR

Formatação, variável esquecida, bucket público: só se eu lembrasse de olhar.

03

Credencial de admin local

Para aplicar, o meu usuário precisava de permissão para tudo.

04

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.

01

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.

modules.tf (trecho)PADRÃO
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.

  1. 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.

  2. 02

    AWS

    uma role com ReadOnlyAccess, assumida por OIDC, que só aceita token de pull request deste repositório.

  3. 03

    Resultado

    o plan de cada stack sai comentado no próprio PR. Quem revisa vê o que muda sem abrir terminal.

02

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:

  1. 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.

  2. 02

    Uma pessoa lê

    o que vai ser criado, alterado e destruído.

  3. 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.

Formulário Run workflow do GitHub Actions com os campos etapa, stack, ação e número da execução do plan
O botão 2: etapa, stack, ação e o número do plan revisado.

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.

bootstrap/github-oidc.tf (trecho)PROTEÇÃO
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,
  ]
}
Execução do workflow Apply no GitHub Actions com o job de apply concluído e o resumo Apply complete: 1 added, 2 changed, 1 destroyed
O primeiro apply pela esteira: a config do front indo para o Parameter Store, em 55 segundos.

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.

Console da AWS mostrando o cluster ambiente-estudo-dev-eks-cluster com status Creating e Kubernetes 1.35
O cluster nascendo, criado pela esteira com a role sem AdministratorAccess.

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.

Resumo da execução Apply número 6 no GitHub Actions: job de apply concluído em 12 minutos e 30 segundos e Apply complete: 68 added, 0 changed, 0 destroyed
12 minutos e meio depois: 68 recursos criados, nada alterado, nada destruído.

07 / APRENDIZADOS

O que eu levo dessa.

  1. 01

    Primeiro avisar, depois bloquear

    ligar a esteira em modo aviso mostrou o tamanho do problema sem travar ninguém.

  2. 02

    Erro de permissão também é achado

    a role só leitura barrada no cofre revelou uma config guardada no lugar errado.

  3. 03

    Limitação vira desenho

    sem revisor obrigatório no plano gratuito, os dois botões mantiveram a revisão e o plan exato.

  4. 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."
COMPARTILHAR IDEIAS

Quer trocar uma ideia sobre Cloud ou DevOps?

Mandar um e-mail ↗