Pular para o conteúdo principal

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:

ActionDescriçãoOnde disparaDefault required_approvals
fortress_deleteExcluir Fortress secret (irrecuperável)DELETE /fortress/:id2
vault_deleteExcluir cofre inteiroDELETE /vaults/:id2
export_zipBundle AES-256 com certs + JWKS + metadadosPOST /app-connections/:id/export-zip2
fortress_viewBreak-glass — revelar Fortress fora do fluxo padrãoPOST /fortress/:id/view (header X-Approval-Id)2
mfa_disableDesabilitar enforcement MFA do tenantSettings → Segurança2
siem_changeMudar configuração SIEM forwardingSettings → Eventos & Destinos2
rbac_changeAlterar grupos IAM ou permissões coreSettings → IAM2
master_key_rotateRotacionar master key (re-encripta fortress_shards.wrapped_key OLD→NEW)POST /admin/master-key/rotate (v3.0+)3
tenant_region_promotePromover region replica a primary (failover Multi-region)POST /admin/tenant-region/:id/promote (v4.2+)2
kube_guardian_policy_changePromover policy do Guardian de audit para blockPATCH /kube-guardian/clusters/:id/policies/:p com mode=block (v4.5+)2
replication_state_changePromote/demote de sink no ReplicationPOST /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 marca consumed; segunda tentativa retorna 409
  • Expirado — request com expires_at < NOW() é rejeitada automaticamente pelo scheduler 30min

Fluxo (UI)

  1. Usuário tenta ação coberta — backend retorna 202 com approval_request_id
  2. UI exibe modal: "Aguardando aprovação · ID req_01HXY..."
  3. Aprovadores recebem notificação (email + bell + SIEM)
  4. Aprovador acessa /Approvals (filtrável por status), clica Aprovar com motivo
  5. Após N de M aprovações (atualmente fixo 2-de-2), action é re-executada por lib/approvalExecutors.js
  6. Audit log registra: request_created, cada approved, executed ou rejected/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_requests com status lifecycle, expires_at, payload JSONB
  • Middleware factory requireDualApproval(actionType, extractFn) em lib/approvalControl.js
  • Re-executores em lib/approvalExecutors.js (7 actions)
  • Scheduler de expiração 30min com leader-lock multi-replica
  • JWT carrega tenant_id claim (necessário para o guard cross-tenant)
  • UI: /Approvals filtrá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_delete e vault_delete, depois adicione mfa_disable e siem_change.
  • Aprovadores em grupo dedicado — não use o admin tenant principal; crie Approvers no IAM e atribua security leads + SRE leads.
  • Reason exigido > 10 chars — evita "ok" como motivo registrado.
  • Audit cruzado — configure alerta SIEM para approvals executed fora de horário comercial.
  • Treinamento — documente em runbook quando cada break-glass é apropriado.