Skip to content

Phase 14 worker identity hardening - #2

Merged
arpan-mondal merged 147 commits into
mainfrom
phase-14-worker-identity-hardening
May 11, 2026
Merged

Phase 14 worker identity hardening#2
arpan-mondal merged 147 commits into
mainfrom
phase-14-worker-identity-hardening

Conversation

@arpan-mondal

Copy link
Copy Markdown
Contributor

prod v.1.14.5

arpan-mondal and others added 29 commits May 9, 2026 23:34
Closes the enterprise multi-worker production readiness gates.

## Authentication & authorisation
- OIDC/JWT Bearer auth: RS256 + ES256/ES384/ES512, JWKS URL, static PEM key
- OIDC config fields: issuer, audience, jwks_url, public_key_file, groups_claim,
  tenant_claim, admin/operator/viewer group mappings
- RBAC roles: viewer (read:*), operator (api:*+operator:*), tenant_admin,
  admin (full), worker (unchanged)
- Scope enforcement wired on every sandbox route when auth is configured;
  viewer tokens are blocked from spawn, exec, and file writes
- Config lint validates OIDC configuration; CI now runs OIDC lint check

## Multi-tenancy & policy controls
- tenants, tenant_members, policies tables (SQLite + Postgres migration 11)
- tenant_id column on sandboxes, admin_audit_logs, operation_audit_logs
- Tenant CRUD, member role assignment, per-tenant audit export, per-tenant
  policy management via /api/v1/admin/tenants
- Policy enforcement middleware: image/provider/network allow-deny rules with
  glob matching; body buffered so downstream handlers still decode it
- Sandbox List/Get/Destroy/Exec/file operations enforce tenant boundaries

## Worker identity & HA
- Centralized signed worker token issuance: POST /api/v1/admin/worker-tokens
- stacyvm worker --bootstrap-admin-key fetches short-lived tokens from issuer
- Worker heartbeat now advertises https:// rpc_url when rpc_tls.enabled = true
- Durable EventBus: Postgres LISTEN/NOTIFY bridge propagates events across
  control-plane replicas; instance UUID prevents double-delivery on origin

## Ops tooling
- stacyvm db pg-backup: wraps pg_dump for Postgres cluster snapshots
- stacyvm db pg-rehearse: verifies schema state and table presence before upgrades
- smoke-remote-worker.sh --mtls: generates ephemeral CA + certs, runs full
  mTLS smoke (spawn → exec → destroy over HTTPS mutual TLS)
- certify-runtime.sh --stacyvm-bin: auto-starts server, spawns sandbox, execs
  a command, destroys — proves runtime works end-to-end through StacyVM
- Enterprise signoff runbook: docs/enterprise-signoff-runbook.md

## CI
- New remote-worker-mtls-smoke CI job: runs --mtls smoke on every push
- New runtime-certification CI job: certify-runtime.sh docker on ubuntu-latest
  with artifact upload of the Markdown certification report
- ci-cluster-conformance.sh: OIDC lint check + mTLS smoke added as final gates

## Bug fixes
- Pre-existing data race in ExecStream goroutine vs deferred metrics recorder
  (goroutine-local variable, no shared state)
- Policy middleware body-buffering fix: io.ReadAll + io.NopCloser restores body
  for downstream handlers

## Security hygiene
- .gitignore: added *.pem, *.key, *.crt, *.p12, *.pfx, *.csr
- SECURITY.md: updated to reflect OIDC, RBAC, mTLS, tenancy, audit model
- README: Enterprise/Multi-worker section, OIDC config, updated roadmap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LICENSE file has always been Apache 2.0 since the initial commit.
README incorrectly stated MIT in the tagline, badge, comparison table,
and license section. All four references corrected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five security bugs fixed before public launch. No new features.

## RS256 uses crypto.SHA256 (was hash=0)
rsa.VerifyPKCS1v15 was called with hash=0 meaning no hash algorithm — this
is not RS256. Every real OIDC provider (Google, Okta, Azure AD, Cloudflare)
signs with SHA256, so all production tokens were rejected. Fixed to
crypto.SHA256. Test helper and new TestVerifyJWT_RS256UsesSHA256 regression
test updated to match.

## Admin routes unprotected in OIDC-only mode
When only OIDC was configured (no admin API key), AdminAuth passed anonymous
requests through silently (adminAPIKey=="" branch) and RequireScope(ScopeAdmin)
was not applied (guarded by API key presence, not authConfigured). Any request
could reach /api/v1/admin/diagnostics, /api/v1/admin/worker-tokens, etc.
Fixed: RequireScope(ScopeAdmin) now fires whenever authConfigured. AdminAuth
skips only for OIDC Bearer identities (Header=="Authorization") so API key
role promotion still works. Full coverage in TestAuthMatrix_OIDCOnly_AdminRouteRequiresAdminRole.

## AuthAny rejected OIDC tokens in mixed OIDC+API-key mode
When both OIDC and API keys were configured, a valid Bearer token was accepted
by OIDCAuth but then AuthAny tried to find an API key header, failed, and
returned 401. Fixed: AuthAny skips its check when OIDC already set an
Authorization-header identity.

## PolicyEnforcer never mounted
Policies could be created but were never evaluated on sandbox creation.
PolicyEnforcer(st) is now applied around POST /api/v1/sandboxes via
NewSandboxRoutesWithPolicy. Server wires the full store automatically.

## Worker token issuer accepted non-worker scopes
admin:* or api:* could be requested in a worker token; the scope was
silently filtered at verification but should be rejected at issuance.
POST /api/v1/admin/worker-tokens now returns 400 for any non-worker:* scope.

## Auth matrix regression tests added
7 tests in internal/api/auth_matrix_test.go cover:
- API-key only: accept/reject on normal + admin routes
- OIDC only: valid bearer, no bearer, expired, viewer-can-read/cannot-spawn,
  anonymous blocked on admin, non-admin blocked on admin, admin-group passes
- Mixed: API key works, OIDC bearer works, invalid bearer rejected, no auth rejected
- Worker token not treated as OIDC Bearer on API routes
- Token issuer rejects admin:* scope, accepts worker:* scope

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generated-By: mintlify-agent

Co-authored-by: mintlify[bot] <109931778+mintlify[bot]@users.noreply.github.com>
@arpan-mondal
arpan-mondal merged commit 2e2ea8b into main May 11, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant