105 Commits
Author SHA1 Message Date
krishicks 8328412959 feat(vm): export driver traces over OTLP (#2564)
Continue distributed traces across the gateway-to-driver process boundary and
export VM driver spans to the same OTLP/gRPC collector. The driver reports as
the distinct openshell-driver-vm service.

Updated the gateway architecture and configuration reference with a generic
external-driver forwarding contract.

Instrumented:

- Every RemoteComputeDriver RPC injects the active W3C trace context into
  tonic metadata. Managed VM readiness and runtime initialization give startup
  capability probes stable parent operations rather than isolated root spans.
- A tonic service layer creates fixed, low-cardinality server spans for every
  ComputeDriver RPC. New handlers inherit tracing automatically; failures record
  OpenTelemetry error status and the gRPC status code.
- Background provisioning remains attached to CreateSandbox after the RPC
  returns without extending the RPC span lifetime.
- Provisioning records image preparation, bootstrap image resolution, overlay
  preparation, lifecycle configuration, pre-launch hooks, guest preparation,
  and launcher spawn as child spans.
- VM startup reconciliation roots one trace for the persisted-sandbox scan,
  with per-sandbox restore and provision operations beneath it. The root remains
  open until all spawned restore tasks finish.
- Delete cleanup records its own child operation.

Design notes:

- The gateway forwards its configured OTLP endpoint to managed external
  drivers. SDK `OTEL_*` variables continue to own sampling, batching, limits,
  headers, and transport tuning.
- The VM driver has its own tracer provider and service resource so trace
  backends preserve the service boundary.
- RPC operation names come from an explicit method mapping, keeping cardinality
  bounded without parsing the protobuf descriptor set at runtime.
- Propagation uses a remote SpanContext for spawned provisioning. This keeps
  one trace while allowing the CreateSandbox server span to finish when the
  RPC response is sent.
- Startup restoration is independent of gateway requests. It begins at the VM
  driver reconciliation span rather than attaching to an unrelated RPC.
- Existing tracing events remain on the logging path. The OpenTelemetry layer
  exports spans only and excludes the SDK exporter callsites to avoid recursive
  traces.
- Export configuration failures do not prevent the driver from serving, and
  buffered spans are drained during graceful shutdown.
- Trace fields identify drivers, sandboxes, images, lifecycle phases, and gRPC
  outcomes without recording credentials, sandbox tokens, or request query
  parameters.

Refs #2507

Signed-off-by: Kris Hicks <khicks@nvidia.com>
2026-08-03 20:52:22 +00:00
John T. MyersandJohn Myers 905b554c7c refactor(network): consolidate proxy egress pipeline (#2373)
* refactor(network): introduce shared egress pipeline

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): cover shared proxy egress paths

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* refactor(network): make destination authorization explicit

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* refactor(network): pin proxy relay policy context

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): lock relay generation contracts

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): establish phase zero compatibility baseline

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* feat(policy): detect ambiguous network endpoints

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* feat(sandbox): fail closed on invalid policy updates

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* refactor(network): invalidate relays on policy changes

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* docs(policy): document validation failure posture

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): cover validation and middleware egress

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(config): move policy failure mode to gateway toml

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): name proxy contracts by behavior

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(network): align overlap validation with endpoint selection

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): expect hard loopback denial

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): match declared endpoint denial

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(policy): preserve path-specific endpoint overrides

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(network): respect hard-blocked host gateways

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(network): reconcile proxy refactor with main

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(network): preserve CONNECT policy generation

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(policy): cover runtime endpoint glob semantics

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(server): reject ambiguous policies before persistence

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* docs(policy): explain ambiguity preflight behavior

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(policy): compare body limits within protocol

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(proxy): avoid global tracing capture race

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>

* chore(server): format rebased provider tests

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* test(server): authenticate rebased policy requests

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(sandbox): retain runtime on middleware outage

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(server): preflight provider composition activation

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

* fix(sandbox): distinguish runtime failure transitions

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-07-31 20:59:00 +00:00
Evan LezarandDrew Newberry d220d89468 feat(compute): negotiate gateway callback listeners (#2492)
* feat(compute): query gateway listener requirements

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* feat(compute): add Podman listener requirements

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(docker): use default gateway bind address

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(gateway): avoid wildcard primary listener

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(podman): validate callback listener discovery

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(server): support split dual-stack listeners

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(podman): support legacy rootless listener discovery

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(e2e): accept loopback plaintext rejection

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(agent): add callback listener diagnostics

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(server): restrict compute callback listeners

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(podman): validate local callback port

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(server): clarify callback listener contract

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(podman): require pasta for local callbacks

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(gateway): document RPM listener default

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(server): keep listener provenance diagnostic-only

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(compute): preserve callback listener isolation

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* test(e2e): remove Podman callback relay

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(packaging): preserve Podman callback loopback

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* ci(e2e): run VM smoke on nested-virt runner

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* ci(e2e): gate VM smoke on usable KVM

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* ci(e2e): probe KVM through VM driver

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* ci(e2e): tolerate hosted KVM denial

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* test(server): close traced futures before assertions

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* revert: remove tracing test stabilization

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
Co-authored-by: Drew Newberry <anewberry@nvidia.com>
2026-07-31 16:41:06 +00:00
krishicks fa24299092 feat(gateway): export traces over OTLP (#2534)
Add an opt-in OTLP/gRPC trace exporter to the gateway. Export is enabled
by the presence of an `[openshell.gateway.otlp]` table with an endpoint;
there is no separate toggle.

Instrumented:

- Inbound request server spans, named for the RPC (`$service/$method`) or
  `{method} {path}` for plain HTTP. They continue valid W3C `traceparent`
  context when present and start a new trace otherwise. gRPC spans also
  carry `rpc.system`, `rpc.service`, `rpc.method`, and trailer-derived
  `rpc.grpc.status_code`.
- Compute driver calls (create, delete, list, get, validate, watch) as
  client spans anchored on the `ComputeDriver` contract.
- Store reads and writes as children of the current request or loop span.
- Work with no inbound request: compute driver initialization, the sandbox
  reconcile sweep, provider credential refresh tick, and driver watch events.
  Each roots one operation trace so its child work does not arrive as anonymous
  single-span traces.

This is deliberately not exhaustive. Auth, policy evaluation, and
middleware remain uninstrumented, as do store lifecycle calls (`ping`,
`close`) that a readiness poll would turn into a span per tick. The aim is
a useful trace tree at a reviewable size; coverage can grow against real
traces.

Design notes:

- The TOML table owns whether and where to export. The SDK `OTEL_*`
  variables own how; sampling, batching, and limits are not mirrored into
  gateway config.
- The OpenTelemetry layer exports spans only. Existing `tracing` events
  remain on the stdout and sandbox-log paths and are not copied into trace
  payloads.
- Telemetry never blocks the gateway. A malformed endpoint logs an error
  and disables export rather than failing startup, and buffered spans are
  drained during graceful shutdown.
- Failed spans carry error status without a separate `error.type` attribute.
  Request spans use HTTP status and gRPC response trailers; driver spans use
  the returned gRPC status; autonomous loop spans record failed results
  explicitly. Store spans exempt `UniqueViolation` and `Conflict`, because
  those errors report expected contention such as a held lease or an
  optimistic-concurrency retry.
- The compute driver is reachable only through `TracedDriver::call`, so a
  call cannot skip its span. This is the client half of a client/server pair
  and the single place to inject context if drivers move out of process.
- Tests share one process-wide subscriber and in-memory exporter because
  `tracing` caches callsite interest globally.

Inbound W3C trace context is propagated into gateway request spans. Context
is not yet injected into outbound driver calls, so a future out-of-process
driver would still need propagation at the `TracedDriver` seam.

The Helm chart is intentionally unchanged, so OTLP export cannot yet be
enabled on a chart-deployed gateway.

Refs #2507

Signed-off-by: Kris Hicks <khicks@nvidia.com>
2026-07-30 20:29:42 +00:00
Derek Carr 9c019a93f5 Wire authorization into workspace model (#2445)
* feat(auth): implement RFC 0011 Phase 2 workspace authorization

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): address PR review feedback on workspace authorization

- Docker e2e: add --health-port and switch readiness probe from
  `openshell status` to `curl /healthz`, fixing a false-positive
  readiness check in OIDC mode where the CLI exited 0 without
  actually contacting the gateway

- ListWorkspaces: move membership filtering from post-query N+1
  lookups into a SQL EXISTS subquery so pagination applies to the
  visible set, not the global ordering. Add generic
  list_with_membership to the persistence layer.

- Descriptor validator: reject role/scope fields on unauthenticated
  and sandbox auth modes, and allow-list workspace_role as
  user/admin and global_role as platform_admin to catch typos at
  startup

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(server): use authed request in delete telemetry test

The workspace authorization added by the Phase 2 auth changes requires
a Principal on every delete request. The delete-telemetry test was still
using a bare Request::new, so extract_principal failed before the handler
could acquire the delete gate, causing a 5-second timeout flake.

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): address gator review findings for workspace authorization

- Inject unauthenticated-local-dev principal in no-auth gateway mode so
  handlers that call extract_principal() always find one.
- Cap label-selector membership query at MAX_PAGE_SIZE instead of
  u32::MAX to bound the in-memory read.
- Authorize workspace membership before resolving workspace existence in
  all sandbox RPCs to prevent workspace-name enumeration by non-members.
- Remove dead_code allow on AuthorizedWorkspace.workspace now that
  callers use the normalized name from the authz result.

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): close workspace-name oracle and label-selector truncation

