mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-17 05:31:16 +08:00
pull-request/2147
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c7879a0a1d |
test(e2e): stop using custom e2e binary builds (#2000)
Remove the production-crate dev-settings feature so e2e tests build the same gateway, CLI, and supervisor binaries that we ship. Move settings coverage from dummy test-only keys to the production ocsf_json_enabled setting. Signed-off-by: Simon Scatton <sscatton@nvidia.com> |
||
|
|
702cbc4f63 |
feat(providers): support SPIFFE-backed token grants (#1784)
* feat(providers): support SPIFFE-backed token grants Add provider profile token_grant metadata and expand endpoint-specific dynamic credentials so sandbox supervisors can request SPIFFE JWT-SVIDs, exchange them with an OAuth-style token endpoint, cache returned access tokens, and inject bearer tokens into matching HTTP requests. Wire Kubernetes and Helm deployments to mount the provider SPIFFE Workload API socket into sandbox pods for token grant exchange. Signed-off-by: Taylor Mutch <taylormutch@gmail.com> Signed-off-by: Gordon Sim <gsim@redhat.com> * test(examples): add SPIFFE token grant demo Add a reusable alpha/beta demo that deploys a SPIFFE-verifying token issuer and protected services, imports a token-grant provider profile, creates a sandbox, and verifies endpoint-specific bearer tokens. The script leaves Kubernetes workloads in place, deletes sandboxes through openshell unless KEEP_SANDBOX=1, and prints protected service logs as proof of life. Signed-off-by: Taylor Mutch <taylormutch@gmail.com> * fix(providers): harden SPIFFE token grants * fix(providers): harden dynamic token grants Signed-off-by: Taylor Mutch <taylormutch@gmail.com> * fix(providers): harden token grant handling --------- Signed-off-by: Taylor Mutch <taylormutch@gmail.com> Signed-off-by: Gordon Sim <gsim@redhat.com> |
||
|
|
3aba30c36f |
feat(telemetry): add build-time option to compile out telemetry (#1845)
* feat(telemetry): add build-time option to compile out telemetry Gate anonymous telemetry emission behind a default-on `telemetry` Cargo feature in openshell-core. The data model (enums, validation, emit_*/enabled* signatures) stays always-compiled, while the endpoint, HTTP client, queue, and emission code are feature-gated. With the feature off, enabled() returns false and emit_* are no-ops, so dependent crates compile unchanged and no telemetry endpoint, HTTP client, or emission code is included in the binary. chrono and reqwest become optional dependencies of openshell-core, dropped from its dependency graph when telemetry is disabled. Thread the switch through the workspace: every crate depends on openshell-core with default-features = false, and the default-on `telemetry` passthrough lives on the binary crates that emit or collect telemetry (openshell-server, openshell-sandbox, openshell-driver-vm). In-process drivers inherit it via resolver v2 feature unification. Build a telemetry-free binary with, e.g.: cargo build --release -p openshell-server --no-default-features The runtime OPENSHELL_TELEMETRY_ENABLED switch is unchanged for default builds. Signed-off-by: Russell Bryant <russell.bryant@gmail.com> * ci(telemetry): guard that telemetry can be compiled out Add tasks/scripts/verify-telemetry-compiled-out.sh, which inspects a built binary for telemetry markers (the telemetry endpoint host and client ID) that exist only when emission code is compiled in. The rust:verify:telemetry-off mise task builds the gateway with default features (positive control: markers must be present, so the absent checks can never be silently vacuous) and with --no-default-features (markers must be absent), and checks the --no-default-features sandbox binary as well. Wire the task into the Rust branch-checks job so a regression that reintroduces telemetry code into a --no-default-features build fails CI. Signed-off-by: Russell Bryant <russell.bryant@gmail.com> --------- Signed-off-by: Russell Bryant <russell.bryant@gmail.com> |
||
|
|
d255cdd9c9 |
feat(providers): add credential refresh foundation (#1349)
* feat(providers): add credential refresh foundation * feat(providers): mint oauth refresh token credentials * fix(providers): allow delegated refresh bootstrap * fix(providers): guard refresh credential modes * fix(providers): refine refresh lifecycle UX * fix(cli): restore provider test gateway response import * fix(server): clean auth endpoint test qualifications * fix(providers): tighten refresh authorization and collisions * chore(providers): trim bundled v2 profiles * fix(providers): resolve refresh rebase fallout * fix(cli): accept rfc3339 credential expiry * test(providers): update inferred claude provider type * test(providers): avoid removed outlook default profile * test(providers): isolate attach limit fixtures |
||
|
|
0cbd2d6c96 |
feat(cli): add -o json/yaml output format to sandbox list (#1422)
Support machine-readable output for `openshell sandbox list` via `-o json` and `-o yaml` flags, matching the pattern established for provider profile commands. Rename ProviderProfileOutput to shared OutputFormat enum. Signed-off-by: Florent Benoit <fbenoit@redhat.com> |
||
|
|
c99849bcb7 |
fix(cli): cp-style sandbox download and workspace-boundary check (#1353)
* fix(cli): cp-style sandbox download and workspace-boundary check `openshell sandbox download` produced a directory at the host destination instead of writing the source bytes as a regular file, and did not refuse sandbox-side paths that resolved outside `/sandbox` (only `creds.json` was rejected, by filesystem policy rather than by an explicit check). Mirrors the cp-style design from the upload-side fixes (#667 / #694 and #885 / #952): destination resolution now respects trailing slashes and existing-directory destinations, single-file sources land as regular files via a staged extraction and atomic rename, and the function delegates the SSH tar stream to a shared helper. Adds an explicit lexical workspace-boundary check on the sandbox-side source — required on the download side because filesystem policy alone is not a substitute for an explicit check when crossing the trust boundary in the leak direction. Signed-off-by: Tinson Lai <tinsonl@nvidia.com> * fix(cli): canonicalise sandbox download path remotely and quote tar member The lexical check in `validate_sandbox_source_path` clears any path whose components stay under `/sandbox` after `.` and `..` are folded out, but it cannot see symlinks. A workspace-relative symlink such as `/sandbox/etc-link -> /etc` lets `openshell sandbox download <name> /sandbox/etc-link/passwd /tmp/passwd` slip through, then `tar -C /sandbox/etc-link passwd` follows the link on the sandbox side and streams `/etc/passwd` back to the host. Add `canonicalize_sandbox_source_path` which runs `realpath -e --` over SSH and rejects the result unless it still equals `/sandbox` or starts with `/sandbox/`. The lexical guard stays as a cheap fail-fast for obviously-bad inputs, and the rest of the download flow now uses the canonical path everywhere a tar invocation or probe might dereference a symlink. While editing the single-file branch, also add the `--` separator before the basename in `tar cf - -C <parent> -- <name>`. Without it, a sandbox file whose basename starts with `-` (e.g. `--checkpoint-action=...`) is parsed by GNU tar as an option rather than a member, which at best fails the wrap and at worst triggers tar option behaviour. The directory branch passes literal `.` and is unaffected. Cover both fixes with pure-function tests: `is_under_sandbox_workspace` gets accept/reject pairs (the same predicate the post-realpath validator uses) and the new `build_single_file_tar_cmd` helper asserts the `--` separator and shell escaping survive any future refactor. Signed-off-by: Tinson Lai <tinsonl@nvidia.com> * chore(cli): drop redundant tempfile dev-dependency entry `tempfile` was promoted from `[dev-dependencies]` to `[dependencies]` once the cp-style single-file download path started using `TempDir::new_in` from production code. The original `[dev-dependencies]` line stayed behind by accident — entries under `[dependencies]` are already available to test compilation, so the second declaration is dead weight. Signed-off-by: Tinson Lai <tinsonl@nvidia.com> * test(cli): add E2E coverage for sandbox download paths Adds four end-to-end tests for `openshell sandbox download` so the happy path and both Codex regressions have explicit coverage: * `sandbox_download_file_only` — seeds a single file via the new `SandboxGuard::exec` harness helper and pulls it back, exercising `sandbox_sync_down_file` without an upload phase. * `sandbox_download_directory_only` — seeds a small directory tree and pulls it recursively, exercising `sandbox_sync_down_directory`. * `sandbox_download_rejects_symlinks_pointing_outside_workspace` — regression for the high-severity finding. Plants three escape shapes (`/sandbox/etc-link -> /etc`, `/sandbox/passwd-link -> /etc/passwd`, and a path with a symlinked component `/sandbox/etc-link/passwd`) and asserts all three are refused with a workspace-boundary error before any tar streams. Also checks the host destination stays empty so a silent leak would surface. * `sandbox_download_handles_dash_leading_basename` — regression for the medium-severity finding. Seeds a file named `--checkpoint-action=evil` and verifies it downloads with byte-for-byte contents, proving the `--` separator stops tar parsing the basename as an option. The harness gains a single new method, `SandboxGuard::exec`, that wraps `openshell sandbox exec --name <name> --no-tty -- <argv>`. Existing tests preferred the upload path to populate sandbox state, but the regression cases need to plant filesystem state the upload flow cannot produce (symlinks, files whose basenames clap would otherwise reject). Signed-off-by: Tinson Lai <tinsonl@nvidia.com> * docs(cli): note cp-style destination and workspace boundary on download `openshell sandbox download` now refuses any source path that resolves outside `/sandbox` (lexically or through a symlink), and the single-file branch follows `cp`-style placement rules. Document both so users hitting the workspace-boundary error or the "land at exact path vs land inside dir" decision can self-serve. Signed-off-by: Tinson Lai <tinsonl@nvidia.com> * test(cli): tolerate miette line-wrapping in symlink-rejection assertions `sandbox_download_rejects_symlinks_pointing_outside_workspace` matched the substring `"outside the sandbox workspace"` against the CLI's error output. The check is correct in spirit but fragile in practice: miette wraps long single-line errors across multiple lines at terminal width and inserts a `│ ` continuation marker, so the substring breaks across the wrap and the test fails even though the canonicalisation step correctly refused the source. Split the match into three short markers — `resolves to`, `outside the`, `sandbox workspace` — each guaranteed to fit on one wrapped line. The combined check is stricter than the original (it now requires evidence that the remote `realpath -e` step actually ran, not just the boundary refusal) without inheriting the wrap fragility. Signed-off-by: Tinson Lai <tinsonl@nvidia.com> --------- Signed-off-by: Tinson Lai <tinsonl@nvidia.com> |
||
|
|
5abc36c461 | feat(relay): route forwarding through ForwardTcp (#1029) | ||
|
|
df8ac94d17 |
feat(cli): add --gateway-insecure flag to skip TLS certificate verification (#1212)
Allow CLI to connect to gateways with self-signed or untrusted certificates by skipping TLS verification when --gateway-insecure is set via flag or OPENSHELL_GATEWAY_INSECURE env var. |
||
|
|
084505425b |
feat(auth): add OIDC/Keycloak authentication with RBAC and scope-based permissions (#935)
* feat(auth): add OIDC/Keycloak authentication with RBAC Add OAuth2/OIDC authentication to the gateway server with role-based access control, CLI login flows, and full deployment plumbing. Server: JWT validation against configurable OIDC issuer (oidc.rs), JWKS key caching with TTL and rotation handling, method classification (unauthenticated/sandbox-secret/dual-auth/bearer), identity extraction with provider-agnostic Identity type, and RBAC enforcement via AuthzPolicy with configurable admin/user roles and auth-only mode. CLI: browser-based Authorization Code + PKCE flow, Client Credentials flow for CI/automation, token storage with refresh, gateway add/login/ logout commands, OIDC bearer token injection over mTLS transport, discovery endpoint for auto-configuration. Security: sandbox-secret scope restriction on UpdateConfig (policy sync only), anti-spoofing header stripping, dual-auth fallthrough from sandbox-secret to Bearer token. Deployment: OIDC config wired through DeployOptions, Docker env vars, Helm values/templates, HelmChart manifest, cluster-entrypoint.sh, and bootstrap scripts. Keycloak dev server script with pre-configured realm (test users, roles, PKCE client, CI client). Tested with Keycloak. The roles claim path and role names are configurable to support other OIDC providers. * feat(auth): add OAuth2 scope-based fine-grained permissions Add opt-in scope enforcement on top of existing OIDC role-based access control. When --oidc-scopes-claim is set, the server extracts scopes from the JWT and checks them per-method against an exhaustive scope map. Scopes: sandbox:read, sandbox:write, provider:read, provider:write, config:read, config:write, inference:read, inference:write, and openshell:all (wildcard). Methods not in the scope map require openshell:all. Scopes layer on top of roles and cannot escalate privilege. Auth-only mode (empty role names) still enforces scopes when enabled. Server: scopes_claim in OidcConfig, scope extraction from JWT (space-delimited and JSON array formats), standard OIDC scope filtering, scope check in AuthzPolicy after role check. CLI: --oidc-scopes on gateway add/start stored in metadata and consumed by gateway login, --oidc-scopes-claim on gateway start forwarded to server, scopes parameter in browser and client credentials OAuth2 flows with openid deduplication. Deployment: oidc_scopes_claim wired through DeployOptions, docker.rs, Helm, bootstrap scripts, and cluster entrypoint. Keycloak: realm config updated with built-in OIDC scopes and 9 OpenShell client scopes as optional on openshell-cli and openshell:all as default on openshell-ci. * fix(auth): address branch review findings Add GetInferenceBundle to sandbox-secret methods so sandbox inference route refresh works under OIDC. Make GetSandboxConfig dual-auth so CLI users can read sandbox settings with Bearer tokens. Preserve OIDC gateway metadata on restart — a bare gateway start without --oidc-* flags no longer erases the stored OIDC registration. Document CI client ID requirement (openshell-ci vs openshell-cli) in the testing guide. Add security note about auth-only mode blast radius for GitHub Actions. * fix(auth): complete review findings for OIDC auth boundary Move OpenShell/GetSandboxConfig from sandbox-secret-only to dual-auth so CLI users can read sandbox settings with Bearer tokens while sandbox supervisors continue using the shared secret. Add sandbox secret interceptor to the inference bundle fetch path so GetInferenceBundle works under OIDC-enabled gateways. Extract shared interceptor constructor to avoid duplication. Add GetSandboxConfig to the config:read scope map so scope enforcement applies consistently when scopes are enabled. Refactor OIDC metadata preservation into apply_oidc_gateway_metadata() with explicit resume semantics — only preserve existing OIDC metadata on real resume paths, not on fresh deployments. Update architecture docs and testing guide to reflect the corrected method classifications and add new test coverage for interceptor injection, scope requirements, metadata preservation, and dual-auth classification. * refactor(auth): use oauth2 crate for CLI OIDC flows Replace hand-written PKCE generation, authorization URL construction, token exchange, client credentials, and token refresh with the oauth2 crate's typed API. Eliminates sha2, hex, and getrandom dependencies from the CLI. The custom urlencoded() helper and manual form POST logic are replaced by BasicClient methods with proper type-state safety. Discovery and the callback server remain custom since the oauth2 crate does not provide OIDC discovery or a localhost redirect listener. * refactor(auth): move server auth modules into auth/ directory Group oidc.rs, authz.rs, identity.rs, and the auth HTTP endpoints under src/auth/ module directory. No behavioral changes. auth/mod.rs — module root, re-exports HTTP router auth/oidc.rs — JWT validation, JWKS caching, method classification auth/authz.rs — role and scope authorization policy auth/identity.rs — provider-agnostic Identity type auth/http.rs — /auth/connect and /auth/oidc-config endpoints * fix(auth): use RequestBody auth type for client credentials flow The oauth2 crate defaults to BasicAuth (HTTP Basic header) but Keycloak and most OIDC providers expect client_secret_post (credentials in the request body). Set AuthType::RequestBody explicitly to match the pre-refactor behavior. Also re-export Identity, IdentityProvider, and JwksCache from the auth module so ServerState's public API remains nameable by external consumers. * fix(auth): forward OPENSHELL_OIDC_SCOPES through cluster bootstrap Pass --oidc-scopes to gateway start so the metadata includes requested scopes after cluster bootstrap. Without this, users had to manually edit metadata.json to set scopes for gateway login. Usage: OPENSHELL_OIDC_SCOPES="openshell:all" mise run cluster * test(auth): add OIDC e2e tests for RBAC, scopes, and client credentials Add 10 end-to-end tests covering OIDC authentication against a live K3s cluster with Keycloak: RBAC (5 tests): admin can create providers, user cannot, user can list sandboxes, unauthenticated requests rejected, health probe works without auth. Scopes (4 tests): sandbox-scoped token can list sandboxes but not providers, openshell:all grants full access, no-scopes token denied. Client credentials (1 test): CI token via client_credentials grant. Tests are opt-in via OPENSHELL_E2E_OIDC=1 and OPENSHELL_E2E_OIDC_SCOPES=1 env vars. They derive the Keycloak URL from gateway metadata to match the server's configured issuer. Run with: OPENSHELL_E2E_OIDC=1 OPENSHELL_E2E_OIDC_SCOPES=1 \ PYTHONPATH=python uv run pytest e2e/python/oidc/ -v * fix(docs): fix markdown lint errors in OIDC architecture docs Add blank lines before lists and fenced code blocks to satisfy markdownlint MD031 and MD032 rules. |
||
|
|
dafb7996af | fix(docker): add openshell-prover to Dockerfile skeleton stages and provide z3 (#800) | ||
|
|
7c314e7405 |
feat(prover): add native Rust policy prover with Z3 solver (#741)
* feat(prover): add native Rust policy prover with Z3 solver Add openshell-prover crate implementing formal policy verification using Z3 SMT solving. Answers two questions about any sandbox policy: "Can data leave?" and "Can the agent write despite read-only intent?" Native Rust — no Python subprocess, no PYTHONPATH, no uv dependency. Z3 bundled via z3-sys for self-contained builds. Replaces the Python prototype from #703. Closes #699 Signed-off-by: Alexander Watson <zredlined@gmail.com> * fix(prover): skip L7-write in exfil, write bypass only on read-only intent Port two fixes from the Python branch: - Exfil query skips endpoints where L7 is enforced and working - Write bypass only fires on explicit read-only intent, not L4-only Signed-off-by: Alexander Watson <zredlined@gmail.com> * chore(prover): add missing SPDX license headers to registry and testdata YAML files * fix(prover): revert serde_yaml to serde_yml to match workspace dependency on main * fix(prover): apply cargo fmt formatting to prover and cli source files * ci: add clang and libclang-dev to CI image for z3-sys bindgen z3-sys requires libclang at build time for bindgen to generate FFI bindings. Without it, the Rust CI jobs fail on the prover crate. * fix(prover): use system libz3 instead of compiling from source Add libz3-dev to the CI image and drop the z3 `bundled` feature from the workspace dependency. This eliminates the ~30 min z3 C++ build that ran on every CI cache miss. sccache only wraps rustc — it does not intercept the cmake C++ build that z3-sys runs when `bundled` is enabled. The cargo target cache helped on warm runs but evicts on toolchain bumps and fork PRs. With the system library pre-installed in the CI image, z3 link time is always zero. A `bundled-z3` opt-in feature is added to the prover crate for local development without system z3: cargo build -p openshell-prover --features bundled-z3 Regular local dev: brew install z3 (macOS) or apt install libz3-dev (Linux), then cargo build just works. Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor * fix(prover): use u16 for ports, align include_workdir default with runtime Port fields changed from u32 to u16 across all prover types (policy, model, finding, queries). Prevents the prover from silently accepting port values >65535 that the runtime rejects, which would produce misleading PASS results on invalid policies. Change include_workdir serde default from true to false to match the runtime (openshell-policy uses #[serde(default)] which gives false). The previous mismatch caused the prover to model a /sandbox path that does not exist at runtime, producing false analysis. Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor * refactor(prover): embed registry at compile time, replace hand-rolled glob Embed binary and API capability registry YAML files into the binary at compile time using include_dir!. The previous approach used env!("CARGO_MANIFEST_DIR") which bakes in the build machine's source path — works in tests but breaks for installed binaries. The CWD fallback was equally fragile. The --registry CLI flag still works as a filesystem override for custom registries. Credentials remain filesystem-loaded (user-supplied data). Replace the hand-rolled glob matching (~60 lines) with the glob crate's Pattern::matches(), which is already a transitive dependency. Remove unused dependencies: openshell-policy, openshell-core, thiserror, tracing. The prover parses policy YAML directly into its own types and does not use the policy or core crate APIs. Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor * docs: add Z3 system library to prerequisites The prover crate now links against system libz3 instead of compiling from source. Document the install steps for macOS, Ubuntu, and Fedora, and note the bundled-z3 feature flag as a fallback. Signed-off-by: Alexander Watson <zredlined@gmail.com> Made-with: Cursor * fix(prover): apply cargo fmt formatting Made-with: Cursor --------- Signed-off-by: Alexander Watson <zredlined@gmail.com> |
||
|
|
a831a8921b |
feat(settings): gateway-to-sandbox runtime settings channel (#474)
* feat(gateway/sandbox): add global and sandbox runtime settings flow |
||
|
|
fbd93a4632 | refactor: rename navigator- crate prefix to openshell- (#277) |