RBAC do Kubernetes por projeto, com members e owners

TLDR: Substituir a ServiceAccount compartilhada sa-developer por ServiceAccounts por projeto, para que cada dev só acesse os namespaces dos projetos a que pertence — com os owners ganhando exec em produção.

Contexto

Todos os desenvolvedores compartilhavam uma única ServiceAccount sa-developer, com acesso a todos os namespaces de staging e read-only em produção em todos os projetos. Não havia isolamento: um dev do trgclub também lia os pods do onion. A única distinção era o contexto developer-owner-<project>, que usava o mesmo token e só mudava o namespace default.

Problema secundário: o próprio repo infrastructure não tinha proteção de merge — qualquer pessoa podia fazer merge de PR que muda RBAC, secrets e configuração de cluster.

Objetivos

  • Campo members por projeto no config/access.yml (quem acessa os namespaces do cluster).
  • ServiceAccounts por projeto: sa-<project> (member) e sa-<project>-owner.
  • Member: total em staging + read-only em produção, sem exec.
  • Owner: total em staging + total em produção, com exec.
  • make k8s.init cria contexto só para os projetos do usuário.
  • .github/CODEOWNERS gerado a partir da lista infra, para que só membros de infra aprovem PR neste repo.

Fora de escopo

  • Remover a sa-developer. Ela fica intacta nesta mudança e sai num apply de follow-up, depois de todas as máquinas terem rodado make k8s.init.

Mudanças

config/access.yml

yaml projects: onion: apps: [onion-backend, onion-mobile] members: [porpino, filipe] owners: [porpino]

src/shared/kubernetes/terraform/rbac.tf

  • Mapa locals.projects espelhando o access.yml — hardcoded de propósito: muda raramente e evita parsear YAML em tempo de plan.
  • locals.project_staging_pairs e locals.project_production_pairs, achatados para uso em for_each.
  • ClusterRole dev-production-owner, com pods/exec permitido.
  • Por projeto, via for_each sobre local.projects: kubernetes_service_account.project_dev e project_owner, os kubernetes_secret de token de cada uma, ClusterRoleBindings de namespace-reader e metrics-reader para ambas, e RoleBindings de staging (admin para as duas), produção read-only (member) e produção total com exec (owner).

commons — scripts de contexto

  • context/setup.sh: helper _save_single_sa extraído; _save_sa_tokens itera os projetos do usuário lidos do access.yml em vez de lista fixa; _cleanup_owner_contexts virou _cleanup_project_contexts (limpa todo developer-*); contexto infra só se o usuário estiver na lista infra; contexto default é o primeiro developer-<project> não-owner.
  • context/summary.sh e context/change.sh: loop dinâmico e cor por padrão de nome — developer-*-owner amarelo, developer-* verde, infra vermelho.

Proteção do repo

  • .project/shell/tasks/ci/github/sync-codeowners.sh (novo) — lê os aliases de infra, resolve users.<alias>.github e escreve * @<username> em .github/CODEOWNERS.
  • .github/workflows/infra.yml — chama o script depois do sync-environment-reviewers.sh.
  • A regra de branch protection em main exigindo review de CODEOWNER é configurada uma vez, à mão ou via gh api.

Sequência de migração

  1. PR 1 (esta mudança): members no access.yml + SAs novas no terraform + scripts atualizados.
  2. make shared.kubernetes.apply — aditivo, sem interrupção.
  3. Todos os devs rodam make k8s.init e recebem os contextos por projeto.
  4. PR 2 (follow-up): remover a sa-developer e seus bindings.

Como verificar

  1. terraform plan mostra só adições, nenhum destroy.
  2. make k8s.init produz developer-<project> conforme a lista de members e developer-<project>-owner para os projetos que o usuário possui.
  3. kubectl --context=developer-onion get pods -n onion-backend--staging → funciona.
  4. kubectl --context=developer-onion get pods -n trgclub-api--staging → Forbidden.
  5. kubectl --context=developer-onion-owner exec -it <pod> -n onion-backend--production -- sh → funciona.
  6. kubectl --context=developer-onion exec ... no mesmo namespace → Forbidden.
  7. .github/CODEOWNERS contém apenas usuários de infra.
  8. Abrir PR neste repo passa a exigir aprovação de um usuário de infra.

Documentação

  • Guia de acesso — campo members, tabela de contexto no modelo por projeto, e o fato de a lista infra controlar o CODEOWNERS deste repo.

Nota histórica: o modelo de token de ServiceAccount de longa duração descrito aqui foi substituído por IAM roles + EKS Access Entries — ver acesso de desenvolvedor ao EKS via IAM, que também eliminou a duplicação entre locals.projects e o access.yml.