mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-10 03:58:54 +00:00
api, handler: 4 admin-gated CA hierarchy endpoints + OpenAPI (Rank 8 commit 4)
Rank 8 commit 4 of 5. The API + RBAC layer that operators drive
the new hierarchy management surface from.
Endpoints (all admin-gated via middleware.IsAdmin; non-admin Bearer
callers get 403):
POST /api/v1/issuers/{id}/intermediates
Discriminator on body shape:
empty parent_ca_id + root_cert_pem + key_driver_id
→ CreateRoot (registers operator-supplied root CA).
parent_ca_id non-empty
→ CreateChild (signs new sub-CA cert under parent).
Service-layer error → HTTP code mapping:
ErrCANotSelfSigned → 400
ErrCAKeyMismatch → 400
ErrPathLenExceeded → 400
ErrNameConstraintExceeded → 400
ErrInvalidCertPEM → 400
ErrParentCANotActive → 409
ErrIntermediateCANotFound → 404
(other) → 500
GET /api/v1/issuers/{id}/intermediates
Returns flat list ordered by created_at; caller renders the
tree from each row's parent_ca_id (nil = root).
GET /api/v1/intermediates/{id}
Single-row detail.
POST /api/v1/intermediates/{id}/retire
Two-phase: confirm=false → active→retiring; confirm=true →
retiring→retired with active-children check (drain-first
semantics; ErrCAStillHasActiveChildren → 409).
Files changed:
internal/api/handler/intermediate_ca.go — 4 handlers
+ handler-defined
service interface
(dependency
inversion).
internal/api/handler/intermediate_ca_test.go — 8 test variants
(M-008 admin-
gate triplet
complete).
internal/api/handler/m008_admin_gate_test.go — register the
new admin-gated
handler in
AdminGatedHandlers
so the M-008
coherence
scanner stays
green.
internal/api/router/router.go — 4 r.Register
calls + new
IntermediateCAs
field on
HandlerRegistry.
cmd/server/main.go — wire the
postgres repo +
service +
handler. Reuses
the same
signer.FileDriver
instance the
OCSP responder
bootstrap path
feeds.
api/openapi.yaml — 4 new
operationIds,
full body
schema + status-
code dispatch.
Tests (8 in this commit):
TestIntermediateCA_Handler_NonAdmin_Returns403 (admin gate
— table-driven across all 4 endpoints)
TestIntermediateCA_Handler_AdminExplicitFalse_Returns403
(defensive: AdminKey present but false ≠ AdminKey absent)
TestIntermediateCA_Handler_AdminPermitted_ForwardsActor
(admin actor forwarded to service for audit attribution)
TestIntermediateCA_HandlerCreate_RootDispatch
(body discriminator: empty parent_ca_id → CreateRoot)
TestIntermediateCA_HandlerCreate_ChildDispatch
(body discriminator: parent_ca_id present → CreateChild)
TestIntermediateCA_HandlerCreate_BadRequestOnMissingRootBundle
(validation: no parent + no root bundle → 400)
TestIntermediateCA_HandlerCreate_ServiceErrorMappings
(table-driven: 7 service errors → expected HTTP codes)
TestIntermediateCA_HandlerRetire_TwoPhaseConfirm
(confirm=false then confirm=true forwarded correctly)
TestIntermediateCA_HandlerRetire_StillHasActiveChildren_Returns409
(drain-first contract — 409 not 500)
Verified locally:
gofmt: clean.
go vet ./...: exit 0.
go test -short -count=1 ./internal/api/handler/...: ok 4.498s.
bash scripts/ci-guards/openapi-handler-parity.sh: clean
(router routes: 182, openapi operations: 148; the +4 new routes
have +4 new operationIds — parity preserved).
bash scripts/ci-guards/* (all 24 guards): clean.
Out of scope of THIS commit (commit 5):
- web/src/pages/IssuerHierarchyPage.tsx (recursive tree render).
- docs/intermediate-ca-hierarchy.md sysadmin runbook (FedRAMP /
financial-services / internal-PKI patterns).
- docs/connectors.md hierarchy_mode row.
- WORKSPACE-ROADMAP entries (HSM-backed roots, automated
rotation, CRL chaining, NameConstraints templates, D3
dendrogram).
Reference: cowork/rank-8-intermediate-ca-hierarchy-prompt.md, commit 4.
This commit is contained in:
@@ -169,6 +169,20 @@ type HandlerRegistry struct {
|
||||
// surfaces ErrApproveBySameActor as HTTP 403. See
|
||||
// docs/approval-workflow.md for the operator playbook.
|
||||
Approvals handler.ApprovalHandler
|
||||
|
||||
// IntermediateCAs handles the admin-gated CA-hierarchy management
|
||||
// surface under /api/v1/issuers/{id}/intermediates and
|
||||
// /api/v1/intermediates/{id}. Rank 8 of the 2026-05-03 deep-
|
||||
// research deliverable — closes the multi-level CA hierarchy gap
|
||||
// for FedRAMP boundary-CA, financial-services policy-CA, and OT
|
||||
// network-CA deployments. Routes:
|
||||
// POST /api/v1/issuers/{id}/intermediates
|
||||
// GET /api/v1/issuers/{id}/intermediates
|
||||
// GET /api/v1/intermediates/{id}
|
||||
// POST /api/v1/intermediates/{id}/retire
|
||||
// Admin-gated at the handler layer (M-003 pattern). See
|
||||
// docs/intermediate-ca-hierarchy.md for the operator playbook.
|
||||
IntermediateCAs handler.IntermediateCAHandler
|
||||
}
|
||||
|
||||
// RegisterHandlers sets up all API routes with their handlers.
|
||||
@@ -373,6 +387,16 @@ func (r *Router) RegisterHandlers(reg HandlerRegistry) {
|
||||
r.Register("POST /api/v1/approvals/{id}/approve", http.HandlerFunc(reg.Approvals.Approve))
|
||||
r.Register("POST /api/v1/approvals/{id}/reject", http.HandlerFunc(reg.Approvals.Reject))
|
||||
|
||||
// IntermediateCA hierarchy routes (Rank 8). Admin-gated inside the
|
||||
// handler (M-003 pattern); non-admin Bearer callers get 403. The
|
||||
// /retire literal segment resolves before the {id} pattern-var
|
||||
// route under Go 1.22 ServeMux precedence — the ordering below
|
||||
// matches the notifications + approvals blocks above.
|
||||
r.Register("POST /api/v1/issuers/{id}/intermediates", http.HandlerFunc(reg.IntermediateCAs.Create))
|
||||
r.Register("GET /api/v1/issuers/{id}/intermediates", http.HandlerFunc(reg.IntermediateCAs.List))
|
||||
r.Register("POST /api/v1/intermediates/{id}/retire", http.HandlerFunc(reg.IntermediateCAs.Retire))
|
||||
r.Register("GET /api/v1/intermediates/{id}", http.HandlerFunc(reg.IntermediateCAs.Get))
|
||||
|
||||
// Stats routes: /api/v1/stats
|
||||
r.Register("GET /api/v1/stats/summary", http.HandlerFunc(reg.Stats.GetDashboardSummary))
|
||||
r.Register("GET /api/v1/stats/certificates-by-status", http.HandlerFunc(reg.Stats.GetCertificatesByStatus))
|
||||
|
||||
Reference in New Issue
Block a user