Swap authorize-before-resolve ordering in 27 handlers across
provider.rs, service.rs, policy.rs, and workspace.rs to prevent
CWE-203 workspace-name enumeration by non-members.

Add combined membership+label SQL query (list_with_membership_and_selector)
to both persistence backends so ListWorkspaces with label selectors no
longer silently drops results beyond the first page of membership matches.

Signed-off-by: Derek Carr <decarr@redhat.com>

* test(auth): add non-member rejection and membership+label persistence tests

Add comprehensive test coverage for workspace authorization changes:
- Non-member rejection tests across all 44 workspace-scoped handlers
  (sandbox, provider, service, policy, workspace, inference) verifying
  PERMISSION_DENIED is returned instead of NOT_FOUND to prevent
  CWE-203 workspace-name oracle
- Persistence test for list_with_membership_and_selector verifying
  SQL-level membership EXISTS + label filtering, multiple predicates,
  no-match cases, and pagination

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): format merged import line in sandbox tests

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): address gator re-review findings on workspace authorization

- Fix TUI unconditionally setting providers_v2_enabled after provider
  refresh; read the actual gateway setting via GetGatewayConfig at
  startup instead
- Fix SQLite json_extract with dotted label keys (e.g. example.com/env)
  by quoting the key in the JSON path
- Add authed_request wrappers to upstream OCI identity tests that were
  missing a principal after rebase
- Add test proving GetGatewayConfig is accessible without Platform Admin
- Add test for dotted/prefixed Kubernetes-style label key filtering

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): address second gator re-review findings

- Loosen GetGatewayConfig from platform_admin to scope-only so workspace
  users can discover providers_v2_enabled during sandbox creation with
  inferred-provider commands; update proto descriptor, descriptor
  validation, and RFC 0011 access table
- Add validate_label_selector to handle_list_workspaces and escape
  single quotes in SQLite json_extract interpolation (CWE-89
  defense-in-depth)
- Re-fetch providers_v2_enabled after TUI gateway switch so the new
  gateway's capability is reflected
- Add e2e test for workspace user with inferred-provider command
- Add persistence test for adversarial label keys with SQL injection
  attempts
- Add handler test for invalid label selector rejection in
  ListWorkspaces

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): address third gator review findings

- Cap label selector pairs at 64 (CWE-400) to bound SQLite dynamic SQL
- Add SCOPE_ONLY_METHODS allowlist for scope-without-role RPCs (CWE-863)
- Normalize ID-based data-plane handlers to return NOT_FOUND for
  unauthorized sandboxes, closing the cross-workspace oracle (CWE-203)
- Fix TUI provider profile cache lookup key mismatch for legacy
  providers with empty profile_workspace
- Add whoami to CLI skill reference command tree
- Update TUI skill doc with workspace, provider, and settings coverage
- Document scope/workspace orthogonality on GetGatewayConfig proto

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): extend CWE-203 normalization to policy.rs sandbox handlers

GetSandboxConfig and GetSandboxLogs in policy.rs had the same
fetch-before-authorize pattern that leaked cross-workspace sandbox
existence. Promote fetch_and_authorize_sandbox to pub(super) and
use it from both sandbox.rs and policy.rs handlers.

Signed-off-by: Derek Carr <decarr@redhat.com>

* test(auth): update assertions for CWE-203 sandbox ID normalization

Cross-workspace sandbox access via ID-based handlers now returns
NOT_FOUND instead of PERMISSION_DENIED to prevent existence inference.
Update the unit test and OIDC e2e assertion to match.

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(auth): narrow CWE-203 error mapping and correct whoami output formats

Only remap PERMISSION_DENIED to NOT_FOUND in fetch_and_authorize_sandbox
and RevokeSshSession, letting INTERNAL and UNAUTHENTICATED propagate
as-is. Fix whoami --output format values in cli-reference.md to match
the actual CLI (table/json/yaml, not text/json).

Signed-off-by: Derek Carr <decarr@redhat.com>

* fix(ci): share network namespace with Keycloak in containerized CI

In GitHub Actions job containers, Docker port publishing lands on the
host, not inside the job container. Detect this environment and attach
Keycloak to the job container's network namespace instead, with
hardened defaults (cap-drop ALL, no-new-privileges, loopback-only
listener).

Signed-off-by: Derek Carr <decarr@redhat.com>

