Approvals — dual-control / break-glass / quorum
A partir da v1.6, operações destrutivas, mudanças críticas e fluxos break-glass podem exigir aprovação multi-pessoa antes da execução.
A v1.6 começou com 2-de-2 fixo. A v3.0 trouxe N-de-M
configurável, rate limiting de pending requests e o endpoint
master_key_rotate (que estava no enum mas sem rota).
Actions cobertas
Configuráveis por tenant. Por padrão são habilitadas em produção:
| Action | Descrição | Onde dispara | Default required_approvals |
|---|---|---|---|
fortress_delete | Excluir Fortress secret (irrecuperável) | DELETE /fortress/:id | 2 |
vault_delete | Excluir cofre inteiro | DELETE /vaults/:id | 2 |
export_zip | Bundle AES-256 com certs + JWKS + metadados | POST /app-connections/:id/export-zip | 2 |
fortress_view | Break-glass — revelar Fortress fora do fluxo padrão | POST /fortress/:id/view (header X-Approval-Id) | 2 |
mfa_disable | Desabilitar enforcement MFA do tenant | Settings → Segurança | 2 |
siem_change | Mudar configuração SIEM forwarding | Settings → Eventos & Destinos | 2 |
rbac_change | Alterar grupos IAM ou permissões core | Settings → IAM | 2 |
master_key_rotate | Rotacionar master key (re-encripta fortress_shards.wrapped_key OLD→NEW) | POST /admin/master-key/rotate (v3.0+) | 3 |
tenant_region_promote | Promover region replica a primary (failover Multi-region) | POST /admin/tenant-region/:id/promote (v4.2+) | 2 |
kube_guardian_policy_change | Promover policy do Guardian de audit para block | PATCH /kube-guardian/clusters/:id/policies/:p com mode=block (v4.5+) | 2 |
replication_state_change | Promote/demote de sink no Replication | POST /replication/sinks/:id/promote|demote (v4.5+) | 2 |
N-de-M (v3.0+)
required_approvals é configurável por action no tenant:
curl -X PUT https://cv.acme.com.br/approvals/config \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{
"actions": {
"fortress_delete": { "required_approvals": 2 },
"master_key_rotate": { "required_approvals": 3 },
"siem_change": { "required_approvals": 2 }
}
}'
Tabela approval_signoffs com UNIQUE(approval_id, approver_id)
previne double-sign do mesmo aprovador. Cada aprovador conta uma
vez.
Rate limit (v3.0+)
Para prevenir spam de pending requests:
APPROVAL_PENDING_LIMIT_PER_HOUR— env var, default 10- Quando excedido, retorna 429
APPROVAL_RATE_LIMITED - Contado por solicitante (
actor)
Status lifecycle
pending → approved → executed
↓
rejected
expired
failed
consumed (one-shot break-glass usado)
Anti-patterns bloqueados
- Auto-aprovação — quem solicita não pode aprovar a própria request
- Cross-tenant — request de tenant A só pode ser aprovada por usuário do tenant A
- Replay — após
approved, qualquer execução marcaconsumed; segunda tentativa retorna 409 - Expirado — request com
expires_at < NOW()é rejeitada automaticamente pelo scheduler 30min
Fluxo (UI)
- Usuário tenta ação coberta — backend retorna 202 com
approval_request_id - UI exibe modal: "Aguardando aprovação · ID
req_01HXY..." - Aprovadores recebem notificação (email + bell + SIEM)
- Aprovador acessa
/Approvals(filtrável por status), clica Aprovar com motivo - Após N de M aprovações (atualmente fixo 2-de-2), action é re-executada por
lib/approvalExecutors.js - Audit log registra:
request_created, cadaapproved,executedourejected/expired/failed
API
Listar pendentes
curl https://cv.acme.com.br/approvals?status=pending \
-H "Authorization: Bearer $CV_TOKEN"
Aprovar
curl -X POST https://cv.acme.com.br/approvals/req_01HXY.../approve \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{ "reason": "Validado em call SRE — incidente SEC-1234" }'
Rejeitar
curl -X POST https://cv.acme.com.br/approvals/req_01HXY.../reject \
-H "Authorization: Bearer $CV_TOKEN" \
-d '{ "reason": "Não há justificativa para deletar fortress de produção fora de janela" }'
Break-glass (one-shot)
Para fortress_view, após aprovação o aprovador recebe approval_id que
o solicitante usa via header em UMA requisição:
curl https://cv.acme.com.br/fortress/42/view \
-H "Authorization: Bearer $USER_TOKEN" \
-H "X-Approval-Id: req_01HXY..." \
-d '{ "reason": "Investigação SEC-1234 — ver token Stripe master" }'
Após uso, consumed no DB. Retentar retorna 410.
CLI Go cv
cv approval list # pendentes
cv approval list --all # incluindo histórico
cv approval approve req_01HXY... -r "OK"
cv approval reject req_01HXY... -r "Negado"
Cobertura técnica
- Tabela
approval_requestscom status lifecycle,expires_at, payload JSONB - Middleware factory
requireDualApproval(actionType, extractFn)emlib/approvalControl.js - Re-executores em
lib/approvalExecutors.js(7 actions) - Scheduler de expiração 30min com leader-lock multi-replica
- JWT carrega
tenant_idclaim (necessário para o guard cross-tenant) - UI:
/Approvalsfiltrável + painel de config + badge de pending count
Limitações atuais
Nenhuma significativa após v3.0 — required_approvals configurável e
master_key_rotate implementado.
Considerações operacionais:
- Quórum baseado em IAM group (não user explícito) — em discussão para v4
- Aprovações requerem login interativo — não há workflow SCIM/API automatizado para "quebrar vidro programaticamente"
Boas práticas
- Habilite gradualmente — comece com
fortress_deleteevault_delete, depois adicionemfa_disableesiem_change. - Aprovadores em grupo dedicado — não use o admin tenant principal; crie
Approversno IAM e atribua security leads + SRE leads. - Reason exigido > 10 chars — evita "ok" como motivo registrado.
- Audit cruzado — configure alerta SIEM para approvals
executedfora de horário comercial. - Treinamento — documente em runbook quando cada break-glass é apropriado.