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:
- Sem novo serviço crítico (SOPS é binário standalone, age é biblioteca Go)
- Secrets ficam versionados em git (auditoria via histórico git)
- Funciona com Pulumi (via SOPS package), Ansible (via
ansible-vaultou SOPS plugin), ArgoCD (viaargocd-vault-pluginou Kustomize SOPS) - Curva de aprendizado baixa
- 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.yamlChaves age:
- 1 por admin pessoal (revogável)
- 1 por automação (CI/CD)
- Recipients listados em
.sops.yamlpor 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:
- Mais de 5 admins com chaves age separadas (rotação vira ingerenciável)
- Self-service de secrets virar requisito (pesquisador pede secret novo via Backstage)
- Audit trail formal exigir Vault audit log (compliance acadêmico)
- Dynamic secrets (DB credentials gerados sob demanda) virarem caso de uso
- Time tiver alguém pra operar Vault HA (1+ admin dedicado)
Notas operacionais
.sops.yamlna raiz do repo define recipients por path- Pre-commit hook impede commit de arquivo
.encrypted.yamlem 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)