---------

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-07-30 00:31:32 +00:00
Matthew Grossman bc14018cad feat(sandbox): use policy-first OCI image identity (#2509)
* feat(sandbox): use policy-first OCI image identity

Closes #2331

Preserve per-field policy omission, derive Docker and Podman fallbacks from the inspected immutable image, and resolve the final numeric identity before starting agent children.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(sandbox): preserve declared process identities

Keep explicit policy values and OCI-declared names intact, defer passwd lookup until a primary GID is required, and refresh stale policy examples.

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(supervisor): reuse resolved OCI identity

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(supervisor): allow Linux pre-exec arguments

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(kubernetes): protect resolved sandbox identity

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(sandbox): prepare workspace for OCI identity

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* refactor(sandbox): own only workspace root

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(sandbox): harden partial identity drops

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* test(sandbox): scope OCI image e2e to Docker

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(sandbox): narrow OCI identity fallback scope

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* test(podman): cover OCI identity launch

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

* fix(podman): exercise OCI fallback in E2E

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>

---------

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
2026-07-29 05:27:21 +00:00
LR90 7955c8309b feat(k8s): support configuring workspace PVC storageClassName (#2463)
The Kubernetes driver's default workspace PVC never set storageClassName,
so on clusters with no default StorageClass the PVC stayed Pending and
sandbox creation failed.

Add a workspace_storage_class option to KubernetesComputeConfig, wired
through SandboxPodParams into the generated volumeClaimTemplates. When
non-empty it sets storageClassName; empty preserves the current behavior
of relying on the cluster default StorageClass.

Expose it via the OPENSHELL_K8S_WORKSPACE_STORAGE_CLASS env var on both
the standalone driver and the embedded gateway runtime defaults, and via
the server.workspaceStorageClass Helm value.

Closes #2442

Signed-off-by: lr90 <qiuweimin@matrixorigin.cn>
2026-07-29 03:35:00 +00:00
Evan Lezar f00ad23a26 fix(podman): tolerate shutdown transport closes (#2498)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-28 08:00:37 +00:00
Grace Smith deced8716b refactor(policy): extract shared L7 endpoint validation (#2389)
Move L7 endpoint semantic checks into the openshell-policy crate so both
profile lint and the runtime validator share one implementation. This
eliminates drift between the two validation paths.

The shared validator covers 9 checks: unknown protocol, rules/access
mutual exclusivity, JSON-RPC family access rejection, json-rpc requires
rules, non-JSON-RPC protocol requires rules or access, MCP requires
rules when allow_all is false, rules-would-deny-all detection,
deny_rules require protocol, and deny_rules require base allow set.

Changes rules/deny_rules fields to Option<Vec<...>> so absent vs empty
is distinguishable at lint time. Adds is_effectively_empty() to
L7AllowProfile for deny-all detection of allow: {} objects. Makes
rules_would_deny_all MCP-aware by checking tool/params.name selectors
before classifying a rule as deny-all. Adds params field to
L7AllowProfile so MCP tool selectors survive proto round-trip.

Signed-off-by: Grace Smith <gsmith@redhat.com>
Signed-off-by: Grace Smith <grasmith@redhat.com>
2026-07-24 17:48:24 +00:00
Philippe Martin 77e5c32217 feat(sandbox,gateway): route sandbox egress through corporate HTTP proxy (#2245)
* feat(sandbox,gateway): route sandbox egress through corporate HTTP proxy

- Chain sandbox egress through a corporate HTTP proxy so outbound
  traffic from within the sandbox respects the host proxy settings
- Forward sandbox proxy environment variables to the generated Podman
  config so the proxy is applied consistently to Podman-managed workloads

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): make corporate proxy routing operator-owned

The operator-configured corporate egress proxy was injected under the
conventional HTTPS_PROXY/HTTP_PROXY/NO_PROXY names as defaults beneath
sandbox spec/template environment, so a sandbox creator could redirect
egress at an arbitrary proxy or disable proxying with NO_PROXY=*.

Route the boundary through reserved, supervisor-only variables
(OPENSHELL_UPSTREAM_HTTPS_PROXY/HTTP_PROXY/NO_PROXY) written in the
Podman driver's required-variable tier. Any sandbox-supplied value under
a reserved name is stripped before the operator value is applied, so the
supervisor never observes a reserved proxy variable the operator did not
set. The supervisor now reads only the reserved names and ignores the
conventional proxy variables the sandbox controls.

Add the reserved proxy variables to the supervisor-only child-environment
denylist so the corporate proxy URL and any embedded credentials are not
inherited by the sandbox workload, which reaches egress through the local
policy proxy and never needs them.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* feat(sandbox,podman): deliver corporate proxy credentials via secret file

Proxy credentials were embedded inline in the proxy URL, so they were
stored in gateway.toml and exposed in container metadata via
'podman inspect'.

Reject inline 'user:pass@' credentials in https_proxy/http_proxy at
startup (parsed with the url crate rather than hand-splitting), and add a
proxy_auth_file option pointing at a 'user:pass' file. The driver stages
that file as a per-sandbox root-only Podman secret, mounts it at a fixed
path, and exports only the path in the reserved
OPENSHELL_UPSTREAM_PROXY_AUTH_FILE variable, so the credential never
appears in config, environment, or container metadata. Reading the file
fails closed on a missing, empty, or control-character-bearing value.

The supervisor reads the credential from the mounted file and builds the
Proxy-Authorization: Basic header, rejecting control characters, and no
longer derives credentials from URL userinfo. The auth-file path is added
to the child-environment strip list, and the generated gateway.toml is
written owner-only (mode 0600).

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): fail closed on invalid upstream proxy configuration

The reserved OPENSHELL_UPSTREAM_* variables are an operator-owned egress
boundary, but the supervisor treated present-but-invalid values as unset:
an unsupported or malformed proxy URL was ignored with a warning, an
unreadable auth file proceeded without credentials, and a malformed
credential silently became unauthenticated. Any of these could quietly
downgrade the corporate proxy boundary to direct dialing or
unauthenticated proxy access.

Make every configured-but-invalid proxy or auth setting fatal to
supervisor proxy startup, emit an OCSF ConfigStateChange failure event
before refusing, and share URL validation semantics between the Podman
driver and the supervisor through a single validator in
openshell-core (parse_upstream_proxy_url), so a value accepted at
sandbox-create time can never be rejected in-container or vice versa.

Inline user:pass@ URL credentials are now fatal in the supervisor too
(previously warn-and-strip), matching the driver. Unset or empty
variables still mean no proxy; only present-but-invalid values fail.
Error paths never include credential content.

Addresses the fail-closed review item on #2245.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): finish the fail-closed upstream proxy credential contract

The upstream proxy URL already had a single shared validator, but the
credential did not: the Podman driver rejected only CR/LF/NUL while the
supervisor rejected every control character, so a credential accepted at
sandbox-create time (e.g. one containing a tab) could still be rejected
in-container. Present-but-whitespace reserved OPENSHELL_UPSTREAM_*
values were also silently treated as unset, quietly downgrading the
operator's egress boundary to direct dialing.

Add parse_upstream_proxy_credential to openshell-core as the single
source of truth for the documented user:pass credential form (non-empty
user, no control characters, trimmed) and use it in both the Podman
driver's secret staging and the supervisor's Proxy-Authorization header
construction. Error variants carry no payload so credential content can
never leak into messages.

Make a present-but-empty reserved variable fatal to supervisor proxy
startup instead of meaning "unset"; only fully unset variables disable
the proxy. The driver correspondingly rejects an empty no_proxy at
config time so it can never inject a value the supervisor refuses.

Addresses the remaining fail-closed credential/config review item on

Signed-off-by: Philippe Martin <phmartin@redhat.com>
#2245.

* fix(sandbox,podman): close remaining fail-open upstream proxy config paths

Two configuration paths could still silently run without the proxy
boundary the operator believed was in effect.

A no_proxy bypass list configured without any https_proxy/http_proxy
was accepted by both the driver and the supervisor and simply meant
"dial everything directly". Reject it on both sides, exactly like the
existing proxy_auth_file-without-proxy rule: an operator who wrote a
bypass list assumed proxying was active, so accepting it hides a
fail-open state.

The gateway.sh dev script guarded proxy settings with [[ -n "${VAR:-}"
]], which conflates unset with explicitly-empty and dropped the latter
before the gateway's validation could see it. Use ${VAR+x} instead so
a set-but-empty variable is written into gateway.toml and rejected at
startup by validate_proxy_config rather than silently discarded.

Addresses the remaining fail-open configuration review item on #2245.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): reject upstream proxy URLs with path, query, or fragment

parse_upstream_proxy_url accepted URLs like http://proxy.corp.com:8080/some/path
and silently discarded everything after host:port, a lenience inherited
from the original supervisor parser. A forward proxy is addressed by
host:port only, so extra components indicate a misconfiguration (for
example a pasted endpoint URL) and silently truncating them violates
the present-but-invalid-is-fatal contract enforced everywhere else in
this configuration surface.

Reject a path, query, or fragment in the shared validator with a new
UnexpectedComponent error. A bare trailing slash remains accepted
because the url crate normalizes an absent http path to "/", making the
two indistinguishable. Both the Podman driver (gateway startup) and the
supervisor (sandbox startup) inherit the rule through the shared
parser, keeping their semantics identical by construction.

Addresses the proxy URL component review item on #2245.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): remove plain-HTTP upstream proxy support

The http_proxy path tunneled plain-HTTP requests through the corporate
proxy with CONNECT to port 80 and then sent origin-form requests down
the tunnel. Conventional enterprise forward proxies expect plain HTTP
as absolute-form requests sent directly over the proxy connection, and
commonly refuse CONNECT to port 80, so the setting looked supported but
failed against typical deployments. Tunneling also blinds the proxy to
the one protocol it could inspect.

Narrow the feature to TLS (CONNECT) egress only, which is the
conventional and already-correct case: plain-HTTP requests now always
dial the destination directly, and only client CONNECT tunnels chain
through the corporate proxy. Remove the http_proxy config field, the
--sandbox-http-proxy / OPENSHELL_SANDBOX_HTTP_PROXY driver surface, the
reserved OPENSHELL_UPSTREAM_HTTP_PROXY variable, and the UpstreamScheme
plumbing. The feature never shipped, so this is a clean removal; a
stray http_proxy key in gateway.toml still fails loudly through the
config's deny_unknown_fields.

Removing the plain-HTTP proxy branch also removes its host-gateway
special case; the architecture doc now documents the real host-gateway
behavior (add driver-injected host aliases to the reserved NO_PROXY
list) instead of an invariant the HTTPS path never implemented.

Plain-HTTP forwarding through a corporate proxy can return later as
absolute-form forwarding behind its own design review.

Addresses the plain-HTTP forwarding review item on #2245.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): escape generated TOML and require explicit proxy URL form

Escape backslashes, quotes, and control characters when gateway.sh writes
proxy values into gateway.toml, so a hostile or unusual environment value
cannot corrupt the config or inject extra keys.

Restrict the upstream proxy URL grammar to the documented http://host:port
form: a scheme-less value is no longer normalized to http:// and a missing
port is no longer silently defaulted to 80. Docs, README, and CLI help now
state the explicit-form requirement consistently.

Also fix a test-only call of handle_tcp_connection that was missing the
upstream_proxy argument added in an earlier commit.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): preserve tunneled bytes read with the CONNECT response

The CONNECT handshake reads the corporate proxy's response in chunks, so
the read that completes the header block can also contain the first
tunneled payload bytes. Those bytes were discarded, silently corrupting
the start of the tunnel for server-speaks-first destinations or proxies
that coalesce writes.

connect_via now returns a PrefixedStream that replays any bytes received
past the response terminator before reading from the socket again; writes
pass through unchanged. Direct dials wrap the stream with an empty prefix
so downstream relay and TLS paths keep a single stream type, and
tls_connect_upstream is generalized to any AsyncRead + AsyncWrite stream.

Adds regression coverage for a combined response/payload read and for
prefix replay ordering.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): gate cleartext proxy Basic auth behind an explicit opt-in

Proxy-Authorization: Basic is base64 over the plain-TCP connection to the
http:// corporate proxy, so anyone on the network path between the sandbox
host and the proxy can recover the credential. Sending it is now an
explicit operator decision instead of an implicit side effect of
configuring proxy_auth_file.

Add a proxy_auth_allow_insecure driver setting (CLI
--sandbox-proxy-auth-allow-insecure, env
OPENSHELL_SANDBOX_PROXY_AUTH_ALLOW_INSECURE), delivered to the supervisor
as the reserved OPENSHELL_UPSTREAM_PROXY_AUTH_ALLOW_INSECURE variable.
Fail-closed pairing on both sides: an auth file without the
acknowledgement is rejected at gateway startup and at supervisor startup,
as is the acknowledgement without an auth file or any value other than
'true'. gateway.sh writes the key only as a TOML boolean; a non-boolean
value is emitted as a quoted string so the gateway rejects it at startup
instead of risking injection.

Documents the exposure prominently in the gateway config reference, the
driver README, and the sandbox architecture doc.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): honor port qualifiers and resolved addresses in NO_PROXY

