mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:31:33 +00:00
60a589ab96
Closes the 5 gaps the post-Phase-5 audit flagged on dev/auth-bundle-1.
C1: cmd/server/main.go now selects auth.NewDemoModeAuth() when
CERTCTL_AUTH_TYPE=none and falls back to auth.NewAuthWithNamedKeys
otherwise. Pre-closure, the no-op pass-through that
NewAuthWithNamedKeys returns for empty keys would have left
ActorIDKey / ActorTypeKey / TenantIDKey unpopulated and 401'd
every Phase-3.5 rbacGate-wrapped admin route + every Phase-4
RBAC handler in demo deployments. NewDemoModeAuth injects the
synthetic 'actor-demo-anon' actor seeded by migration 000029,
which holds r-admin at global scope.
C2: backfillNamedKeyActorRoles startup hook (cmd/server/auth_backfill.go)
iterates CERTCTL_API_KEYS_NAMED entries (and legacy
CERTCTL_AUTH_SECRET synthesized fallbacks) and grants r-admin
or r-viewer to each via authActorRoleRepo.Grant before the
HTTP server starts accepting requests. Idempotent via
ON CONFLICT DO NOTHING in the repo. Failures log a warning but
are non-fatal — the server still starts and the operator can
fix grants via /v1/auth/keys. Helper extracted from main.go so
the role-mapping invariant is pinned by 4 focused unit tests
(admin->r-admin, non-admin->r-viewer, empty no-op,
grant-error non-fatal, nil-logger safe).
M1: HealthHandler.AuthCheck now returns actor_id, actor_type,
tenant_id, roles, effective_permissions, and admin_via_role
when the optional AuthCheckResolver is wired (production path:
authCheckResolverAdapter wraps the postgres ActorRoleRepository
in main.go). Nil resolver preserves the legacy {status, user,
admin} contract for back-compat with pre-Bundle-1 GUIs and
test fixtures. Adds 2 regression tests + 1 fake resolver shim.
M2: refreshes the stale 'Admin gate: every method calls
auth.IsAdmin first' comment on IntermediateCAHandler — the gate
moved to router.go::rbacGate via auth.RequirePermission
middleware in Phase 3.5; the new comment block points readers
there.
M4: 11 RBAC routes (auth/me, auth/permissions, 5 role lifecycle,
2 role-permission grant/revoke, 2 actor-role grant/revoke) added
to api/openapi.yaml under the [Auth] tag with operationIds and
shared AuthRole / AuthRolePermission schemas. AuthCheck path
extended with the Bundle-1 enrichment fields. The 11 entries
removed from openapi_parity_test.go::SpecParityExceptions.
Tests: go vet + staticcheck + go test -short -count=1 green
across cmd/server/, internal/auth/, internal/api/router/, and
internal/api/handler/. New tests: 4 backfill unit tests,
2 AuthCheck M1 enrichment tests, 1 demo-mode + rbacGate chain
integration test (TestRBACGate_DemoModeChainReachesHandler).
Branch SECURITY.md (cowork/auth-bundle-1-SECURITY.md, not part
of this commit) captures the full posture of dev/auth-bundle-1
as of this closure for the operator's pre-merge review.
59 lines
2.0 KiB
Go
59 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"github.com/certctl-io/certctl/internal/auth"
|
|
"github.com/certctl-io/certctl/internal/domain"
|
|
authdomain "github.com/certctl-io/certctl/internal/domain/auth"
|
|
)
|
|
|
|
// actorRoleGranter is the narrow interface backfillNamedKeyActorRoles
|
|
// needs from the postgres ActorRoleRepository. Pulled out so the unit
|
|
// test can inject a fake without spinning up the full repo / DB.
|
|
type actorRoleGranter interface {
|
|
Grant(ctx context.Context, ar *authdomain.ActorRole) error
|
|
}
|
|
|
|
// backfillNamedKeyActorRoles is the Bundle 1 Phase 3 closure (C2)
|
|
// startup hook that ensures every CERTCTL_API_KEYS_NAMED entry — and
|
|
// every legacy CERTCTL_AUTH_SECRET synthesized fallback — has an
|
|
// actor_roles row before the HTTP server accepts requests. Admin-flagged
|
|
// keys grant `r-admin` (full canonical permission set); non-admin keys
|
|
// grant `r-viewer` (read-only surface), matching the pre-Phase-3.5
|
|
// capability shape.
|
|
//
|
|
// Idempotent via ON CONFLICT DO NOTHING in the repo Grant — reboots
|
|
// don't create duplicates. Failures are logged but non-fatal: the server
|
|
// still starts, and the operator can fix the grant via the RBAC API.
|
|
//
|
|
// The function is package-private + extracted from main() so the unit
|
|
// test in auth_backfill_test.go can pin the role-mapping invariant
|
|
// without depending on the full server bootstrap path.
|
|
func backfillNamedKeyActorRoles(
|
|
ctx context.Context,
|
|
repo actorRoleGranter,
|
|
keys []auth.NamedAPIKey,
|
|
logger *slog.Logger,
|
|
) {
|
|
for _, nk := range keys {
|
|
role := authdomain.RoleIDViewer
|
|
if nk.Admin {
|
|
role = authdomain.RoleIDAdmin
|
|
}
|
|
if err := repo.Grant(ctx, &authdomain.ActorRole{
|
|
ActorID: nk.Name,
|
|
ActorType: authdomain.ActorTypeValue(domain.ActorTypeAPIKey),
|
|
RoleID: role,
|
|
TenantID: authdomain.DefaultTenantID,
|
|
GrantedBy: "bootstrap",
|
|
}); err != nil {
|
|
if logger != nil {
|
|
logger.Warn("api-key actor-role backfill failed; key authenticates but RBAC routes will 403 until grant is added via /v1/auth/keys",
|
|
"key", nk.Name, "role", role, "err", err)
|
|
}
|
|
}
|
|
}
|
|
}
|