mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:31:33 +00:00
d473398aba
Bundle 1 / Phase 3 (primitive ship): the load-bearing RBAC middleware factory plus its dependencies. Handler conversion sweep (5 admin files: bulk_revocation.go, admin_crl_cache.go, admin_scep_intune.go, admin_est.go, intermediate_ca.go) + m008_admin_gate_test.go registry update is Phase 3.5 follow-on; this commit ships the primitive so 3.5 is mechanical. New context keys (internal/auth/context.go): ActorIDKey, ActorTypeKey, TenantIDKey alongside the legacy UserKey + AdminKey. New helpers GetActorID / GetActorType / GetTenantID with safe fallbacks (UserKey for actor id, ActorTypeAPIKey for missing type, DefaultTenantID for missing tenant). Constants DemoAnonActorID + ActorTypeAPIKey + ActorTypeAnonymous mirror internal/domain/auth without an import cycle. RequirePermission factory (internal/auth/require_permission.go): wraps a handler and gates it behind a named permission. 401 when no actor, 403 when actor lacks permission, 500 on repository error. Skips the gate entirely for protocol endpoints (ACME / SCEP / EST / OCSP / CRL) per the audit's Category F do-not-gate allowlist. PermissionChecker is an interface so internal/auth doesn't depend on internal/service/auth (cmd/server wires the concrete Authorizer at startup). HasPermission is the imperative variant for handlers that branch behaviour rather than 403'ing. ScopeFunc closure extracts the scope type + id from the request for per-resource gating. Protocol-endpoint allowlist (internal/auth/protocol_endpoints.go): IsProtocolEndpoint matches /acme, /scep, /.well-known/est, /.well-known/pki/ocsp, /.well-known/pki/crl prefixes. Adding a new protocol endpoint MUST update this list and add a parallel test. Demo-mode synthetic admin (internal/auth/middleware.go::NewDemoModeAuth): when CERTCTL_AUTH_TYPE=none is configured, this middleware injects ActorID=actor-demo-anon, ActorType=Anonymous, TenantID=t-default, plus the legacy UserKey + AdminKey for back-compat with existing handlers. The synthetic actor's admin-role grant is seeded by migration 000029 so RequirePermission resolves through the JOIN like any other actor. cmd/server startup wires this middleware only when none-mode is configured. API-key middleware extension: NewAuthWithNamedKeys now populates the new keys (ActorIDKey, ActorTypeKey=APIKey, TenantIDKey=t-default) alongside UserKey + AdminKey on every successful Bearer match. Existing handlers continue to read UserKey / IsAdmin until the Phase 3.5 sweep converts them to RequirePermission. Test coverage: TestRequirePermission_NoActorReturns401, TestRequirePermission_GrantedActorReaches200, TestRequirePermission_DeniedActorReturns403, TestRequirePermission_CheckerErrorReturns500, TestRequirePermission_ProtocolEndpointBypassesGate (covers all 5 prefixes), TestRequirePermission_ScopeFnExtractsResourceID, TestIsProtocolEndpoint_PrefixesOnly, TestNewDemoModeAuth_InjectsSyntheticActor, TestNewAuthWithNamedKeys_PopulatesPhase3ContextKeys. fakeChecker pins the contract without a database. Phase 3.5 follow-on (NOT in this commit): convert each of the 5 admin handlers from auth.IsAdmin checks to auth.RequirePermission middleware in router.go; update internal/api/handler/m008_admin_gate_test.go to track auth.RequirePermission call sites instead of (or alongside) auth.IsAdmin; pick the right permission per handler (cert.revoke for bulk_revocation, etc.). Each handler conversion needs the 3-test triplet (_NonAdmin_Returns403 / _AdminExplicitFalse_Returns403 / _AdminPermitted_ForwardsActor) per M-008. Branch: dev/auth-bundle-1. Phase 2 was prior commit (service layer). Phase 3.5 (handler conversion) + Phase 4 (HTTP API) on the next session.
50 lines
1.9 KiB
Go
50 lines
1.9 KiB
Go
package auth
|
|
|
|
import "strings"
|
|
|
|
// ProtocolEndpointPrefixes lists the URL path prefixes that authenticate
|
|
// via the protocol itself rather than via certctl's Bearer / cookie
|
|
// stack. Bundle 1 Phase 3 uses this allowlist as the explicit "do NOT
|
|
// wrap with RequirePermission" set: the RBAC middleware applies only to
|
|
// admin handlers replacing legacy IsAdmin checks plus any new
|
|
// permission-gated routes; the endpoints below keep their existing
|
|
// protocol-level auth.
|
|
//
|
|
// Adding a new protocol endpoint that doesn't take a Bearer token MUST
|
|
// also add the prefix here and a parallel test in Phase 12 asserting
|
|
// the route is unwrapped.
|
|
//
|
|
// Per the Phase 3 audit:
|
|
//
|
|
// ACME server : /acme/profile/<id>/* + /acme/* (JWS-signed, RFC 8555).
|
|
// SCEP server : /scep (challenge password +
|
|
// signed CSR, RFC 8894).
|
|
// EST server : /.well-known/est/* (mTLS client cert,
|
|
// RFC 7030).
|
|
// OCSP responder : /.well-known/pki/ocsp (RFC 6960, public).
|
|
// CRL distrib. : /.well-known/pki/crl/* (RFC 5280, public).
|
|
//
|
|
// Plus the existing public-route bypass list at internal/api/router
|
|
// (router.go:69-72): /health, /ready, /api/v1/auth/info. Those bypass
|
|
// EVERY middleware stack, not just RBAC, so they're not in this
|
|
// allowlist; they're handled in router.go directly.
|
|
var ProtocolEndpointPrefixes = []string{
|
|
"/acme",
|
|
"/scep",
|
|
"/.well-known/est",
|
|
"/.well-known/pki/ocsp",
|
|
"/.well-known/pki/crl",
|
|
}
|
|
|
|
// IsProtocolEndpoint reports whether the request path is in the
|
|
// "do not gate" allowlist. Phase 3 RequirePermission check bails out
|
|
// early for these paths so the protocol surface is preserved.
|
|
func IsProtocolEndpoint(path string) bool {
|
|
for _, p := range ProtocolEndpointPrefixes {
|
|
if path == p || strings.HasPrefix(path, p+"/") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|