Two divergences from the documented NO_PROXY contract:

- A port-qualified entry (internal.corp:8443) was silently stripped to its
  hostname and bypassed the proxy for every port, excluding traffic the
  operator never listed. Entries now keep the optional :port qualifier
  (also on IP and CIDR entries) and only apply to that destination port; a
  trailing qualifier that is not a valid port stays part of the pattern
  instead of widening the entry.

- IP and CIDR entries only matched IP-literal hosts, so a bypass like
  10.0.0.0/8 never applied to hostnames resolving into that range. NO_PROXY
  evaluation now sees the validated resolved addresses: an IP/CIDR entry
  matching through resolution authorizes a direct dial of only the
  addresses it contains, so a bypass scoped to an internal range cannot
  widen into a direct dial of addresses outside it. Hostname-level matches
  (loopback, wildcard, domain entries, IP-literal hosts) keep authorizing
  all validated addresses.

proxy_for is replaced by decision(host, port, resolved) returning either
the proxy endpoint or the permitted direct-dial subset, and dial_upstream
restricts the direct connect to that subset.

Adds regression coverage for port-scoped bypasses on domain, IP, and CIDR
entries, invalid port qualifiers, resolved-address matching, and
split-resolution subset dialing.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): bind proxied CONNECT tunnels to validated addresses

The CONNECT request sent to the corporate proxy carried the destination
hostname, so the proxy resolved the name itself and the addresses that had
passed SSRF and allowed_ips validation were discarded. Split-horizon DNS
or rebinding at the proxy could then reach internal or otherwise
unapproved destinations through a tunnel the supervisor logged as
validated, and IP-range policy could not be enforced at all on proxied
dials.

CONNECT now targets a validated resolved address by default: the proxy
performs no DNS resolution and the tunnel stays bound to the answer the
supervisor checked. The hostname still travels inside the tunnel (TLS SNI,
application Host), so destination servers behave normally. In
split-horizon networks, operators point the gateway host at the corporate
resolver so internal names validate to their internal addresses.

For proxies whose ACLs filter on hostnames and reject IP CONNECT targets,
a new proxy_connect_by_hostname opt-in (CLI
--sandbox-proxy-connect-by-hostname, env
OPENSHELL_SANDBOX_PROXY_CONNECT_BY_HOSTNAME, reserved
OPENSHELL_UPSTREAM_PROXY_CONNECT_BY_HOSTNAME) restores hostname CONNECT,
documented as re-opening proxy-side resolution and making the proxy's ACLs
the effective egress control. Fail-closed pairing on both sides: the
opt-in without a proxy, or any value other than 'true', is fatal.

Adds regression coverage for the IP CONNECT request line (including IPv6
bracketing and hostname non-leakage), the hostname opt-in, and the
config pairing rules in driver and supervisor.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): reject empty port after bracketed IPv6 proxy host

http://[fd00::1]: passed the explicit-port check because the bracketed
branch only tested for a colon after the bracket, then fell back to port
80 — violating the fail-closed http://host:port contract and potentially
sending configured Basic credentials to an unintended service. Require a
non-empty suffix after ]:, matching the unbracketed branch, and cover the
case in the shared parser tests.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): fall back across validated addresses in proxied CONNECT

The direct path hands TcpStream::connect the whole validated address list
and it falls back across them, but the validated-IP CONNECT path attempted
only the first address, so a dual-stack destination could fail through the
corporate proxy even when a later validated address was reachable.

connect_via_validated tries each validated address in order under one
aggregate CONNECT_HANDSHAKE_TIMEOUT budget, returning the first success;
when every attempt fails the error names the attempt count and carries the
last failure. An empty address list is rejected up front.

Adds regressions for first-fails/second-succeeds fallback (asserting both
CONNECT request lines), the aggregate all-addresses failure message, and
the empty-list rejection.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): strip new reserved proxy vars and complete docs/tests

Follow-ups from review:

- Add OPENSHELL_UPSTREAM_PROXY_AUTH_ALLOW_INSECURE and
  OPENSHELL_UPSTREAM_PROXY_CONNECT_BY_HOSTNAME to the supervisor-only
  strip list so workload child processes never inherit them, matching the
  documented contract for the other reserved proxy variables, and cover
  both in the supervisor-only variable test.
- Document all five OPENSHELL_SANDBOX_* proxy variables in the
  mise run gateway help text, marked Podman-only and stating the
  auth-file/acknowledgement pairing, and complete the gateway-key list in
  the Podman README.
- Add NO_PROXY composition coverage for bracketed IPv6 entries with port
  qualifiers, bare IPv6 entries, and IPv6 CIDR matching against an
  IPv6-literal host and against a hostname's resolved addresses,
  including the port-qualified CIDR form.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): cap each proxied CONNECT attempt within the shared budget

A proxy that accepted the first CONNECT request but never responded
consumed the entire aggregate handshake timeout, so later validated
addresses were never tried and the hang defeated the multi-address
fallback.

Each attempt is now time-boxed to its fair share of the time remaining
before the shared deadline (remaining / attempts_left): a hanging attempt
is cut off with enough budget left for every remaining address, while
time a fast failure does not use rolls over to later attempts and the
total never exceeds CONNECT_HANDSHAKE_TIMEOUT. A timed-out attempt is
recorded like any other failure, and the aggregate error distinguishes
all-attempted from budget-exhausted runs.

Adds a first-hangs/second-succeeds regression driven through a
test-visible budget parameter so it runs in about a second instead of a
real 30s window.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): deliver corporate proxy config on the supervisor argv

The proxy settings were injected as reserved OPENSHELL_UPSTREAM_* container
environment variables. The driver only wrote names the operator configured,
but container runtimes layer the spec environment over ENV values baked
into the sandbox image, so an image could supply NO_PROXY=*, enable
hostname CONNECT, or point an unconfigured deployment at an
attacker-controlled proxy whenever the operator left a field unset.

The settings now travel as supervisor command-line arguments
(--upstream-proxy, --upstream-no-proxy, --upstream-proxy-auth-file,
--upstream-proxy-auth-allow-insecure,
--upstream-proxy-connect-by-hostname) built by the driver from operator
config. The driver sets the container entrypoint and command explicitly,
so neither sandbox spec/template environment nor image ENV can influence
argv, and an omitted flag genuinely means unconfigured — in every
supervisor topology, since the supervisor no longer consults its
environment for these settings at all. Credentials stay on the root-only
secret mount; only the mount path appears on argv.

