Phase 14 worker identity hardening - #2
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
prod v.1.14.5