Skip to content

ADR-006 — Secrets management

Status

Vigente — 2026-05-01. Crítico (MVP) — sem isso, qualquer credencial em código é leak iminente.

Contexto

CPPS tem credenciais que precisam ficar fora de git em texto plano:

  • Tokens API (Cloudflare, Proxmox, GitHub)
  • Senhas de bancos de dados (Postgres apps)
  • Chaves SSH privadas
  • Certificados TLS
  • Secrets de aplicações (OAuth client secrets, API keys de pesquisadores)

Hoje há Pulumi.dev.yaml com proxmox-sp:ssh_public_key (chave pública — OK em git), mas anteriormente havia proxmoxve:api_token criptografado também (removido em PR #6 quando auth virou env vars). Isso revela a falta de padrão.

Opções consideradas:

  • SOPS + age: criptografia simétrica, secrets ficam em git criptografados, descriptografados em runtime (Pulumi, Ansible, ArgoCD via plugin)
  • External Secrets Operator (ESO) + Vault: secrets ficam em Vault (servidor dedicado), K8s consome via Operator
  • Sealed Secrets: criptografia assimétrica, secrets criptografados em git, controller no K8s descriptografa
  • HashiCorp Vault standalone: sem operator K8s, acesso via API
  • Pulumi config encryption: passphrase-based; serve só pra Pulumi

Decisão

MVP: SOPS + age.

Razões:

  1. Sem novo serviço crítico (SOPS é binário standalone, age é biblioteca Go)
  2. Secrets ficam versionados em git (auditoria via histórico git)
  3. Funciona com Pulumi (via SOPS package), Ansible (via ansible-vault ou SOPS plugin), ArgoCD (via argocd-vault-plugin ou Kustomize SOPS)
  4. Curva de aprendizado baixa
  5. Backup é o próprio git

Estrutura proposta:

secrets/
├── pulumi/
│ └── proxmox-sp.encrypted.yaml
├── ansible/
│ └── vault.encrypted.yaml
└── apps/
├── franca/
│ └── postgres.encrypted.yaml
└── sp/
└── postgres.encrypted.yaml

Chaves age:

  • 1 por admin pessoal (revogável)
  • 1 por automação (CI/CD)
  • Recipients listados em .sops.yaml por path (granularidade fina)

Futuro (deferido pra Fase 8 — IAM unificada): ESO + Vault.

Alternativas rejeitadas (pra MVP)

  • ESO + Vault agora: Vault é serviço crítico que precisa ser HA, ter unsealing strategy, integrar com Authentik. Complexidade desproporcional pra fase atual.
  • Sealed Secrets: chave master no controller K8s vira ponto de falha único; secrets só descriptografáveis no cluster que tem a chave (dificulta dev local).
  • Pulumi config encryption: serve só pra Pulumi, não cobre Ansible/ArgoCD/apps.

Consequências

Positivas:

  • Secrets versionados em git com auditoria automática (git blame mostra quem mudou)
  • Sem novo serviço crítico
  • Funciona local e em CI igual
  • Migração futura pra ESO + Vault é incremental (decrypt SOPS → put em Vault, atualiza referências)

Negativas:

  • Rotação de chave age exige re-encrypt de tudo (não automatizado nativamente)
  • Gerenciar recipients (quais chaves descriptografam quais paths) precisa disciplina
  • Sem revogação granular em tempo real (rotação = re-encrypt + force-push)
  • Secrets em git mesmo criptografados podem ser “desencriptados no futuro” se quantum break ocorrer (preocupação teórica)

Critério de revisão

Migrar pra ESO + Vault quando:

  1. Mais de 5 admins com chaves age separadas (rotação vira ingerenciável)
  2. Self-service de secrets virar requisito (pesquisador pede secret novo via Backstage)
  3. Audit trail formal exigir Vault audit log (compliance acadêmico)
  4. Dynamic secrets (DB credentials gerados sob demanda) virarem caso de uso
  5. Time tiver alguém pra operar Vault HA (1+ admin dedicado)

Notas operacionais

  • .sops.yaml na raiz do repo define recipients por path
  • Pre-commit hook impede commit de arquivo .encrypted.yaml em texto plano
  • CI usa chave age dedicada armazenada em GitHub Secret
  • Decriptação local: admin tem chave em ~/.config/sops/age/keys.txt
  • Backup das chaves age: documentado fora de git (1Password / paper / safe)