The reserved environment names, their strip-list entries, and the
env-based validation surface are removed. UpstreamProxyConfig::from_args
replaces from_env, reusing the same shared fail-closed validation and
pairing rules keyed by the CLI flag names.

Driver tests now assert the argv contract, including that sandbox-supplied
environment cannot add, remove, or redirect proxy flags; supervisor tests
cover from_args mapping and its pairing rules.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* docs(sandbox): align proxy comments with the argv transport

The argv migration left comments describing the configuration as reserved
environment variables ("reserved value", "present-but-empty variable",
"reserved upstream proxy variables"). Rephrase them as driver-supplied
arguments and operator settings so the documented trust boundary matches
the implementation. Comment-only change.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox): parse bracketed IPv6 authorities in client CONNECT targets

parse_target split the CONNECT authority at the first colon, so an
IPv6-literal target like [2001:db8::1]:443 always failed port parsing and
IPv6-literal clients could never reach policy evaluation; a regression
test even locked in that failure. Parse the RFC 3986 bracketed form and
return the host bracket-free, matching what DNS resolution, SSRF
validation, NO_PROXY matching, and the upstream CONNECT builder expect.
Unclosed brackets, a missing or empty port after the bracket, and
non-numeric ports are rejected; unbracketed behavior is unchanged.

Replaces the failure-locking test with success coverage for bracketed
targets and adds malformed-bracket rejection cases.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* test(podman): cover proxy-auth secret cleanup across lifecycle failures

The per-sandbox proxy-auth credential secret is staged before the
container is created and removed on cleanup, but no test proved the
cleanup paths actually issue the secret removal. Add Podman-stub tests
that drive create_sandbox to a container-create failure and to a
start failure, and delete_sandbox for an out-of-band deletion, asserting
each path issues the DELETE for the per-sandbox proxy-auth secret so a
credential can never outlive the sandbox that owned it.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* docs: list corporate proxy keys in the Podman compute-driver overview

The Fern Podman driver section enumerated its gateway.toml keys but
omitted the corporate egress proxy settings. Add https_proxy, no_proxy,
proxy_auth_file, proxy_auth_allow_insecure, and proxy_connect_by_hostname
with a pointer to the gateway configuration reference for the full
contract. No navigation change: the reference folder already includes the
gateway configuration page.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* test(sandbox): cover the SSRF-to-TLS composition across the proxy tunnel

Existing tests exercised validated-IP CONNECT and the upstream-TLS helper
independently, but not the full boundary. Add an end-to-end regression
that stands up a fake corporate proxy tunneling to a fake TLS server and
drives the real path: connect_via_validated CONNECTs to the validated
address, the proxy splices the tunnel, and tls_connect_upstream verifies
the upstream certificate against the original hostname carried in SNI.

It asserts the CONNECT authority is the validated IP and never the
hostname, that verification succeeds for the matching hostname, and that a
mismatched hostname is rejected — proving a rebinding or split-horizon
substitution behind the proxy cannot pass certificate verification.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox,podman): bound proxy-auth reads, reject port 0, fix stale comment

Three review findings:

- CWE-400: the proxy-auth credential file was read with an unbounded
  read_to_string on both the driver (sandbox-create) and supervisor
  (startup) paths, so a huge file or a special file such as /dev/zero
  could exhaust memory. Add a shared bounded reader in openshell-core that
  rejects non-regular files, caps the size at 4 KiB, and reads at most that
  many bytes; the driver runs it via spawn_blocking. Covers oversized,
  special-file, and missing-path cases on both sides.

- Reject an upstream proxy URL with port 0: it passed the explicit-port
  check and startup validation but is not a connectable TCP port, so every
  proxied dial would fail later. Add a typed ZeroPort error with
  shared-validator and Podman-config tests.

- Reword a driver-config comment that still described a 'reserved
  variable' to match the argv transport.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(sandbox): open proxy-auth file non-blocking to reject FIFOs promptly

read_upstream_proxy_credential_file opened the path with a blocking
File::open before the regular-file check, so a configured FIFO with no
writer would block open() indefinitely — hanging sandbox creation on the
driver and supervisor startup. Open with O_NONBLOCK on Unix so the open
returns immediately, then reject the non-regular file as before;
O_NONBLOCK has no effect on the later read of a regular file. Adds a
mkfifo regression asserting the reader returns promptly with a
non-regular-file error instead of hanging.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* test(podman): cover corporate proxy egress across driver and supervisor

The existing corporate-proxy tests construct config structs or call CONNECT
helpers directly, so none of them detect a break in the wiring between
layers: gateway TOML deserialization, the Podman argv and secret-mount
semantics, supervisor CLI parsing, or policy denial before proxy contact.

Add a Podman e2e that drives the whole chain against a fake authenticated
forward proxy and asserts that an approved TLS request traverses it with a
validated-IP CONNECT, a policy-denied destination is refused with 403
without ever reaching the proxy, credentials arrive through the mounted
per-sandbox secret, and deleting the sandbox removes that secret.

SupportContainer is a new harness fixture. Unlike ContainerHttpServer it
probes readiness with a TCP connect rather than an HTTP GET, so it can host
a forward proxy and TLS servers, and it exposes container logs and network
IP for assertions.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* test(podman): restart the gateway on the proxy-config panic path

The panic cleanup for the temporary corporate proxy configuration
restored the gateway TOML but left the gateway process running with the
temporary configuration still loaded, which could poison later test
binaries in the same run. Nothing restarted it: the only ManagedGateway
is the short-lived one inside restart_gateway, and its Drop only calls
start, which does not reload config for an already-running gateway.

Restore and synchronously stop/start the gateway in Drop, and set
restored only after the normal restore and restart both succeed so a
failed restart no longer suppresses the fallback.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(supervisor-network): derive the loopback proxy bypass from resolved IPs

The automatic bypass treated the host string "localhost" as proof that
the destination was loopback and returned every resolved address. A
sandbox controls its own /etc/hosts and resolve_socket_addrs consults it
before DNS, so a workload could map localhost to any policy-allowed
address and dial it directly, escaping the operator proxy and the
inspection and audit boundary it exists to provide.

Check the resolved addresses instead: the name bypasses only when the
resolution is non-empty and every address is loopback. A mixed answer is
not partially honored, and an IP literal is still authoritative for
itself. A spoofed localhost falls through to the entries below, so an
explicit operator NO_PROXY entry is still honored.

This matches the trust model detect_trusted_host_gateway already applies
to the same hosts file, which validates the mapped address rather than
the alias.

Signed-off-by: Philippe Martin <phmartin@redhat.com>

* fix(podman): clean up proxy-auth secret when container already deleted

The delete_sandbox early-return path cleaned up the token secret but
skipped the proxy-auth secret, leaking it on disk. Also update tests
for recent API changes (Optional socket_path, workspace field,
list-based container lookup).

Signed-off-by: Philippe Martin <philippe@openshell.dev>
Signed-off-by: Philippe Martin <phmartin@redhat.com>

---------

