feat: add developer console and phase-1 for sandbox lifecycle (OSEP-0006)#835
feat: add developer console and phase-1 for sandbox lifecycle (OSEP-0006)#835divyamagrawal06 wants to merge 13 commits into
Conversation
…cle (OSEP-0006) Implements OSEP-0006 Phase 1 (alibaba#348). Closes alibaba#348. Server — auth / identity - Config: add AuthConfig (mode: api_key_only|api_key_and_user), AuthzConfig (roles, scope keys), ConsoleConfig (optional SPA mount). Default is api_key_only — existing deployments are unaffected. - AuthMiddleware: dual-auth path for console; trusted-header user identity set by a reverse proxy. Missing required headers → 401 MISSING_TRUSTED_IDENTITY; never falls back to anonymous. - principal.py: Principal dataclass (source, subject, role, canonical_owner/team). canonicalize_scoped_value() maps arbitrary strings to deterministic Kubernetes label-safe tokens. Server — authorization and lifecycle - authorization.py: authorize_action() enforces read_only / operator / service_admin role matrix (OSEP-0006 Table 1); 403 on role or owner/team scope mismatch. - lifecycle_helpers.py: authorize_mutating_action() wraps authorize_action and emits a mutation_audit entry on denial (403s are always recorded). apply_reserved_metadata_for_create() injects access.owner / access.team on create; merge_list_scope_from_request() enforces scope on list. - All mutating routes (create/delete/pause/resume/renew) audit success, HTTPException error, 404 not_found, 403 forbidden, and bare Exception UNEXPECTED outcomes consistently. - Optional console SPA static mount in main.py (console.enabled). Console (console/) - Standalone React + TypeScript SPA (Vite, Tailwind, dark-default). - Pages: Sandbox List (filter by state/metadata), Detail (renew, endpoint, pause/resume, delete), Create (image, entrypoint, timeout, resources, env, metadata). - API client uses user-auth path — no server API key in browser code. - Role hint from VITE_UI_ROLE disables mutating buttons for read_only; server is always the enforcement point. - AuthHint renders an explicit misconfiguration banner on 401 MISSING_TRUSTED_IDENTITY instead of silently retrying. - Vite dev proxy injects trusted headers from VITE_DEV_IDENTITY_USER / VITE_DEV_IDENTITY_ROLES for local development. - Unit tests (vitest) and Playwright integration tests with reference screenshots under docs/public/images/console/. Spec - sandbox-lifecycle.yml: document dual auth modes, 401 trusted-header semantics, 403 INSUFFICIENT_ROLE / OUT_OF_SCOPE codes, reserved metadata keys for ownership scoping. Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
|
Changed directories: .gitignore、console、docs. 📋 Recommended labels (based on changed files):
Other available labels:
💡 Tip: Use |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e28332362f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Implements Phase 1 of OSEP-0006 by adding a React-based developer console on top of the existing sandbox lifecycle API, while introducing a config-gated dual-auth/authz path for browser users alongside the existing API-key workflow.
Changes:
- Adds server-side auth/authz primitives for trusted-header users, including roles, owner/team scoping, and audit-style lifecycle helpers.
- Adds a new
console/SPA for listing, viewing, creating, renewing, pausing/resuming, deleting sandboxes, and fetching endpoints. - Updates API spec, sample config, and server/console tests to cover the new rollout path.
Reviewed changes
Copilot reviewed 43 out of 50 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| specs/sandbox-lifecycle.yml | Documents dual auth modes and updated lifecycle auth semantics. |
| server/tests/test_routes_renew_expiration.py | Updates renew-expiration route tests for authz-aware flow. |
| server/tests/test_routes_pause_resume.py | Updates pause/resume route tests for prefetch/authz behavior. |
| server/tests/test_routes_endpoint_behavior.py | Adjusts endpoint route tests for authz-backed sandbox lookup. |
| server/tests/test_routes_create_delete.py | Refreshes create/delete route tests and extension validation coverage. |
| server/tests/test_routes_authorization.py | Adds end-to-end route authorization tests for read_only/operator users. |
| server/tests/test_principal.py | Adds unit tests for principal construction and role resolution. |
| server/tests/test_lifecycle_helpers.py | Tests lifecycle helper scoping and forbidden-action logging. |
| server/tests/test_helpers.py | Adds a reusable minimal sandbox test fixture. |
| server/tests/test_config.py | Adds console mount-path validation tests. |
| server/tests/test_authorization.py | Adds unit tests for role matrix and scope checks. |
| server/tests/test_auth_trusted_header.py | Adds trusted-header auth middleware tests. |
| server/opensandbox_server/middleware/principal.py | Introduces principal model, canonical scope values, and role derivation. |
| server/opensandbox_server/middleware/authorization.py | Adds lifecycle authorization and scope enforcement helpers. |
| server/opensandbox_server/middleware/auth.py | Extends auth middleware for API key + trusted-header user mode. |
| server/opensandbox_server/main.py | Mounts the built console SPA when enabled. |
| server/opensandbox_server/examples/example.config.toml | Shows example auth/authz/console configuration. |
| server/opensandbox_server/config.py | Adds auth/authz/console config models and constants. |
| server/opensandbox_server/api/lifecycle.py | Wires authz, scoping, and audit logging into lifecycle routes. |
| server/opensandbox_server/api/lifecycle_helpers.py | Adds shared helpers for principal lookup, scope merge, metadata injection, and audit logs. |
| docs/.vitepress/config.mts | Tweaks docs site presentation config. |
| console/vitest.config.ts | Configures console unit testing. |
| console/vite.config.ts | Configures console base path and dev proxy/header injection. |
| console/tsconfig.node.json | Adds TS config for Vite/Vitest tooling files. |
| console/tsconfig.json | Adds TS config for console source. |
| console/tests/vitest-setup.ts | Sets up jest-dom for Vitest. |
| console/tests/unit/role.test.ts | Tests console role helper behavior. |
| console/tests/unit/client.test.ts | Tests console API client parsing and error handling. |
| console/tests/playwright.config.example.ts | Adds Playwright config for console e2e runs. |
| console/tests/e2e/console.integration.spec.ts | Adds browser-flow tests and screenshot generation. |
| console/tailwind.config.js | Adds Tailwind theme config for the console. |
| console/src/vite-env.d.ts | Adds Vite env typings for the console. |
| console/src/tailwind.css | Adds Tailwind CSS entrypoint. |
| console/src/pages/ListPage.tsx | Implements sandbox list/filter UI. |
| console/src/pages/DetailPage.tsx | Implements sandbox detail and action UI. |
| console/src/pages/CreatePage.tsx | Implements sandbox creation form UI. |
| console/src/main.tsx | Boots the console app and router basename handling. |
| console/src/components/AuthHint.tsx | Adds shared auth and error hint components. |
| console/src/App.tsx | Adds app shell, nav, theme toggle, and routes. |
| console/src/api/role.ts | Adds UI role parsing helpers. |
| console/src/api/client.ts | Adds browser client for lifecycle API calls. |
| console/postcss.config.js | Adds PostCSS/Tailwind pipeline config. |
| console/package.json | Defines console scripts and dependencies. |
| console/index.html | Adds the console HTML entrypoint. |
| .gitignore | Ignores console build and test artifacts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…t-side routing Snapshot authz gap: snapshot routes were not covered by the OSEP-0006 role matrix, allowing read_only principals to call create/delete snapshot when auth.mode=api_key_and_user. Add CREATE_SNAPSHOT and DELETE_SNAPSHOT as operator-only actions; LIST_SNAPSHOTS and GET_SNAPSHOT as read_only-allowed. Wire authorize_action / authorize_mutating_action + full mutation audit logging (success/error/UNEXPECTED) into all four snapshot handlers. create_snapshot also enforces sandbox owner/team scope, matching the pattern used for delete/pause/resume/renew. SPA fallback: StaticFiles(html=True) serves index.html for directory paths but returns 404 for unknown sub-paths, breaking BrowserRouter routes like /console/sandboxes/:id on direct load or refresh. Replace with _SPAStaticFiles which catches 404 from the parent and falls back to index.html, the standard pattern for single-page apps on Starlette. Update test_routes_snapshots to patch lifecycle.sandbox_service in create_snapshot tests now that the handler calls get_sandbox for scope resolution. Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
- Remove undocumented CONSOLE=1 env injection from CreatePage; it made console-created sandboxes behave differently from API requests and risked colliding with existing env vars in user workloads - Fix read-only banner to reference VITE_UI_ROLE (the actual UI hint var) instead of VITE_DEV_IDENTITY_ROLES (the dev proxy identity header var) - Fix vite.config.ts proxy key: try VITE_API_PREFIX first so the proxy and the browser client both key off the same env var when the API prefix is customized; fall back to VITE_API_BASE_PATH then /v1 - Replace external raw.githubusercontent.com logo img with an inline SVG so the console works in offline and CSP-restricted deployments - Disable 'Next' pagination button when data.pagination.hasNextPage is false instead of only while a request is in-flight - Add accessible sr-only label to the port input in DetailPage - Replace hardcoded 127.0.0.1:8080 in the 500 fallback message with a generic prompt to check server and proxy logs - Log a warning when console.enabled = true but console/dist is not found so operators get an actionable message instead of a silent 404 Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
|
Resolved copilot and codex comments. Would appreciate a review @jwx0925. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4624e1a8ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…-restore path - authorize_snapshot_scope helper resolves snapshot's source sandbox and raises OUT_OF_SCOPE (403) for user-scoped principals whose sandbox is outside their owner/team scope or has been deleted - create_sandbox: when snapshotId is provided, scope-check the source sandbox before delegating to the service layer - get_snapshot / delete_snapshot: scope-check after snapshot fetch; delete also logs a forbidden audit event on rejection - list_snapshots: if sandboxId filter provided, scope-check that sandbox; otherwise inject access_owner/access_team into the SQL query so only the caller's own snapshots are returned - persist access_owner/access_team on SnapshotRecord at create time; SQLite migration adds columns to existing databases transparently Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
…ole from server Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38157e908f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…box deletion Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
|
Resolved @jwx0925 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 33b035837d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…, tighten fallback and console bypass Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bf82d6b56f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…results Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
Resolve conflicts in lifecycle routes (keep OSEP auth/async handlers) and sandbox-lifecycle spec (pool mode + dual-auth docs). Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
Upstream main workflow updates are picked up at PR merge time; reverting avoids OAuth workflow-scope requirement on fork push. Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
|
Fixed conflicts. |
There was a problem hiding this comment.
💡 Codex Review
These SELECT projections omit access_owner/access_team, so _row_to_record reconstructs snapshots with None scope even when the DB row has ownership metadata. That breaks the new scope model: snapshot_service.get_snapshot() feeds these records into authorize_snapshot_scope, which then treats scoped snapshots as legacy and can allow GET/DELETE /snapshots/{id} after source sandbox deletion because it cannot verify owner/team. Add the scope columns to both get() and list() read queries.
OpenSandbox/server/opensandbox_server/services/snapshot_service.py
Lines 251 to 255 in f4e8f66
The delete-state transition rebuilds SnapshotRecord without copying access_owner and access_team, and update_if_state persists that replacement row. As a result, any snapshot entering Deleting loses its tenant scope metadata; if runtime deletion later errors and the record remains, subsequent authz paths see it as unscoped/legacy instead of owner-bound. Carry forward both access fields when constructing deleting_record.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Signed-off-by: divyamagrawal06 <ludicrouslytrue@gmail.com>
Summary
console/React SPA for day-to-day sandbox management (list, detail, create, renew, delete, get endpoint) so operators no longer need raw API access for common workflows.auth.mode = "api_key_and_user") with role-based access (read_only / operator) and metadata-based owner/team scoping.auth.mode = "api_key_only") is unchanged; existing API key automation is unaffected.Testing
Breaking Changes
auth.modedefaults toapi_key_only. No existing behavior changes unless operators explicitly opt in.Checklist
specs/sandbox-lifecycle.ymlupdated with dual auth modes, 401/403 semantics, reserved metadata keys; docs nav updated; console screenshots addedauth.mode = "api_key_and_user"with an explicit reverse proxy in front; missing headers always 401, never anonymous fallback; scope keys (access.owner,access.team) are server-controlled on create and cannot be overridden by the clientapi_key_onlydefault preserves all existing behavior;OPEN-SANDBOX-API-KEYpath is untouched