mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 19:21:29 +00:00
68ca42fef1
Closes the wire-layer authorization gap surfaced by the 2026-05-10 audit
(CRIT-1). Before this commit only ~24 of ~140 routes carried rbacGate
enforcement — all of them admin-only fine-grained perms (auth.session.*,
auth.oidc.*, auth.breakglass.admin, cert.bulk_revoke, crl.admin, scep.admin,
est.admin, ca.hierarchy.manage). Every catalogued legacy-CRUD perm
(cert.read/issue/revoke/delete, profile.edit/delete, issuer.edit/delete,
target.*, agent.*, plus role-mgmt verbs) was declared in
internal/domain/auth/validate.go but never wired at the router. A r-viewer
Bearer was essentially r-admin minus five verbs at the wire layer (CWE-862).
This commit:
- Adds rbacGateScoped(checker, perm, scopeType, scopeFn, h) helper to
internal/api/router/router.go for path-bound scope resolution. Per-profile
and per-issuer grants (Decision 2) now reach the wire layer.
- Wraps every state-changing route AND every read endpoint in router.go
with rbacGate (global) or rbacGateScoped (path-bound). The auth-management
routes (POST /api/v1/auth/roles, etc.) gain router-level enforcement
in addition to the existing service-layer Authorizer check — defense in
depth (HIGH-9 of the same audit collapses into this closure).
- Auth-exempt surfaces stay un-gated by design: login, callback, BCL,
logout, breakglass-login, bootstrap, health, auth-info, version. Allowlist
is documented in TestRouterRBACGateCoverage.
- Extends internal/domain/auth/validate.go CanonicalPermissions with 30 new
perms across 12 namespaces: cert.edit; job.read, job.cancel; approval.read,
approval.approve, approval.reject; policy.read/edit/delete;
team.read/edit/delete; owner.read/edit/delete; notification.read/edit;
discovery.read/run/claim; network_scan.read/edit/run;
healthcheck.read/edit/delete/acknowledge; digest.read, digest.send;
verification.read, verification.run; stats.read; metrics.read.
- Updates DefaultRoles for r-admin / r-operator / r-viewer / r-mcp / r-cli /
r-agent. r-auditor gets NOTHING new — the auditor pin
(TestAuditorRoleHoldsExactlyAuditReadAndExport) stays invariant.
- Migration 000039_audit_crit1_perms seeds the new perm rows + role grants
per the updated DefaultRoles map. Idempotent ON CONFLICT DO NOTHING.
Reverse migration removes role_permissions before permissions
(ON DELETE RESTRICT on the FK).
- AST-level CI guard TestRouterRBACGateCoverage in
internal/api/router/router_rbac_coverage_test.go walks router.go and
asserts every state-changing + read route is wrapped (or in the
documented allowlist). Adding a new ungated route fails CI.
- Updates docs/operator/rbac.md permission-catalogue table with the new
namespaces + footer link to the AST CI guard.
- Updates certctl/CHANGELOG.md v2.1.0 section with the closure narrative.
Audit doc cowork/auth-bundles-audit-2026-05-10.md CRIT-1 row annotated
CLOSED 2026-05-10. Bundle's exit-gate spec lives at
cowork/auth-bundles-fixes-2026-05-10/01-crit-1-rbac-gates.md.
CRIT-2 / CRIT-3 / CRIT-4 / CRIT-5 of the same audit remain open and
continue to block the v2.1.0 tag.
Verification gate green:
- gofmt -d (no diff after gofmt -w on the touched files)
- go vet ./...
- go test -short -count=1 ./... (all packages pass including auditor pin)
- go build ./...
HIGH-9 of the audit closes via this commit's router-layer rbacGate on
POST /api/v1/auth/keys/{id}/roles + DELETE /api/v1/auth/keys/{id}/roles/{role_id}
(defense-in-depth on top of the existing service-layer privilege check).
Refs: cowork/auth-bundles-audit-2026-05-10.md CRIT-1 HIGH-9
43 lines
1.7 KiB
PL/PgSQL
43 lines
1.7 KiB
PL/PgSQL
-- 000039_audit_crit1_perms.down.sql
|
|
-- Reverse of 000039_audit_crit1_perms.up.sql.
|
|
--
|
|
-- role_permissions.permission_id is ON DELETE RESTRICT, so the down
|
|
-- migration explicitly removes the role grants first, then the
|
|
-- permission rows themselves. Wrapped in a single transaction.
|
|
|
|
BEGIN;
|
|
|
|
DELETE FROM role_permissions WHERE permission_id IN (
|
|
'p-cert-edit',
|
|
'p-job-read', 'p-job-cancel',
|
|
'p-approval-read', 'p-approval-approve', 'p-approval-reject',
|
|
'p-policy-read', 'p-policy-edit', 'p-policy-delete',
|
|
'p-team-read', 'p-team-edit', 'p-team-delete',
|
|
'p-owner-read', 'p-owner-edit', 'p-owner-delete',
|
|
'p-notification-read', 'p-notification-edit',
|
|
'p-discovery-read', 'p-discovery-run', 'p-discovery-claim',
|
|
'p-network-scan-read', 'p-network-scan-edit', 'p-network-scan-run',
|
|
'p-healthcheck-read', 'p-healthcheck-edit', 'p-healthcheck-delete', 'p-healthcheck-acknowledge',
|
|
'p-digest-read', 'p-digest-send',
|
|
'p-verification-read', 'p-verification-run',
|
|
'p-stats-read', 'p-metrics-read'
|
|
);
|
|
|
|
DELETE FROM permissions WHERE id IN (
|
|
'p-cert-edit',
|
|
'p-job-read', 'p-job-cancel',
|
|
'p-approval-read', 'p-approval-approve', 'p-approval-reject',
|
|
'p-policy-read', 'p-policy-edit', 'p-policy-delete',
|
|
'p-team-read', 'p-team-edit', 'p-team-delete',
|
|
'p-owner-read', 'p-owner-edit', 'p-owner-delete',
|
|
'p-notification-read', 'p-notification-edit',
|
|
'p-discovery-read', 'p-discovery-run', 'p-discovery-claim',
|
|
'p-network-scan-read', 'p-network-scan-edit', 'p-network-scan-run',
|
|
'p-healthcheck-read', 'p-healthcheck-edit', 'p-healthcheck-delete', 'p-healthcheck-acknowledge',
|
|
'p-digest-read', 'p-digest-send',
|
|
'p-verification-read', 'p-verification-run',
|
|
'p-stats-read', 'p-metrics-read'
|
|
);
|
|
|
|
COMMIT;
|