Signed-off-by: Philippe Martin <phmartin@redhat.com>
Signed-off-by: Philippe Martin <philippe@openshell.dev>
2026-07-24 16:51:52 +00:00
Drew Newberry 59f7839f6b fix(auth): report gateway authentication status (#2435)
* fix(auth): report gateway authentication status

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(auth): reuse gateway info for status probe

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
2026-07-23 20:01:28 +00:00
Mesut Oezdil 744a65d52e fix(driver-podman): avoid panic when HOME is unset on macOS (#2327)
* fix(driver-podman): resolve Podman socket via auto-detection

Align Podman socket selection with the existing Docker model: explicit
config wins, otherwise probe openshell-core for a responsive socket.
This also fixes the original HOME-unset panic, since resolution no
longer hardcodes a per-OS default path.

- add detect_podman_socket() in openshell-core, mirroring
  detect_docker_socket
- PodmanComputeConfig.socket_path is now Option<PathBuf>, no default
- remove default_socket_path() (podman driver) and podman_socket_path()
  (vm driver), both replaced by the shared detector
- update server env override and CLI for the new Option type
- add tests: responsive-candidate detection in openshell-core, and
  config-error (not panic) when no socket is configured or reachable

* fix(driver-podman): address review feedback on socket resolution

Extract socket resolution into resolve_socket_path, taking the
detector as a parameter so tests do not depend on real env vars or
the host's actual Podman state. Replace the flaky env-mutating test
with three deterministic cases: explicit wins, detected is used when
absent, and neither source errors.

Fix the socket_path doc comment to describe it from a config user's
point of view, matching DockerComputeConfig's docstring. Drop a
comment that only made sense next to the Docker driver code.

Update the Podman README and gateway docs: they described a fixed
per-OS default path that no longer exists, replace with the actual
probe-then-fail behavior.

* docs(driver-podman): simplify socket default description

Previous wording was self-contradictory (says auto-detect on unset,
then lists the same var as a probed candidate) and omitted the Linux
/run/user/uid/podman/podman.sock candidate.
2026-07-21 14:01:19 +00:00
Mesut Oezdil 80987e91c8 docs: fix broken links and small inconsistencies (#2329)
- README: fix github-sandbox tutorial link missing get-started segment
- README: replace dead community-sandboxes doc link with the actual repo
- README: match supported host list to support-matrix.mdx
- architecture/README: list the missing google-vertex-ai-provider doc
- SECURITY.md: fix a mis-indented list item
- standardize on NVIDIA/OpenShell-Community casing for repo links
2026-07-20 17:46:31 +00:00
Piotr Mlocek d556748771 feat(supervisor-middleware): add network egress middleware (#2027)
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-07-16 17:47:49 -07:00
krishicks cf4deccd37 fix(gateway): probe Docker socket during driver auto-detection (#2303)
Previously, Docker was auto-detected when the CLI was installed or a candidate
Unix socket existed. Neither check verified that the Docker API was responsive.
A similar check was done when auto-detecting Podman in the past, but was
replaced in 1f07bf04 with a probe of candidate Podman sockets instead.

This change applies the functional API probing approach introduced for Podman
in 1f07bf04 to Docker. It also makes Docker driver initialization use the same
socket-selection mechanism as Docker auto-detection instead of Bollard’s local
defaults. This means the previously auto-detectable Docker socket paths
$HOME/.docker/run/docker.sock and $XDG_RUNTIME_DIR/docker.sock will actually be
usable.

When no working compute driver can be auto-detected, the gateway exits early
with a message saying as much:

> configuration error: no compute driver configured and auto-detection found no
> suitable driver; set --drivers or OPENSHELL_DRIVERS to kubernetes, podman,
> docker, or vm

This makes for a better user experience when installing OpenShell without an
available supported compute driver.

Signed-off-by: Kris Hicks <khicks@nvidia.com>
2026-07-16 10:02:24 -07:00
Drew Newberry 83003e80fc feat(interceptors): initial gateway interceptor implementation and reference example (#2005)
* feat(gateway): add descriptor-driven interceptors

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(gateway): add service-reflected interceptors

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* wip

* fix(gateway): harden interceptor evaluation

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(interceptors): label metrics and harden governance smoke

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* remove on_error: ignore

* feat(gateway-interceptors): emit log annotations

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(examples): govern provider profiles in interceptor

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway): preserve update config annotations

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(providers): support interceptor profile catalogs

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* wip

* feat(governance-interceptor): sign provider profiles

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(providers): use configured profile sources for refresh updates

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(gateway-interceptors): add phase-specific evaluation payloads

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(providers): compose provider profile sources

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway-interceptors): preserve committed responses

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway-interceptors): reject ambiguous protobuf oneofs

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway-interceptors): validate patch candidates per binding

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* refactor(gateway-interceptors): use reflected protobuf codec

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(governance-example): canonicalize signed protobuf hashes

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway): commit policy provenance atomically

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway): close signed governance bypasses

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway): isolate interceptor secrets and authority

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway): snapshot provider profiles per request

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* chore(gateway): resolve server clippy warnings

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(server): satisfy provider source clippy lint

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(server): initialize policy test annotations

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(gateway-interceptors): require explicit route allowlist

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* docs(proto): clarify update annotation semantics

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
2026-07-14 20:58:36 -07:00
mjamiv 614c8c164d feat(kubernetes): support PVC subPath driver config (#2034)
* feat(kubernetes): support PVC subPath driver config

Signed-off-by: mjamiv <michael.commack@gmail.com>

* test(kubernetes): cover writable PVC driver config

Signed-off-by: mjamiv <michael.commack@gmail.com>

* fix(kubernetes): address PVC subPath review feedback

Signed-off-by: mjamiv <michael.commack@gmail.com>

* fix(kubernetes): address PVC config review follow-up

Signed-off-by: mjamiv <michael.commack@gmail.com>

* fix(kubernetes): address PVC review follow-ups

---------

Signed-off-by: mjamiv <michael.commack@gmail.com>
2026-07-10 20:17:37 +00:00
Taylor MutchandSeth Jennings 8eacb4779f feat(kubernetes): add sidecar supervisor topology (#2076)
* feat(kubernetes): add sidecar supervisor topology

Add the Kubernetes sidecar supervisor topology, its Helm/Skaffold configuration, topology documentation, and sidecar e2e matrix coverage. Skip root-only sandbox identity rewriting when process enforcement is network-only so the low-permission sidecar process container can start successfully.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(supervisor): avoid similar process id names

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(supervisor): avoid similar process id names

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(sandbox): avoid similar proxy id names

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* docs(kubernetes): clarify sidecar topology limits

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): keep sidecar process leaf capless

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): refresh sidecar provider env snapshots

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* test(supervisor): align hot-swap identity regression

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): stage sidecar mtls files before proxy chown

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): simplify sidecar supervisor topology

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* chore(helm): reuse sidecar skaffold values

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(supervisor): avoid similar iptables helper names

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(e2e): harden kube gateway wrapper setup

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(supervisor): avoid nft batch rollback on OCP

Run nftables setup as individual commands so optional conntrack and log expressions can fail without rolling back required table, chain, and reject rules.

Signed-off-by: Seth Jennings <sjenning@redhat.com>
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): preserve process identity in sidecar topology

Render sidecar pods with a shared process namespace, keep binary-aware network policy enabled, and move Kubernetes sidecar settings under the nested sidecar config table.

Also apply unprivileged Landlock/seccomp setup in NetworkOnly supervisor mode so sidecar topology keeps sandbox child hardening without privileged process setup.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* refactor(kubernetes): replace sidecar snapshots with control socket

Coordinate sidecar policy and provider bootstrap over a local Unix socket so the process leaf no longer reads policy/provider snapshot files.

Report entrypoint startup through the control channel and keep gateway credentials confined to the network sidecar.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* feat(kubernetes): support relaxed sidecar network identity

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(sandbox): satisfy sidecar clippy lint

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* refactor(kubernetes): standardize topology naming

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(sandbox): satisfy linux clippy timeout import

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): support kata sidecar on ipv4 pods

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): satisfy linux clippy for sidecar fallback

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* chore(kubernetes): remove stale supervisor topology references

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): enable sidecar binary policy inspection

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): harden sidecar control boundary

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): couple sidecar supervisor lifecycles

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Co-authored-by: Seth Jennings <sjenning@redhat.com>
2026-07-10 13:01:39 -07:00
Taylor Mutch bebf440b25 fix(helm): propagate supervisor image overrides (#2216)
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-07-10 12:31:11 -07:00
Florent BENOIT 10702133a3 fix(core): pin supervisor image tag to gateway version for all drivers (#2070)
* fix(core): pin supervisor image tag to gateway version for all drivers

The Podman and Kubernetes drivers defaulted the supervisor image to
`:latest` via DEFAULT_SUPERVISOR_IMAGE, while the Docker driver already
resolved a version-pinned tag. Extract the tag resolution logic into
openshell-core so all three drivers use the same
OPENSHELL_IMAGE_TAG > IMAGE_TAG > CARGO_PKG_VERSION priority chain.

Closes #2068

Signed-off-by: Florent Benoit <fbenoit@redhat.com>

* refactor(core): simplify supervisor image tag resolver to slice-based API

Remove the Docker driver's wrapper functions and call
openshell_core::config::default_supervisor_image() directly.
Simplify resolve_supervisor_image_tag to accept &[&str] instead
of three separate parameters.

Signed-off-by: Florent Benoit <fbenoit@nvidia.com>
Signed-off-by: Florent Benoit <fbenoit@redhat.com>

---------

Signed-off-by: Florent Benoit <fbenoit@redhat.com>
Signed-off-by: Florent Benoit <fbenoit@nvidia.com>
2026-07-10 11:26:49 -07:00
Mesut Oezdil ed8ce8208f docs: fix Docker version format from 28.04 to 28.0 (#2136) 2026-07-08 16:57:07 -07:00
Evan Lezar 6252aa17c8 rfc-0006: add driver config passthrough proposal (#1589)
* docs(rfc): add driver config passthrough proposal

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(rfc): link driver config proposal PR

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(rfc): clarify driver config scope

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(rfc): clarify driver-local config schemas

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(rfc): clarify driver config extension path

* docs(rfc): update driver config baseline

* docs(drivers): document bind-mount selinux_label and whitespace rules

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-06 09:56:34 +02:00
Seth Jennings 6461677c32 feat(policy): accept numeric UIDs for sandbox process identity (#1973)
* feat(policy): accept numeric UIDs in sandbox process identity validation

Allow run_as_user and run_as_group to be either the literal 'sandbox'
or a numeric UID/GID within [1000, 2_000_000_000]. This removes the
hard dependency on a baked-in 'sandbox' user in container images,
enabling compute drivers to inject resolved UIDs at sandbox creation.

Phase 1 of #1959.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(supervisor): accept numeric UIDs for process identity dropping

Allow run_as_user and run_as_group to be numeric UIDs/GIDs, removing
the hard dependency on a baked-in 'sandbox' user in container images.

Changes:
- validate_sandbox_user(): accepts numeric UIDs without passwd lookup
  (logs OCSF event); keeps passwd check for "sandbox" name; rejects
  non-numeric non-sandbox strings that fail passwd lookup
- prepare_filesystem(): passes numeric UIDs/GIDs directly to chown()
  instead of requiring a passwd entry
- drop_privileges(): resolves numeric UIDs/GIDs directly via UID::from_raw
  / Gid::from_raw; skips initgroups when target uid matches current euid;
  uses guard conditions before setgid/setuid calls
- session_user_and_home(): falls back to ("{uid}", "/sandbox") for
  numeric UIDs, avoiding a passwd lookup that will fail

Re-exports MIN_SANDBOX_UID and MAX_SANDBOX_UID from openshell-policy
so callers have consistent range constants.

Phase 2 of #1959.

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(driver-kubernetes): resolve sandbox UID/GID from config or OpenShift SCC annotations

Phase 3 of the numeric-UID plan: allow operators to specify explicit
sandbox_uid/sandbox_gid in Kubernetes driver config, auto-detect from
OpenShift SCC namespace annotations, and propagate resolved values to
supervisor container env vars and PVC init container securityContext.

Changes:
- Add sandbox_uid/sandbox_gid fields to KubernetesComputeConfig
- Add SANDBOX_UID/SANDBOX_GID env var constants to openshell-core
- Implement resolve_sandbox_identity() to fetch namespace annotations
  and auto-detect OpenShift SCC UID ranges (sa.scc.uid-range)
- Pass resolved UID/GID through SandboxPodParams to pod spec builder
- Inject SANDBOX_UID/SANDBOX_GID env vars into supervisor container
- Update PVC init container securityContext with resolved UID/GID
  instead of hard-coded root
- Add comprehensive unit tests for resolution logic and annotation
  parsing (resolve_sandbox_uid, resolve_sandbox_gid, OpenShift SCC
  annotation parsing)

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* feat(driver-vm): add configurable sandbox UID/GID and update docs/examples

Phase 4 of the numeric-UID plan: replace hardcoded SANDBOX_UID (10001)
in VM rootfs preparation with configurable sandbox_uid/sandbox_gid fields.

Changes:
- Add sandbox_uid/sandbox_gid to VmDriverConfig with serde derives
- Pass resolved UID/GID through prepare_sandbox_rootfs_from_image_root
  to ensure_sandbox_guest_user which writes /etc/passwd/group/gshadow
- Update BYOC Dockerfile: remove groupadd/useradd, document runtime UID
  injection and the ability to skip baked-in sandbox user
- Update gateway-config.mdx: document sandbox_uid/sandbox_gid for both
  Kubernetes (with OpenShift SCC autodetection) and VM drivers
- Update sandbox-compute-drivers.mdx: add Sandbox User Identity section
  explaining numeric UID support across all compute drivers
- Update rootfs tests to use non-default UIDs, verify config passthrough

Signed-off-by: Seth Jennings <sjenning@redhat.com>

* code review changes

* fix(supervisor): harden tests for restricted CI container environments

Guard tests against CI-specific constraints: root without CAP_SETPCAP,
UIDs with no /etc/passwd entry, and restricted /proc access.

Signed-off-by: Seth Jennings <sjennings@nvidia.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>

---------

Signed-off-by: Seth Jennings <sjenning@redhat.com>
Signed-off-by: Seth Jennings <sjennings@nvidia.com>
2026-07-02 13:31:06 -07:00
Florian Bergmann 43bb030266 feat(docker,podman): add SELinux label support for bind mounts (#2092)
* feat(docker,podman): add SELinux label support for bind mounts

The Docker Engine structured Mount API does not support SELinux
relabelling (:z / :Z). Move user-supplied bind mounts from the
structured `mounts` field to the legacy string-format `binds` field,
which does support these options.

Add a shared `SelinuxLabel` enum (shared/private) to openshell-core so
both Docker and Podman drivers accept an optional `selinux_label` field
on bind mount configs. For Docker, labels are appended to the bind
string; for Podman, they are pushed to the mount options vec.

Signed-off-by: Florian Bergmann <fbergman@redhat.com>

* fix(docker): reject missing bind source paths on legacy binds

Moving user bind mounts from the structured Mount API to the legacy
Binds field changed Docker's behavior for missing source directories:
the legacy path silently creates them as empty root-owned dirs instead
of erroring. Add an explicit Path::exists() check to preserve the
fail-fast behavior operators expect.

Signed-off-by: Florian Bergmann <fbergman@redhat.com>

---------

Signed-off-by: Florian Bergmann <fbergman@redhat.com>
2026-07-02 08:16:52 -07:00
Taylor Mutch 914da339b4 feat(kubernetes): add combined topology config surface (#2074)
* feat(kubernetes): add combined topology config surface

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* docs(kubernetes): clarify topology defaults

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-30 15:07:55 -07:00
Shiju 5477e2f21d docs(mcp): fix granular policy lifecycle examples (#2066)
Signed-off-by: Shiju <shiju@nvidia.com>
2026-06-30 13:36:37 -07:00
krishicksandddurst 7bce1223dc feat(policy): add JSON-RPC and MCP L7 policies (#1865)
Add policy schema, proto, provider profile, OPA, and L7 proxy support for
`protocol: json-rpc` and `protocol: mcp`. Generic JSON-RPC endpoints match
exact method names only, with `method: "*"` as the all-method sentinel;
wildcard/glob methods and params matchers are rejected.

Parse JSON-RPC request bodies and batches in the forward proxy, deny
response-shaped client frames, limit receive-stream GET allowance to MCP
endpoints, and redact params in decision logs. Preserve L7 rule params on the
proto load path so MCP `tools/call` tool filters behave like YAML-loaded
policies.

Add MCP conformance coverage, JSON-RPC L7 e2e coverage, and docs for the new
protocols and current matcher limitations.

Signed-off-by: Kris Hicks <khicks@nvidia.com>
Co-authored-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
2026-06-26 15:52:16 -07:00
Taylor Mutch ba21bb32a2 feat(kubernetes): support agent-sandbox v1beta1 (#2009)
* feat(kubernetes): support agent-sandbox v1beta1

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* ci(kubernetes): test agent-sandbox api versions

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): retry agent-sandbox raw 404s

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): harden agent-sandbox api setup

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(kubernetes): cache agent-sandbox api version

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* docs(kubernetes): document agent-sandbox upgrade behavior

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* refactor(kubernetes): collapse agent-sandbox api selection

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-26 11:44:52 -07:00
Jesse JaggarsandRussell Bryant f569a0ade6 feat(sandbox): proxy-side AWS SigV4 credential signing for CONNECT tunnels (#1638)
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Co-authored-by: Russell Bryant <russell.bryant@gmail.com>
2026-06-26 10:52:24 -07:00
st-grandEvan Lezar 75a317ea45 feat(server): support out-of-tree compute drivers via --compute-driver-socket (#1703)
* feat(server): support remote compute driver endpoints

Add named remote compute driver endpoint support to the gateway.

Remote drivers are selected by a non-reserved compute driver name and either a CLI/env socket endpoint or [openshell.drivers.<name>].socket_path. The VM driver now enters ComputeRuntime through the same acquired remote endpoint path, while Docker, Podman, and Kubernetes retain their in-process drivers.

Require --drivers/OPENSHELL_DRIVERS when pairing an ad-hoc socket endpoint so the socket does not imply a magic driver name, and keep reserved in-tree names unavailable for unmanaged socket endpoints.

Co-authored-by: Evan Lezar <elezar@nvidia.com>

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>

* test(server): cover remote compute driver UDS lifecycle

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: st-gr <38470677+st-gr@users.noreply.github.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Co-authored-by: Evan Lezar <elezar@nvidia.com>
2026-06-26 12:32:20 +02:00
Evan Lezar d93293ad12 fix(e2e): stabilize local Docker smoke test (#1935)
* fix(docker): honor configured supervisor image

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(cli): isolate ssh from host linker environment

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-25 16:51:27 +02:00
Evan Lezar 2c545893ed feat(cli): add GPU count requests (#1812)
* feat(gpu)!: add resource requirements

BREAKING CHANGE: SandboxSpec.gpu and DriverSandboxSpec.gpu were replaced with resource_requirements.gpu, changing protobuf field 9 from a bool to a message for both public and driver APIs.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass requirements through sandbox create

Pass the coupled GPU requirement object through the CLI sandbox_create boundary instead of splitting presence and count into separate arguments.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass requirements to timeout message

Pass ResourceRequirements into the provisioning timeout message helper so GPU hints are derived from the same nested request object used to create the sandbox.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(gpu): pass driver requirements through helpers

Thread Option<GpuResourceRequirements> through driver validation and rendering helpers instead of splitting GPU presence and count into separate arguments.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(gpu): validate exact device requests

Require exact driver GPU device lists to be tied to a GPU request, allow a single exact device to use the default countless request, and require explicit matching counts for multi-device lists.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-23 21:49:02 -07:00
Piotr Mlocek 82d03f1c98 fix(linux): lower host glibc floor to 2.28 to support RHEL/Rocky 8 (#1934) 2026-06-22 14:09:26 -07:00
Alex Lewontin fb83d1a3c8 feat(gateway): add system registry support and source indicators (#1625)
* feat(bootstrap): add system gateway registry for installer defaults

Adds a read-only installer-seeded gateway registry that the CLI consults after per-user gateway config. The registry uses the same layout as per-user config with `active_gateway` at the root and `gateways/<name>/metadata.json` beneath it. By default the system config root is `/etc/openshell`, while `OPENSHELL_SYSTEM_GATEWAY_DIR` remains available as an override for packages that need a different location. User-managed gateways continue to shadow installer entries on name collision.

Originally-authored-by: Mark Shuttleworth <mark@ubuntu.com>
Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* feat(cli): show gateway config source in list and term

Expose whether a gateway registration comes from user or system config in `openshell gateway list`, the TUI gateway pane, and list JSON output. The CLI also refuses to remove system-managed registrations and the smoke tests cover the new list output.

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* fix(bootstrap): preserve user shadowing on invalid metadata

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>

* fix(cli): keep system fallback out of rollback state

* docs(gateway): describe system config fallback layout

* test(bootstrap): cover system gateway last_sandbox persistence

* test(gateway): cover system-only removal rejection

* fix(bootstrap): validate gateway names before path joins

---------

Signed-off-by: Alex Lewontin <alex.lewontin@canonical.com>
2026-06-11 16:25:12 -07:00
Evan Lezar 58a3777d07 fix(drivers): filter bind-backed named volumes (#1861)
* fix(podman): filter bind-backed named volumes

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(drivers): detect recursive bind-backed volumes

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-11 08:03:33 -07:00
alangou b6c87a76ab feat(server): add grpc rate limiting gateway-wide (#1566)
Signed-off-by: Adrien Langou <alangou@nvidia.com>
2026-06-11 13:49:41 +02:00
Evan Lezar 1dc59853ff feat(gpu): move device selection to driver config (#1815)
* refactor(kubernetes): use shared proto struct helpers

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* refactor(docker): extract sandbox template validation

* feat(gpu)!: move device selection to driver config

BREAKING CHANGE: The openshell sandbox create --gpu-device flag and
corresponding API field were removed. Select specific GPUs through
driver-specific driver_config fields instead.

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* fix(kubernetes): reject unknown driver config fields

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-11 12:53:06 +02:00
Taylor Mutch 7dab612feb feat(helm): support Deployment kind in HA gateway workloads (#1867)
* feat(helm): support Deployment kind in HA gateway workloads

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

* fix(helm): handle null workload values

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-10 23:52:07 +00:00
Sergey 42e7b80943 feat(podman): make container health check interval configurable (#1833)
* feat(podman): make container health check interval configurable

The Podman driver hardcoded a 3-second health check interval, which
spawns a conmon subprocess on every tick. On systems running multiple
sandboxes this creates sustained process churn and unnecessary CPU
overhead.

Add a `health_check_interval_secs` field to `PodmanComputeConfig`
(default: 10s) and wire it into the container health check spec.
Operators can tune it further via `[openshell.drivers.podman]` in
gateway.toml.

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

* docs(podman): document health_check_interval_secs config field

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

* docs(podman): document health_check_interval_secs zero-disables behavior

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>

---------

Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
2026-06-10 15:08:45 -07:00
Drew Newberry 530aaf1360 feat(drivers): support docker and podman config mounts (#1785)
* feat(drivers): support docker and podman config mounts

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* docs(drivers): trim mount docs

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* test(e2e): cover local driver volume mounts

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(podman): satisfy linux clippy lint

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(drivers): gate bind mounts behind gateway config

* docs(sandbox): simplify mount examples

* cleanup

* test(e2e): stabilize branch checks

* fix(drivers): tighten local mount validation

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
2026-06-10 11:46:20 -07:00
Taylor Mutch 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>
2026-06-10 10:54:39 -07:00
Evan Lezar c3964a6513 feat(kubernetes): support driver config passthrough (#1744)
* test(docker): default sandbox template fixtures

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* feat(cli): add driver config json plumbing

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* feat(kubernetes): tolerate invalid driver config

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* feat(kubernetes): prototype driver config specifics

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* docs(kubernetes): note driver-local config schema boundary

Signed-off-by: Evan Lezar <elezar@nvidia.com>

* feat(kubernetes): support runtime class driver config

Signed-off-by: Evan Lezar <elezar@nvidia.com>

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-05 12:23:23 -07:00
Taylor Mutch e26a1b1ffc fix(kubernetes): configure sandbox apparmor profile (#1767)
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-06-04 17:24:59 -07:00
Seth Jennings 5e32403dbc feat(k8s-driver): add default_runtime_class_name config for sandbox pods (#1729)
Allow operators to configure a default Kubernetes runtimeClassName that
is applied to sandbox pods when the CreateSandbox request does not
specify one. This avoids requiring every API caller to explicitly set the
runtime class for clusters that always need a specific RuntimeClass
(e.g. kata-containers, nvidia).

The fallback is applied in the Kubernetes driver only — per-request
values still take priority, and an empty default (the built-in) preserves
existing behavior (field omitted, cluster default applies).
2026-06-03 15:09:59 -07:00
Taylor Mutch e4bcfdfaaa fix(gateway): allow local sandbox jwt to not expire (#1721) 2026-06-03 13:50:56 -05:00
Taylor Mutch d9908222f2 feat(kubernetes): support sandbox image pull secrets (#1671) 2026-06-01 20:28:50 -07:00
mjamivandJohn Myers f1fc87e1ad fix(sandbox): trust exact declared private endpoints (#1560)
* fix(sandbox): trust exact declared private endpoints

* fix(sandbox): preserve advisor endpoint provenance

* fix(sandbox): repair advisor provenance lint failures

---------

Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-29 16:18:42 -07:00
Taylor Mutch 0f73d117a5 fix(podman): avoid host-gateway on macOS machines (#1637)
Closes #1307

Default the Podman host gateway alias override to gvproxy's host-loopback IP on macOS while preserving host-gateway resolution on Linux. Wire the setting through Podman config, gateway TOML inheritance, and the standalone driver, and document the platform behavior.

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
2026-05-29 17:38:56 -05:00
Taylor Mutch 188b355033 docs(config): update gateway config reference (#1624) 2026-05-28 20:51:06 -07:00
Mesut Oezdil b2f0f22cfe docs(kubernetes): note that Sandbox volumeClaimTemplates is immutable (#1543) 2026-05-27 15:13:52 +00:00