Commit Graph
88 Commits
Author SHA1 Message Date
alangou 0d708d6d51 fix(policy): gate uninspected credentialed endpoints (#2493)
* fix(policy): gate uninspected credentialed endpoints

Signed-off-by: Adrien Langou <alangou@nvidia.com>

* refactor(cli): extract allowed-ip option parsing

Signed-off-by: Adrien Langou <alangou@nvidia.com>

* fix(policy): gate endpointless credential bindings

Signed-off-by: Adrien Langou <alangou@nvidia.com>

---------

Signed-off-by: Adrien Langou <alangou@nvidia.com>
2026-08-19 15:18:02 +00:00
Mrunal Patel 8d67250a5d fix(providers): keep refresh credential handles stable (#2780)
* fix(providers): keep refresh credential handles stable

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>

* fix(providers): protect refresh-owned credentials

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>

---------

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
2026-08-18 20:45:36 +00:00
Piotr Mlocek 44bf0df485 feat(middleware): inspect WebSocket text messages (#2477)
* feat(middleware): inspect websocket text messages

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): address websocket review feedback

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): bound websocket message assembly

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): harden websocket upgrade lifecycle

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): unify in-process and remote transports

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(middleware): support regex websocket redaction

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): bound persistent streaming sessions

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): accept websocket sequence gaps

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): refine websocket introspection contract

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): clarify websocket preflight lifecycle

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): clarify websocket coverage semantics

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): type websocket frame failures

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): return 503 when middleware admission is exhausted

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): align streaming API contract

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): clarify WebSocket event result scope

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(rfc): simplify middleware revision history

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(examples): add WebSocket content guard support

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): unify binding payload limits

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(middleware): align payload limit terminology

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): address websocket review feedback

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): address websocket review findings

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* test(network): allow Linux handler setup in preflight regression

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): harden websocket relay finalization

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(network): inspect compressed websocket messages

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* test(network): stabilize compressed websocket regressions

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(go-sdk): regenerate middleware protobuf binding

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): clarify websocket skip lifecycle

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(middleware): address WebSocket review feedback

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

---------

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-08-14 21:51:42 +00:00
Derek Carr 59479f492a feat(k8s): add namespace-per-workspace support (RFC 0011 Phase 3) (#2656)
* feat(k8s): add namespace-per-workspace support (RFC 0011 Phase 3)

Implement three workspace namespace modes for the Kubernetes compute
driver: shared (default, preserves current single-namespace behavior),
managed (auto-creates/deletes namespaces per workspace), and operator
(pre-provisioned namespaces with dynamic discovery via label selector
or drop-in allowlist file).

Key changes:
- WorkspaceMode enum and namespace resolution in driver config
- Managed namespace lifecycle with ServiceAccount and OpenShift SCC
  annotation propagation
- Cluster-wide sandbox CR watchers for managed/operator modes
- NamespaceValidator (Exact/Prefix/Allowlist) for SA token auth
- Workspace-aware credential secret storage
- Helm ClusterRole for multi-namespace RBAC
- Gateway config, architecture, and reference docs

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

* test(k8s): add e2e tests for workspace namespace modes

Add end-to-end tests for managed and operator workspace modes
introduced in RFC 0011 Phase 3. The managed mode tests verify
namespace creation with correct labels, ServiceAccount provisioning,
sandbox CR placement, and namespace survival with remaining sandboxes.
The operator mode tests verify rejection of unlabeled and nonexistent
namespaces. The positive operator path (sandbox in labeled namespace)
is known to fail due to an RBAC gap and will be addressed separately.

Also fixes Helm 4 compatibility: move SPDX license headers inside
conditional guards in 8 chart templates to prevent empty comment-only
documents, and fix a trailing whitespace trimmer in clusterrole.yaml
that concatenated the license header with apiVersion.

Adds cleanup sweep in with-kube-gateway.sh to remove managed and
operator namespaces before Helm uninstall, and mise tasks for running
each mode independently.

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

* feat(k8s): add operator namespace label watcher

Spawn a background kube::runtime::watcher in the K8s driver that
watches namespaces matching the configured label selector and populates
the OperatorNamespaceAllowlist at runtime. The driver owns the
allowlist and exposes its Arc so the server can share the same set with
the SA token authenticator.

create_sandbox now gates pod creation on the allowlist in operator
mode — workspaces whose namespace is not yet labeled are rejected at
resource render time rather than silently proceeding. Workspace
lifecycle itself is unaffected; only sandbox (resource) creation is
gated.

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

* fix(k8s): harden operator mode and address review findings

Close the fail-open gap in operator mode when only
operator_namespace_file is configured: the allowlist is now created
unconditionally in operator mode (fail-closed from startup).

Implement the namespace file watcher using the notify crate, following
the TLS hot-reload pattern (parent-directory watch, 1s debounce,
ConfigMap symlink-swap safe). The file format is a JSON array of
namespace name strings.

Additional fixes from the 10-reviewer audit:
- Change allowlist rejection from InvalidArgument to FailedPrecondition
  so callers know the request may succeed later once the namespace is
  provisioned.
- NamespaceValidator::Allowlist now holds the OperatorNamespaceAllowlist
  newtype instead of a raw Arc<RwLock<BTreeSet>>, eliminating silent
  denial on RwLock poison.
- Verify LABEL_MANAGED_BY and LABEL_GATEWAY_ID ownership before
  deleting a managed namespace.
- Replace fixed 5s sleep in operator e2e test with a 30s poll loop.
- Add Helm validation for workspaceMode values.
- Fix Helm README type column and description for operator fields.
- Add insert/remove methods to OperatorNamespaceAllowlist; label
  watcher now uses them instead of reaching through shared().
- Reject configs with both operator_namespace_label and
  operator_namespace_file set.

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

* feat(k8s): add workspace-level compute driver RPCs and harden RBAC

Decouple namespace lifecycle from sandbox lifecycle by adding
EnsureWorkspace/DeleteWorkspace RPCs to the ComputeDriver service.
Namespace creation now happens before credential storage and namespace
deletion happens on workspace delete, fixing credential storage in
managed workspace mode.

- Add EnsureWorkspace and DeleteWorkspace proto RPCs with
  implementations across all compute drivers (K8s managed delegates to
  ensure_namespace/delete_namespace_if_empty; others no-op)
- Wire ensure_workspace into provider create/update/refresh paths so
  the namespace exists before the credential driver writes secrets
- Wire delete_workspace into workspace deletion for cleanup
- Remove delete_namespace_if_empty from sandbox deletion path
- Scope ClusterRole secrets access to non-shared workspace modes
- Add TODO for TLS cert hot-reload in sandbox gRPC client
- Harden e2e tests with control-plane sandbox resolution assertions
- Fix docker image save --platform flag for OCI index manifests

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

* fix(k8s): address re-review findings and add test coverage

- Use server-side apply for TLS secret sync (fixes second sandbox
  creation failure when TLS is enabled)
- Scope gateway-ID label selector unconditionally across all workspace
  modes (fixes operator reads/watches/deletes seeing foreign sandboxes)
- Validate operator allowlist in EnsureWorkspace and DeleteWorkspace
  RPCs (prevents credential writes to namespaces outside the allowlist)
- Extend ClusterRole secrets patch+delete to all non-shared modes with
  credential driver enabled (fixes operator credential storage RBAC)
- Validate namespace ownership on 409 conflict in ensure_namespace
  (prevents adopting unowned namespaces in managed mode)
- Replace delete_namespace_if_empty with unconditional delete_namespace
  letting Kubernetes cascade cleanup (fixes stuck terminating CRs)
- Strengthen NetworkPolicy TODO to cover both managed and operator modes
- Extract selector and ownership logic into testable free functions
- Add unit tests for gateway-ID selectors and namespace ownership
- Add Helm ClusterRole RBAC tests for operator credential driver

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

* ci(k8s): add workspace managed and operator mode e2e to CI

Wire the existing e2e:kubernetes:workspace-managed and
e2e:kubernetes:workspace-operator mise tasks into the branch-e2e
workflow so they run alongside the other core Kubernetes e2e suites.
Both are gated by run_core_e2e and included in the Core E2E result
gate.

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

* test(k8s): add e2e tests for workspace namespace modes

Add 7 new e2e tests covering workspace namespace lifecycle, TLS secret
copying, ownership conflict detection, DNS-1123 validation, operator
namespace preservation, and dynamic label watcher behavior. Fix async
sandbox deletion race condition in existing tests by polling sandbox
list instead of asserting immediately after delete.

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

* fix(k8s): grant secrets/patch unconditionally and backfill gateway-id labels

Address two review findings:

1. RBAC: server-side apply (PATCH) is used for TLS secret sync in
   multi-namespace modes, but the ClusterRole only granted patch when
   the kubernetes-secrets credential driver was enabled. Grant patch
   unconditionally for non-shared modes since TLS sync always needs it;
   keep delete gated on the credential driver.

2. Upgrade safety: the new gateway-id label selector would orphan
   legacy Sandbox CRs that predate its introduction. Add a startup
   backfill in shared mode that patches any managed Sandbox CR missing
   the gateway-id label before the driver begins serving requests.

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

* fix(k8s): address workspace namespace review findings

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

* fix(k8s): address follow-up review findings

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

* fix(k8s): preserve workspace lookup after rebase

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

* fix(helm): allow managed secret creation

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

* fix(k8s): stop pods in workspace namespace

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

* test(k8s): scope pod deletion check to v1alpha1

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

* fix(k8s): address workspace namespace review findings

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

---------

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-08-14 21:22:41 +00:00
Piotr Mlocek bdabb54cb3 fix(security): authenticate extension services (#2638)
* fix(security): authenticate extension services

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(extension-core): verify gateway JWTs

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(extension-core): keep inbound verification external

This should become an extension SDK package.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(security): harden the extension authentication contract

Follow-up hardening on the alpha extension authentication mechanism.

Claim contract:
- Extension tokens carry an explicit `typ` of `openshell-ext+jwt`. They
  share a signing key with sandbox-to-gateway admission tokens and were
  otherwise separated by audience alone, so a verifier that neglects to
  check `aud` could accept a gateway credential. The header is a second,
  independent discriminator.
- Publish OIDC-shaped discovery at `/.well-known/openid-configuration`
  so a service configured with only the gateway URL can learn the exact
  expected issuer and the JWKS location. It is shaped, not compliant:
  `issuer` is the gateway identity, not the serving URL.

Audience agreement:
- `MiddlewareManifest` and `InterceptorManifest` gain `expected_audience`.
  The audience is otherwise configured independently on each side of the
  boundary, where a mismatch surfaces only as an opaque authentication
  failure on every call. OpenShell now compares the two and fails at
  startup. An empty field keeps the check off for existing services.

Compatibility:
- Add `allow_insecure_transport` per registration. Enabling gateway JWT
  signing previously made any plaintext endpoint a hard startup failure,
  including the endpoint form used in our own documentation. The opt-out
  attaches no credential, is refused by the gateway if a supervisor asks
  for one, and warns at every startup.
- Make the transport requirement kind-aware. A middleware endpoint must
  be reachable from every sandbox supervisor, so only interceptors may
  use a gateway-local Unix socket.

Credential lifecycle:
- Replace the process-global slot map with a supervisor-owned
  `ExtensionCredentialStore` shared explicitly across the gateway
  connections the supervisor opens, removing test-order coupling.
- Rotate only when a credential is missing or has passed four fifths of
  its lifetime. Configuration polling ran every ten seconds against
  fifteen-minute credentials, so each poll re-ran gateway effective-policy
  resolution and re-minted the gateway token.
- Bound credential minting per sandbox, since each request resolves the
  caller's effective policy.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs: record alpha extension authentication in RFC appendices

Restore the RFC 0009 and 0010 bodies to their accepted text and move
every extension-authentication update into appendices instead. An RFC
records a decision at a point in time; superseding detail belongs
alongside it rather than rewritten into it.

RFC 0009's appendix carries the shared contract: claims, authorization,
key distribution, the `allow_insecure_transport` replacement for the
body's `allow_insecure`, and residual risks. RFC 0010's records only
what differs for interceptors and links to it. The existing
protocol-extensions appendix, which parked the phase 2 transport
question, now points forward to what was built.

Also document the audience handshake, the discovery endpoint, the
`typ` requirement, and `jti` replay guidance in the extensibility and
gateway configuration pages, and correct the middleware transport
guidance: middleware endpoints must be reachable from sandbox
supervisors, so Unix sockets are not an option there.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(extension-core): abstract extension server trust

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(core): update middleware manifest example

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(extension-auth): preserve unsigned gateway compatibility

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(extension-auth): reject cross-domain token replay

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

---------

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-08-14 20:34:10 +00:00
Seth Jennings 0f8fad23c4 feat(sandbox): add stop and start operations (#2653)
* feat(sandbox): add suspend and resume operations

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

* fix(server): preserve lifecycle work after cancellation

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

* fix(server): reconcile ambiguous lifecycle outcomes

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

* fix(server): complete suspended session cleanup

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

* fix(vm): preserve suspension state on resume failure

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

* fix(server): retry retained lifecycle transitions

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

* fix(server): clean sessions after suspend reconciliation

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

* test(sandbox): cover deleting suspended sandbox

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

* fix(kubernetes): preserve progressing sandbox suspension

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

* fix(kubernetes): bound suspend status polling

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

* fix(kubernetes): detect legacy sandbox suspension

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

* fix(tui): render suspended sandbox phases

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

* refactor(sandbox): rename suspend and resume lifecycle

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

* perf(server): clean stopped sessions on transition

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

* fix(kubernetes): fail fast on rejected stop

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

* fix(compute): fence stale restart lifecycle events

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

---------

Signed-off-by: Seth Jennings <sjenning@redhat.com>
2026-08-13 06:13:54 +00:00
John T. MyersandJohn Myers 0120535efc feat(proxy): bind static credentials to provider endpoints (#2510)
* feat(proxy): bind static credentials to provider endpoints

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

* test(e2e): verify static credential endpoint isolation

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

* docs(provider): explain static credential endpoint binding

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

* fix(e2e): use valid endpoint isolation fixtures

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

* docs(provider): explain static credential endpoint binding

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

* fix(credentials): preserve binding identity across rotations

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

* fix(proxy): enforce bindings across request lifecycle

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

* fix(proxy): close credential relay gaps

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

* docs(credentials): clarify binding failure behavior

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

* fix(credentials): hash selected provider profile scope

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

* fix(proxy): resolve credentials after request admission

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

* docs(credentials): clarify binding failure diagnostics

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

* fix(proxy): align single-route credential denials

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

* fix(credentials): harden endpoint-bound rotation

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

* fix(credentials): enforce identity and authority binding

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

* fix(credentials): snapshot provider environment atomically

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

* test(e2e): include authority port in query proxy requests

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

* fix(credentials): close credential revocation gaps

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

* docs(proxy): explain authority mismatch diagnostics

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

* fix(credentials): enforce binding lifecycle invariants

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

* fix(provider): reject credential config collisions

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

* fix(network): capture credential scope atomically

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

* fix(network): distinguish origin and absolute targets

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

* fix(provider): isolate endpointless profile credentials

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

* fix(network): normalize IPv6 request authorities

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

* docs(credentials): clarify endpointless profile isolation

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

* feat(policy): bind endpointless provider credentials

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

* fix(credentials): use current GCP placeholder revision

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

* docs(providers): explain policy credential bindings

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

* test(credentials): cover endpointless fail-closed invariant

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

* test(policy): expect ambiguity rejection at creation

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>

* refactor(proxy): share credential mismatch finding builder

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

* test(credentials): cover malformed binding metadata

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

* test(credentials): verify multi-key endpoint isolation

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

* test(e2e): cover same-host credential path denial

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

* docs(credentials): document serialized refresh contract

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

* refactor(proxy): consolidate L7 log formatting

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

* perf(credentials): precompile endpoint binding patterns

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

* perf(credentials): share identity epoch revisions

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

* test(proxy): require explicit request default ports

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

* fix(policy): validate SigV4 credential sources

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

* fix(credentials): preserve endpoint bindings for credential handles

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

* feat(go-sdk): expose network credential bindings

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

---------

Signed-off-by: John Myers <johntmyers@users.noreply.github.com>
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
Co-authored-by: John Myers <johntmyers@users.noreply.github.com>
2026-08-10 19:19:45 +00:00
5548405fcb feat(credentials): add provider credential storage drivers (#2437)
* feat(credentials): add provider credential storage drivers

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

* fix(credentials): harden credential update handling

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

* fix(credentials): harden credential driver security, correctness, and performance

Address review findings from the credential storage drivers PR:

- Route additional_credentials through the driver on refresh to prevent
  silent data loss for multi-credential providers (e.g. AWS STS)
- Clean up stored credential handles on CAS failure during refresh to
  prevent orphaned secrets in external backends
- Enforce namespace validation in the Kubernetes Secrets driver to
  prevent cross-namespace credential access when allow_reference_namespace
  is not enabled
- Cache Vault Kubernetes auth tokens with 80% TTL to avoid re-authenticating
  on every credential operation
- Parallelize resolve_credentials in all three drivers using try_join_all
  for faster sandbox startup
- Add existingSecret support for the KEK Secret to fix helm template/GitOps
  workflows where lookup returns empty and regenerates the key
- Document RBAC blast radius for the Kubernetes Secrets credential driver
  and recommend a dedicated namespace

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): add optimistic concurrency, fix thundering herd, parallelize operations

Use resourceVersion optimistic concurrency with retry loop for K8s
Secret ownership checks to prevent TOCTOU races. Switch Vault token
cache from RwLock to Mutex with double-check pattern to prevent
thundering herd on cache miss. Parallelize credential store and delete
operations across independent keys using try_join_all.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): handle partial failures, add delete retry, consolidate cleanup

Replace try_join_all with join_all in credential store/delete operations
to handle partial failures — successfully-stored handles are cleaned up
when another key fails. Add retry loop with conflict detection to
db-credstore delete_credential, matching the K8s driver pattern.
Consolidate 4 manual cleanup_pre_stored_provider_credentials call sites
into a single error handler using an async block. Remove inconsistent
.trim() from db-credstore validate_handle_owner.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): fix retry loop guard and remove unprotected validation

Remove attempt-count guard from 409/Aborted match arms in retry loops
so the post-loop Status::aborted error is reachable after exhausting
retries. Previously, last-attempt conflicts fell through to the
catch-all error arm, producing misleading Status::unavailable errors.

Remove duplicate validation calls that ran after
prepare_provider_credential_update but outside the cleanup-protected
async block, which would leak pre-stored handles on failure.

Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>

* fix(credentials): add workspace/provider UUID to credential backend paths

Include workspace and provider ID in credential backend object paths to ensure
cross-workspace uniqueness and prevent credential collision (GATOR-1806c9be-01).

- Updated credential driver proto to include workspace and provider_id fields
- Modified Vault driver to include workspace/provider_id in managed_secret_path
- Modified Kubernetes Secrets driver to include workspace/provider_id in
  credential_owner_id and managed_secret_name
- Updated all credential runtime calls to pass workspace/provider_id
- Updated tests to use the new signatures

This prevents two workspaces sharing the same external credential store from
colliding on provider names, which was a critical security issue (CWE-639).

* fix(credentials): preserve provider-level expiration for handle-backed credentials

Compute effective expiration from both provider and driver values using the
earliest non-zero timestamp and skip expired values before insertion
(GATOR-1806c9be-02).

- Modified resolve_provider_handles to check provider credential_expires_at_ms
- Skip expired credentials during resolution instead of returning them
- Use effective expiration (min of provider and driver) in resolution results
- Fix inference.rs to preserve earliest expiration when merging

This ensures handle-backed credentials respect the same expiration semantics
as inline credentials.

* fix(credentials): stage refresh changes under new handles before validation

Stage credential replacements under new immutable handles instead of reusing
existing handles to prevent overwriting committed values before validation/CAS
(GATOR-1806c9be-03).

- Stage credentials with empty existing_handles map to force new handle creation
- Validate and CAS before the new values are committed to backend storage
- Delete old handles only after successful CAS
- On CAS failure, delete only the newly staged handles
- This prevents CWE-362/CWE-367 race conditions where failed refreshes could
  still modify or delete the active credential

The fix ensures that a rejected refresh cannot modify the backend object still
referenced by the committed provider record.

* fix(credentials): add timeouts to credential driver RPCs

Apply configured timeouts to both startup capability negotiation and runtime
RPCs to prevent indefinite hangs (GATOR-1806c9be-05).

- Add DEFAULT_CREDENTIAL_DRIVER_RPC_TIMEOUT_SECS constant (30s)
- Apply timeout to GetCapabilities during startup connection
- Apply timeout to all runtime RPCs (store, delete, resolve)
- Use tokio::time::timeout to bound the entire GetCapabilities operation
  during startup, not just the socket connection
- Return contextual deadline errors on timeout

This prevents a faulty or overloaded driver from hanging gateway operations
indefinitely.

* fix(credentials): fix test to use consistent workspace/provider identity

The Kubernetes auth Vault resolve test was constructing a managed path with
test-workspace/test-provider-id but sending default/prov-123 in the request,
causing validation to reject the request (GATOR-18e32351-01).

- Update test to use test-workspace and test-provider-id in the request to
  match the logical_path construction
- This ensures the test exercises the intended code path and validates
  Kubernetes auth resolution properly

The test now passes and correctly validates identity enforcement.

* fix(credentials): use unique staging ID for refresh to avoid overwrites

Stage refresh replacements under genuinely distinct immutable handles using
a unique staging ID to prevent overwriting committed values (GATOR-1806c9be-03).

- Generate a unique staging ID using UUID for each refresh operation
- Use this staging ID when storing credentials instead of the real provider ID
- Pass the same staging ID during cleanup on failure to delete only staged objects
- This ensures deterministic paths (Vault) and object names (K8s) don't collide
  with the committed provider's credentials

The fix prevents failed refreshes from silently replacing active credentials
or breaking providers by deleting still-referenced backend objects.

* fix(credentials): wrap credential driver RPCs in local timeouts

Add local tokio::time::timeout wrappers around credential driver RPCs to
bound non-compliant or stalled UDS peers (GATOR-1806c9be-05).

- Wrap StoreCredential, DeleteCredential, and ResolveCredentials in local timeouts
- Return contextual deadline_exceeded errors when timeouts occur
- Keep existing gRPC timeout metadata for compliant implementations
- GetCapabilities during startup was already wrapped in previous commit

This ensures a faulty local driver cannot hang gateway operations indefinitely,
even if it accepts the connection but never responds to the RPC.

* fix(credentials): preserve ownership for staged refreshes

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

* fix(credentials): bound startup capability probe

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

* test(provider): authenticate credential handler requests

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

* fix(ci): grant actions read to credential driver e2e

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

---------

Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Varsha Prasad <varshaprasad96@gmail.com>
Signed-off-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Co-authored-by: Taylor Mutch <taylormutch@gmail.com>
Co-authored-by: Varsha Prasad Narsing <varshaprasad96@gmail.com>
2026-08-05 16:29:41 +00:00
Simon Scatton 1959ea19be build(bazel): establish RFC 0012 Rust reference graph (#2414)
Establish the Phase 1 reference implementation proposed by RFC 0012
while retaining the existing Cargo and Mise workflows during evaluation.

- pin Bazel 9.1.1 and configure Bzlmod, rules_rs, LLVM, protobuf, and
  Rust 1.95 toolchains
- import third-party crates from Cargo metadata and propagate the
  workspace version into Bazel targets
- add library, binary, proc-macro, unit-test, and integration-test
  targets across the supported Rust workspace crates and drivers
- generate protobuf Rust sources and descriptor sets under Bazel while
  preserving Cargo-compatible generated-code imports
- annotate aws-lc-sys and zstd-sys native dependencies, build Z3 4.15.2
  from source, and generate z3-sys bindings
- make CLI and procfs test fixtures available as explicit Bazel inputs
  without relying on fixed host binary paths
- define optimized release targets for Linux x86_64 and aarch64 CLI,
  sandbox, and gateway binaries, plus macOS aarch64 artifacts
- add Bazel, buildifier, and lcov to the Nix development environment

RFC: 0012 (rfc12 branch)
Refs: #2491

Signed-off-by: Simon Scatton <sscatton@nvidia.com>
2026-08-03 09:05:40 +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
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
Derek Carr 5952a5a23f feat(workspace): add workspace resource model with scoping, membershi… (#2243)
* feat(workspace): implement workspace model (Phase 1 of RFC 0011)

Implements workspace and membership model providing hard isolation
boundaries for multi-player OpenShell deployments.

Workspace CRUD with Kubernetes-style Terminating phase for graceful
deletion. All resources scoped by workspace via ObjectMeta. Membership
RPCs for workspace access control. Persistence migration shifts name
uniqueness to (object_type, workspace, name). Provider profiles support
platform and workspace scoping. Service routing uses workspace-prefixed
DNS labels. Inference routes renamed and workspace-scoped with
DeleteInferenceRoute RPC. Python SDK with WorkspaceClient, two-method
list pattern (workspace-scoped and for_all_workspaces), and workspace
parameter on all methods. CLI workspace flags, TUI workspace cycling.
K8s driver filters unmanaged CRs and uses delete preconditions. Podman
driver uses immutable container IDs. Label serialization fixed across
all put_if call sites.

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

* fix(cli): delegate sandbox upload command to existing upload function

The standalone `sandbox upload` command reimplemented upload logic
inline with two bugs: it used `Path::exists()` which follows symlinks
(rejecting dangling symlinks), and it ran git-aware filtering on
symlink sources. The `run::sandbox_upload()` function already handles
both cases correctly via `sandbox_upload_plan()`. Replace the inline
logic with a call to the existing function.

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

* fix(e2e): shorten sandbox names and fix test compatibility

Shorten the sandbox name in initial_sparse_policy_is_acknowledged_as_loaded
from 'e2e-2159-sparse-enrich' (22 chars) to 'e2e-sparse-enrich' (17 chars)
to comply with MAX_ROUTABLE_NAME_LEN (19 chars).

Also capture stderr in create_keep_with_args so future sandbox creation
failures include the actual CLI error instead of reporting empty output.

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

* test(workspace): add test coverage for workspace CRUD and persistence isolation

Add unit tests for workspace create happy path, get round-trip, get
not-found, get empty-name rejection, already-exists error, and
resolve_workspace not-found. Add persistence test proving cross-workspace
name uniqueness (same name in different workspaces produces separate
records). Add workspace name max-length boundary tests. Fix e2e harness
to include stderr in name-parse-failure error path. Align Python e2e
test_workspace_crud with try/finally pattern. Document provider profile
catalog workspace scoping gap in RFC 0011.

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

* fix(examples): update examples for workspace model compatibility

Shorten sandbox names in demo scripts to fit the 19-character
MAX_ROUTABLE_NAME_LEN limit: policy-demo prefix to pd-, multi-agent
notepad derives a short SANDBOX_TAG from the run ID, governance
interceptor uses gs-PID-RANDOM. Update vscode-remote-sandbox.md SSH
host aliases from openshell-{name} to openshell-{name}.{workspace}
format.

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

* feat(sdk): add workspace-scoped client and workspace CRUD

Add WorkspaceScopedClient modeled after kube::Api::namespaced — captures
workspace once and injects it into every sandbox request. Add workspace
CRUD methods (create, get, list, delete) and list_sandboxes_all_workspaces
on OpenShellClient. Extend SandboxRef with workspace field and add
WorkspaceRef type. Include mock tests for all new operations.

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

* fix(lint): resolve clippy warnings in workspace test assertions

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

* fix(docs): convert indented code blocks to fenced in RFC 0011

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

* fix(lint): resolve clippy warnings and apply cargo fmt across workspace

Auto-format with cargo fmt and fix clippy warnings exposed by the
reformat: unnecessary qualifications, map_unwrap_or, identical match
arms, unused variable prefix, dead code annotations, and let-unit-value
in e2e harness.

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

* fix(workspace): address workspace scoping issues from review

- Add workspace field to settings JSON output (CLI)
- Skip Podman containers missing workspace label instead of defaulting
  to empty string, matching K8s driver behavior
- Add resource_version to list_by_scope SELECT in both SQLite and
  Postgres backends, with regression test
- Gate PolicyLocalContext proposal/lookup routes on workspace readiness,
  returning 503 when workspace is not yet discovered
- Block sandbox and provider creation in TUI all-workspaces mode
- Clear workspace vectors in TUI reset_sandbox_state

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

* fix(workspace): make provider profile catalog workspace-aware

Thread workspace through snapshot_catalog so the
EffectiveProviderProfileCatalog enforces workspace boundaries on both
read and write paths. UserProviderProfileSource now loads platform-scoped
profiles (workspace "") plus the target workspace's profiles, preventing
cross-workspace duplicate profile ID collisions that previously caused
global catalog failures.

Update RFC 0011 to reflect catalog scoping is implemented in Phase 1
rather than deferred to future work.

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

* fix(persistence): include workspace column in atomic policy revision INSERT

put_policy_revision_atomic omitted the workspace column from the INSERT
into the objects table in both SQLite and Postgres backends, causing
atomically-written policy revisions to lose their workspace association.
Add workspace field to AtomicPolicyRevisionWrite and thread it through
both backend INSERT statements, matching the non-atomic put_policy_revision
path which already included it.

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

* fix(proxy): skip ancestor walk when socket owner is the entrypoint

collect_ancestor_identities walked the entire process tree above the
entrypoint when the connecting process was the entrypoint itself,
SHA256-hashing every ancestor binary (IDE, shell, container runtime).
On dev machines with large binaries in the ancestor chain this exceeded
the 30-second test timeout. When start_pid == stop_pid there are no
intermediate ancestors to verify, so return an empty list immediately.

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

* fix(workspace): make provider profile catalog scope-aware

Allow the same profile ID at platform and workspace scopes by
introducing layered catalog entries where workspace profiles shadow
platform profiles. Add source and scope fields to the ProviderProfile
proto and CLI output. Migrate List/Get handlers to the catalog,
fixing divergence with runtime profile resolution.

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

* fix(e2e): align podman e2e labels with centralized driver constants

The podman driver moved its container labels to the centralized
openshell.ai/ prefix, but the e2e test harness and cleanup script
still referenced the old openshell.sandbox-* keys, causing the
local_driver_token_restart test to fail on container lookup.

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

* fix(e2e): align python profile isolation test with scope-aware catalog

Platform profiles are now visible in workspace listings as fallbacks
per the layered catalog design. Update the assertion to match.

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

* fix(workspace): honor profile_workspace in runtime profile resolution

Runtime profile lookups now consult provider.profile_workspace via
get_type_profile_for_scope. Providers created with --global-profile
(profile_workspace="") resolve to the platform profile even when a
workspace profile shadows the same ID. All 6 runtime call sites
updated; type-only call sites remain scope-agnostic.

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

---------

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-07-21 00:59:18 +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
Russell Bryant aa483ecb9a feat(providers): AWS STS AssumeRole refresh strategy and aws-s3 profile (#1782)
Add gateway-managed AWS STS credential refresh (provider-v2, #1576). The
gateway calls sts:AssumeRole and writes three short-lived credentials
(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN) to the
provider record; the proxy re-signs requests with SigV4. Adds the aws and
aws-s3 provider profiles and a declarative multi-output refresh model
(additional_outputs) so one AssumeRole co-mints all three credentials.

Signed-off-by: Russell Bryant <rbryant@redhat.com>
2026-07-16 13:28:01 -07:00
Evan Lezar 21aaa89522 feat(gateway): add elevated gateway info (#2202)
* feat(cli)!: fold gateway metadata into list

BREAKING CHANGE: openshell gateway info no longer shows local gateway registration metadata. Use openshell gateway list or openshell gateway list -o json for local registration details.

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

* feat(gateway): add elevated gateway info

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

---------

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-15 22:04:57 +00: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
Evan Lezar 45614a3fb6 refactor(api): remove SandboxTemplate.volume_claim_templates (#2088)
The field was added during the Kubernetes driver extraction refactor
(#817) as a pass-through mechanism, but was never wired up to a CLI
flag, Python SDK helper, or any documentation. The only reachable
user path was raw gRPC construction.

The Kubernetes driver now always injects the default workspace PVC,
removing the branching logic that checked for a user-supplied VCT.
Field number 9 is reserved in the proto to prevent reuse.

Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-07-01 16:04:47 +02: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
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
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
John T. Myers 48a7d09e82 feat(providers): support profile updates (#1914)
* feat(providers): support profile updates

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

* fix(providers): require CAS for profile updates

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

* fix(providers): serialize profile update validation

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

* fix(providers): serialize profile import invariants

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

* fix(providers): require profile update target id

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

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-06-23 11:34:46 -07: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 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
Calum Murray b392b2eefe feat(providersv2): add path auth_style (#1622)
Signed-off-by: Calum Murray <cmurray@redhat.com>
2026-06-05 17:14:05 -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 e4bcfdfaaa fix(gateway): allow local sandbox jwt to not expire (#1721) 2026-06-03 13:50:56 -05:00
Adam Miller f061b1d923 feat(providers): add Google Vertex AI inference provider (#1568)
* feat(providers): add Google Vertex AI provider

Adds Vertex AI provider profiles, routing, credential refresh plumbing, CLI support, docs, and regression coverage. Keeps the related NETLINK_ROUTE seccomp allowance needed by Vertex client tooling that calls getifaddrs.

* docs: add Vertex AI sandbox usage for Claude Code and OpenCode

Cover the full end-to-end setup for running Claude Code and OpenCode
inside an OpenShell sandbox via inference.local with a Vertex AI backend:

- google-vertex-ai.mdx: add 'Use from a Sandbox' section with tabbed
  examples for Claude Code (--bare flag, no /v1 suffix) and OpenCode
  (/v1 suffix required). Add providers_v2_enabled prerequisite and
  --no-verify note for global region. Document policy proposals table
  covering metadata.google.internal (always blocked), downloads.claude.ai,
  and storage.googleapis.com.

- inference-routing.mdx: expand 'Use the Local Endpoint' section with
  tabbed examples for Claude Code, OpenCode, Python OpenAI SDK, and
  Python Anthropic SDK. Add notes explaining the /v1 path suffix
  difference between clients.

- supported-agents.mdx: update Claude Code and OpenCode rows to mention
  inference.local support and correct base URL requirements.

* fix: address vertex review findings

* test(sandbox): retry on spurious Ok in fork-exec ambiguity test

On arm64 under heavy CI load, the /proc fd scan in
find_socket_inode_owners can transiently miss the parent process's
socket fd entry, returning only the child as an owner. This causes
resolve_process_identity to return Ok (single owner, no ambiguity
check fires) instead of the expected ambiguous-ownership Err.

Extend the retry loop to also handle unexpected Ok results, mirroring
the existing retry for transient Err results. 10 retries at 50ms gives
a 500ms settling window, which is sufficient for procfs to stabilize
on loaded arm64 runners.

* fix: address vertex review regressions

* docs(router): clarify stream_response semantics for Vertex rawPredict routing

Document the three call sites of prepare_backend_request and their
stream_response values in a caller table:

- send_backend_request: false → :rawPredict (unary endpoint)
- send_backend_request_streaming: true → :streamRawPredict
- verify_backend_endpoint: explicitly false to probe the unary endpoint

Cross-reference the table from build_provider_url and
is_vertex_anthropic_rawpredict_route so the stream_response=true guard
in the suffix upgrade branch is understood in full context.

Also note that is_vertex_anthropic_rawpredict_route is a structural
predicate (model_in_path + anthropic_messages + :rawPredict suffix),
not a named-provider check, so any future provider with the same route
shape inherits the transforms automatically.
2026-06-02 10:45:50 -05:00
Evan Lezar 79aa355dd0 refactor(driver): trim compute capability response (#1402)
Signed-off-by: Evan Lezar <elezar@nvidia.com>
2026-06-02 08:18:35 -07:00
Kirit ThadakaandJohn Myers c63ac76d06 feat(telemetry): add anonymous opt-out OpenShell usage telemetry (#1433)
* feat(telemetry): add anonymous opt-out usage telemetry

Signed-off-by: Kirit93 <kthadaka@nvidia.com>

* Removed enums from schema

Signed-off-by: Kirit Thadaka <kthadaka@nvidia.com>

* Updated telemetry URL

Signed-off-by: Kirit93 <kthadaka@nvidia.com>

* ci(kubernetes): pin mise installer for e2e

---------

Signed-off-by: Kirit93 <kthadaka@nvidia.com>
Signed-off-by: Kirit Thadaka <kthadaka@nvidia.com>
Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-06-01 14:35:51 -07:00
Alexander Watson e98ea3ee93 feat(policy): add agentic approval loop (#1528) 2026-05-29 21:11:31 -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
Derek Carr d01d10650b refactor(proto): move phase and current_policy_version into status (#1565)
* refactor(proto): move phase and current_policy_version into SandboxStatus

Move phase and current_policy_version from SandboxSpec into
SandboxStatus to correctly model mutable runtime state. Update all
callers in the gateway server, TUI, and Python SDK to read and write
these fields through SandboxStatus accessors.

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

* fix(server): preserve sandbox status on statusless driver updates

When a driver update arrives without a status payload (e.g. before
Kubernetes populates the status subresource), preserve the stored
phase, conditions, and current policy version instead of resetting
them. Adds a regression test covering the edge case.

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

---------

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-05-29 08:39:12 -07:00
John T. Myers 0cef26521a feat(providers): derive discovery from profiles (#1503)
* feat(providers): derive discovery from profiles

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

* fix(providers): keep v2 discovery profile-only

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

* docs(providers): update providers v2 behavior

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

* fix(providers): make github profile read-only

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

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-22 08:54:15 -07:00
Taylor Mutch a3b16c18ab feat(auth): per-sandbox authentication to gateway (#1404) 2026-05-21 17:58:55 -07:00
John T. Myers 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
2026-05-19 07:42:10 -07:00
Derek Carr 7f16d60ef1 feat(persistence): implement optimistic concurrency control with CAS (#1292)
Add resource_version-based optimistic concurrency control to the
persistence layer. Every write now requires an explicit WriteCondition
(MustCreate or MatchResourceVersion), enforced at compile time by
gating unconditional put/put_message behind #[cfg(test)].

- Add WriteCondition enum and put_if for conditional writes
- Add update_message_cas for atomic read-modify-write operations
- Add list_messages/list_messages_with_selector helpers that hydrate
  resource_version from authoritative DB rows
- Convert all production write paths to CAS-aware methods
- Gate put/put_message behind #[cfg(test)] to prevent non-CAS writes
- Use structured PersistenceError::UniqueViolation matching instead of
  string matching for duplicate detection
- Hydrate resource_version from WriteResult directly on creates,
  eliminating unnecessary read-after-write round trips

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-05-18 16:35:46 -04:00
Florent BENOIT 442b0b6b92 feat(exec): add bidirectional streaming for interactive TTY sessions (#1331)
* feat(exec): add bidirectional streaming for interactive TTY sessions

The existing ExecSandbox RPC sends stdin upfront in the request body,
making interactive programs (bash, top, vim) unusable with --tty. This
adds an ExecSandboxInteractive bidirectional streaming RPC that forwards
live keystrokes, terminal resize events, and EOF to the sandbox.

- Add ExecSandboxInteractive RPC, ExecSandboxInput and
  ExecSandboxWindowResize messages to the proto definition
- Implement server handler with relay bridging, PTY allocation via
  russh channel.split(), and timeout support
- Implement CLI client with raw mode, spawn_blocking stdin reader,
  SIGWINCH resize forwarding, and process::exit for clean shutdown
- Route to interactive path only when --tty is explicitly passed

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

* fix(exec): address interactive TTY streaming review feedback

Server-side (sandbox.rs):
- Close SSH channel after EOF so PTY-backed programs (bash, vim, top)
  terminate on client disconnect instead of leaking the session
- Break output loop when tx.send() fails (client gone) to tear down
  the streaming pipeline promptly
- Default zero cols/rows to 80x24 before request_pty, matching the
  proto "0 = use default" contract and preventing 1x1 terminals for
  non-CLI clients

Client-side (run.rs):
- Replace spawn_blocking stdin reader with a detached std::thread so
  the tokio runtime shutdown does not hang on a thread blocked in
  stdin.read(). This removes the need for std::process::exit(),
  allowing save_last_sandbox() in the caller to execute normally
- Switch from futures::channel::mpsc with try_send() to
  tokio::sync::mpsc with blocking_send() for proper backpressure —
  try_send silently stopped forwarding all input when the channel
  filled during paste or slow network
- Add TaskGuard (abort-on-drop) for the resize task to ensure cleanup
  on all return paths including early errors

---------

Signed-off-by: Florent Benoit <fbenoit@redhat.com>
2026-05-15 12:12:44 -07:00
Alexander Watson ea2fddbe2d feat(policy): agent-driven policy management — the agent half (#1323)
* feat(policy): plumb chunk_ids and rejection_reason through proposal pipeline

Prereq plumbing for the agent revise-and-resubmit loop. Two narrow
additive proto changes unblock the upcoming /wait endpoint (#1092),
prover validation badge (#1097), and reject --guidance surfaces (#1098).

- SubmitPolicyAnalysisResponse: add accepted_chunk_ids so the in-sandbox
  agent gets handles to watch its proposals. Surfaced through the typed
  grpc_client wrapper and policy.local's POST /v1/proposals 202 body.
  Closes #1094.
- PolicyChunk + StoredDraftChunk + DraftChunkPayload: add
  validation_result (gateway prover verdict, populated by #1097) and
  rejection_reason (operator free-form text). Both plain strings; no
  enums, no parsing on the read path. Closes #1096.
- RejectDraftChunk now persists the existing reason field into the
  chunk's rejection_reason so it round-trips back to the agent via
  GetDraftPolicy. UndoDraftChunk clears it on the way back to pending
  so consumers cannot read a stale guidance string from a prior reject
  -> re-approve -> undo cycle.

Whole surface stays gated behind agent_policy_proposals_enabled. Two
focused tests cover the round-trip and the undo-clears guarantee.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* feat(sandbox): add /v1/proposals/{id} and /wait long-poll to policy.local

The agent feedback channel back from policy.local. Two new routes let
the in-sandbox agent learn its proposal's outcome on a single blocking
HTTP call — zero LLM tokens during the wait.

- GET /v1/proposals/{chunk_id} returns the chunk's current state in one
  gateway call.
- GET /v1/proposals/{chunk_id}/wait?timeout=<s> blocks until the chunk
  transitions out of pending. Default 60s, clamped [1, 300]. Agent
  re-issues on timeout to extend.

Response carries the chunk's status plus the two feedback fields shipped
in the prereq commit: rejection_reason (free-form reviewer text) and
validation_result (gateway prover verdict, empty until #1097). On
timeout: same shape with timed_out: true so the agent can disambiguate
without parsing.

Wait handler short-polls GetDraftPolicy every 1s inside the request with
a tokio::time::Instant deadline. One gateway connection is opened per
request and reused across all polls, so a 60s wait does one TLS
handshake instead of sixty. A future commit can swap the loop body for
a tokio::sync::broadcast driven by a watcher task — the agent-visible
contract (URL, query, response shape) is independent of the polling
implementation.

All routes stay behind agent_policy_proposals_enabled. Closes #1092.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* docs(sandbox): teach policy_advisor skill the wait + redraft loop

The agent-facing instructions for the feedback loop. The endpoints
exist; this is the doc that makes them usable.

policy_advisor.md gains:

- API entries for GET /v1/proposals/{chunk_id} and /wait?timeout=<s>,
  including the field semantics (status, rejection_reason,
  validation_result, timed_out).
- A note on the submit response's accepted_chunk_ids /
  rejection_reasons split so the agent handles partial acceptance.
- Step 6 saves the chunk_ids and addresses any submit-time rejections
  before waiting.
- Step 7 walks the four wait outcomes: approved (retry, with the
  honest "may still fail" caveat), rejected (read rejection_reason
  AND validation_result; address whichever has content), still-pending
  with timed_out (re-call), non-2xx (surface, do not retry).

skills.rs gains two assertions on the skill content so a future edit
cannot drop the wait endpoint or the rejection_reason directive
silently.

Closes #1095.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* test(policy-advisor): add end-to-end smoke for the agent feedback loop

A focused smoke that exercises the new policy.local /wait endpoint
on a live gateway + sandbox, separate from the existing no-LLM
regression harness (which still drives the OLD retry-with-bash-loop
recovery pattern).

Two flows:

- Flow A — approve-and-retry: agent submits, /wait blocks, host runs
  `openshell rule approve`, /wait returns status=approved. Confirms
  the happy path round-trip latency.
- Flow B — reject-with-guidance: agent submits, /wait blocks, host
  runs `openshell rule reject --reason "..."`, /wait returns
  status=rejected with the exact reviewer text in rejection_reason.
  Confirms the free-form guidance contract round-trips through the
  agent feedback channel.

No GitHub credentials needed — proposals are synthetic and never
trigger outbound traffic. Both flows expect agent_policy_proposals_enabled=true
and a running gateway.

Adds three cases to sandbox-runner.sh: submit-test-proposal (no GH
deps), proposal-status, proposal-wait. The existing put-file and
submit-proposal cases used by test.sh are untouched.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(policy-advisor): surface real CLI errors from wait-smoke preflight

The preflight piped openshell's stderr to /dev/null and relied on jq to
default the missing setting key to "<unset>", but under `set -euo
pipefail` a non-zero exit from openshell makes the whole pipeline fail
and the command substitution exits the script silently before the
intended fail() message can print.

Capture stderr explicitly, check the CLI exit code, and surface the
real error plus the expected fix (port-forward + gateway add + select)
when the CLI cannot reach the gateway.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(policy-advisor): pass --json to settings get in wait-smoke preflight

`openshell settings get --global` defaults to a human-readable table;
jq cannot parse it and the preflight died with a numeric-literal error.
Pass --json so jq gets actual JSON. Also touched up the suggested
recovery commands in the preflight error to match the real CLI shape
(`gateway add <endpoint> --name <name>` and the env-var override
warning).

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(policy): dedup draft chunks only in mechanistic mode; return effective id

The smoke harness for the agent feedback loop caught a real bug in the
gateway: SubmitPolicyAnalysis's response carried a chunk_id that was
never persisted whenever the SQL ON CONFLICT path fired. Two failure
modes, both load-bearing:

- Agent-authored proposals targeting the same host/port/binary
  (e.g. the redraft-after-rejection loop) silently folded into one row
  and any RejectDraftChunk by the new chunk_id failed with "chunk not
  found." Latent since #1151, surfaced by #1094 returning chunk_ids.
- Mechanistic mode had the same class of bug — the dedup fold-in is
  the intended behavior there, but the response still advertised the
  newly-generated UUID instead of the existing row's id. Less visible
  because no current caller reads mechanistic chunk_ids back, but the
  proto contract was violated either way.

Fix in three parts:

- put_draft_chunk now takes Option<&str> dedup_key explicitly and
  returns the effective row id (via RETURNING). None binds NULL to the
  dedup_key column, which bypasses the partial-index ON CONFLICT path
  entirely. Caller-decides semantics replace store-side magic.
- handle_submit_policy_analysis picks dedup_key per chunk using an
  allowlist (only "mechanistic" dedups) and pushes the returned
  effective_id to accepted_chunk_ids. New modes default to no-dedup so
  a misconfigured caller cannot silently lose proposals.
- The two-copy draft_chunk_dedup_key helper consolidated to one
  observation_dedup_key in policy_store.rs with a doc comment.

Tests:

- agent_authored_submits_for_same_endpoint_do_not_dedup pins the
  redraft-loop contract: two intentional submissions with the same
  host/port/binary get distinct chunk_ids, both findable via
  GetDraftPolicy, both rejectable by id.
- mechanistic_submits_for_same_endpoint_dedup_into_one_chunk locks in
  the observation-mode dedup AND asserts both submits return the same
  effective_id — would have caught the deeper bug.

Proto: SubmitPolicyAnalysisRequest.analysis_mode doc updated to
describe the actual semantics (mechanistic dedups, agent_authored and
unknown modes do not).

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* docs(examples): retarget policy-management demo at the /wait endpoint

The narrated demo (examples/agent-driven-policy-management) has been the
public face of this feature since #1151. Its agent prompt told Codex to
retry the original PUT every few seconds for up to 120 seconds — a
polling workaround for the missing /wait endpoint that this branch
shipped. Update the demo to exercise /wait so the canonical reading of
the feature reflects the actual UX win.

- agent-task.md: step 4 is now "call /wait, branch on status" with the
  three outcomes spelled out (approved → retry once; rejected → read
  rejection_reason and revise or stop; pending+timed_out → re-issue
  /wait once, do NOT busy-loop or shorten the timeout). Also makes
  explicit that the demo submits one rule per proposal so
  accepted_chunk_ids[0] is the safe single id to wait on.

- demo.sh: header docstring rewritten as a six-step loop that mirrors
  the README. narrate_sandbox_workflow drops its parallel numbering and
  uses bullets (the runtime narration is the agent's sub-actions, not a
  separate decomposition of the loop). Approve step header and success
  message now reference /wait waking the agent, not "policy hot-reload
  retry."

- README.md: top-of-file flow expanded from 5 to 6 steps to include the
  /wait call and chunk_id capture; "Going further" section now describes
  both regression scripts and the boundary between them (real-GitHub
  retry vs. synthetic /wait wire test). Slow-path qualifier corrected
  from "image pull on first run" to "sandbox cold-start (SSH bring-up
  plus Codex install)".

- wait-smoke.sh header rewritten to make it unambiguous this is a
  regression, NOT a tutorial, with explicit prereq commands instead of
  prereq descriptions, and a pointer at demo.sh for the narrated story.

No code paths change; this is the readability pass.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(examples): pass --yes on demo.sh's global setting writes

Global setting updates require explicit confirmation in non-interactive
mode; demo.sh's enable_agent_proposals and the cleanup restore path
were missing --yes and hard-failed the preflight. Pre-existing issue
that surfaced now that more of the demo runs through this path.

No other behavior change.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* feat(sandbox): emit OCSF audit events for policy proposal lifecycle

The demo's policy decision trace previously showed only the proxy
enforcement story (HTTP:PUT DENIED, CONFIG:LOADED, HTTP:PUT ALLOWED).
It was silent about who proposed what or who decided what — the
audit-trail receipts for the agent feedback loop were missing.

policy.local now emits sandbox-side OCSF events at the observation
moments, into the same stream as the existing CONFIG:LOADED:

- CONFIG:PROPOSED on submit_proposal acceptance. Per accepted chunk:
  the message names the chunk_id, target endpoint, L7 method/path, and
  binary so the trace correlates against the inbox card via chunk_id.
- CONFIG:APPROVED on /wait observation of approved status.
- CONFIG:REJECTED on /wait observation of rejected status. Carries the
  reviewer's free-form rejection_reason in the message AND as an
  unmapped field, both sanitized (control chars stripped, capped at
  200 chars with an ellipsis marker). The agent still reads the raw
  text via GET /v1/proposals/{id}; sanitization is audit-side only,
  per AGENTS.md's no-secrets-in-OCSF rule.

The submit path defends the audit_summaries / accepted_chunk_ids
index pairing against a future gateway change that compresses past
rejected chunks (the proto doesn't promise 1:1 ordering with the
request). Today client-side validation makes the lengths always
match; if they don't, the pairing falls back to a generic per-id
event rather than mis-attribute.

The wait handler's emit site fires once per terminal-status
observation. Multiple concurrent waiters on the same chunk would
each emit one event; acceptable for single-waiter-per-chunk demos
and the right place to dedup is the SIEM.

demo.sh's trace filter now surfaces the four CONFIG: events
alongside HTTP:PUT, so the trace at the end of every run tells the
full story from deny to allow via propose -> approve.

wait-smoke.sh's prereq notes recommend redirecting kubectl
port-forward output so its "Handling connection for 8090" lines
don't bleed into demo narration.

Three new unit tests on the sandbox-side helpers — summary builder
happy path, fallback, and the rejection_reason sanitizer.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* feat(policy): /wait awaits local policy reload; demo auto-approves redrafts

Three things in one commit, all surfaced by running the demo end-to-end
against a real gateway and finding the agent had to draft a broader
second proposal.

1. /wait race fix. Previously /wait returned `approved` the moment it
   observed the gateway's chunk status flip, but the local supervisor
   reloads policy on its own poll cycle (~10s in practice). The agent's
   retry would race the reload and hit the still-old policy, getting
   denied. Codex then drafted a broader rule and re-submitted — sound
   agent behavior, but not what /wait should provoke. Now /wait captures
   the local policy version at start, and after observed-approved waits
   for the supervisor to load a strictly-newer version before returning.
   Bounded by the caller's deadline; best-effort return if the deadline
   elapses without the version bumping. Two new unit tests pin the
   happy path and the deadline-clamped fallback.

2. demo.sh auto-approve loop. Replaces approve_when_pending +
   wait_for_agent with one approve_pending_until_agent_exits function
   that keeps watching for pending chunks and approving them until the
   agent process exits (or the configured timeout). Defense in depth
   against future redraft scenarios for any reason; today (post-fix #1)
   the agent should only submit one proposal per task, but we don't
   want to hang silently if it does submit more.

3. UX. Step headers now carry "[t+1.2s]" relative timestamps so reading
   the run output makes latency visible (the demo's whole point is the
   wait is cheap — surface that). A spin_wait helper renders an ASCII
   spinner during the watch loop so the demo never looks frozen on a
   TTY. Falls back to plain sleep on non-TTY contexts.

Closes the race condition diagnosed from the trace timing where the
gateway approved at t+0, sandbox observed at t+0.3s, but the supervisor
didn't load v2 until t+9.4s — well after the agent had already retried
and been denied.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(sandbox): /wait detects policy reload by content, not the schema version

The previous attempt at the /wait-after-approve race fix compared
`SandboxPolicy.version` between /wait start and the policy reload —
but that field is the *schema* version (constant 1), not a revision
counter. Every comparison was `current(1) > baseline(1) == false`, so
the wait blocked until the agent's 300s timeout regardless of whether
the supervisor had actually reloaded. The demo SSH connection then
timed out around the 240s mark.

Diagnosed from a live run's OCSF trace: supervisor pulled v2 at
+8.5s after approval (CONFIG:LOADED), but the sandbox-side
CONFIG:APPROVED that my /wait emits didn't fire until +304s — exactly
at the 300s deadline.

Fix: compare the whole policy via prost's derived PartialEq. Any
field change (network_policies map being the only one that actually
mutates today) flips equality. A clone-per-200ms-tick on a few-KB
proto is cheap inside the bounded wait window.

Tests rewritten to match the new contract: the supervisor-reload
fixture now keeps `version: 1` constant and changes `network_policies`
contents, mirroring the exact failure mode from the live run.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(examples): redact tokens with python literal-string replace, not sed

The sed-based redact_log in demo.sh broke when one of the auth tokens
contained a character that conflicted with sed's pattern parser
("unterminated substitute pattern" on the Codex JWT). The whole log
tail then blanks on failure, hiding the very failure context we're
trying to surface.

Switch to a python subprocess that takes the tokens via argv and does
literal str.replace. No regex, no delimiter games, no truncation.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(sandbox): scope /wait reload check to the approved rule

Reviewer (John Myers) flagged two failure modes in the prior whole-policy
fingerprint approach used by policy.local /wait:

- False sleep: when the supervisor reloads between two /wait calls
  (the skill tells the agent to re-issue on timed_out), the new call
  snapshots the already-updated policy as baseline and burns the full
  timeout waiting for a change that never comes.
- False wakeup: any unrelated reload (other agent's approval, settings
  change) flips the diff, but the chunk's actual rule may not be loaded
  yet — the agent retries and hits policy_denied for no real signal.

Replace the diff with rule-coverage. New public helper
openshell_policy::policy_covers_rule reuses endpoints_overlap (so it
matches add_rule's merge semantics, including the fold-into-existing-key
case) plus an L7 allow check on method/path (so an existing endpoint
that doesn't yet contain the proposed method doesn't signal coverage).

Add policy_reloaded: true|false to the /wait response on approve, with
a 500ms floor on the reload-wait phase so approvals arriving near the
deadline still get a fair shot at reloaded=true. Update the
policy_advisor skill to branch on it: reloaded=true → retry;
reloaded=false → re-issue /wait once with timeout=30, then surface to
user. Don't loop tightly.

Tests:
- 9 new unit tests in openshell-policy pinning coverage semantics
  (L4-only, L7 method gap, fold-into-existing-key, empty binaries).
- 4 new tokio tests in policy_local mirroring John's exact scenarios.
- wait-smoke.sh asserts policy_reloaded=true on Flow A.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(server): make GetDraftPolicy dual-auth so /wait works under OIDC

policy.local calls GetDraftPolicy from inside the sandbox supervisor
via the sandbox gRPC client, which authenticates with the shared
x-sandbox-secret. GetDraftPolicy was listed only in the Bearer-auth
scope table (config:read) and was not in SANDBOX_SECRET_METHODS or
DUAL_AUTH_METHODS, so OIDC-enabled gateways rejected those calls and
the /wait long-poll surfaced gateway_lookup_failed. Local/no-OIDC
setups happened to work because the auth check is short-circuited.

Add GetDraftPolicy to DUAL_AUTH_METHODS, matching the existing
GetSandboxConfig pattern (called by both CLI reviewer surfaces with
Bearer and the sandbox supervisor with x-sandbox-secret). Dual-auth
short-circuits the scope check for sandbox-secret callers, so the
config:read entry in authz.rs continues to gate Bearer-only flows.

Mirror the openshell_get_sandbox_config_is_dual_auth assertion for
GetDraftPolicy.

Note: ssh_handshake_secret is server-wide, not per-sandbox, so a
sandbox-secret caller can today name any sandbox in a SubmitPolicyAnalysis
request — and now in a GetDraftPolicy request. The exposure is
symmetric with the existing SANDBOX_SECRET_METHODS pattern. Filed as a
follow-up: per-sandbox secret binding, tracked separately.

Signed-off-by: Alexander Watson <zredlined@gmail.com>

* fix(ci): address rebased check failures

Signed-off-by: Alexander Watson <zredlined@gmail.com>

---------

Signed-off-by: Alexander Watson <zredlined@gmail.com>
2026-05-13 15:24:18 -07:00
Piotr Mlocek 0797fefa44 feat(gateway): add local-domain service routing (#1101) 2026-05-12 17:44:55 -07:00
Aaron Erickson 🦞andJohn Myers 9ea94b645d fix(sandbox): rewrite messaging credential placeholders (#1286)
* fix(sandbox): rewrite credential placeholders in websocket text frames

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket credential rewrite

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(sandbox): add websocket l7 inspection and compression

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket upgrade validation

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(sandbox): cover route-selected websocket upgrades

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): harden websocket negotiation parsing

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(sandbox): add websocket conformance relay matrix

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* test(e2e): add websocket conformance lane

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(policy): support websocket incremental rules

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(policy): enable websocket credential rewrite updates

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(cli): make websocket rewrite endpoint-local

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* feat(sandbox): support graphql websocket policy

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(policy): allow private IPs for websocket endpoints

* feat(sandbox): rewrite REST credential placeholders

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* refactor(sandbox): generalize credential aliases

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): rewrite encoded form credentials

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(sandbox): close websocket policy and provider alias gaps

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

* fix(e2e): route websocket probe through host gateway

* fix(e2e): stabilize websocket probe handshake

* fix(e2e): exercise websocket probe through proxy

* ci: remove websocket conformance workflow

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Co-authored-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-05-11 22:40:44 -07:00
Piotr Mlocek 5abc36c461 feat(relay): route forwarding through ForwardTcp (#1029) 2026-05-11 21:23:37 -07:00
John T. Myers 1d3b741ee3 feat(providers): support sandbox provider attach lifecycle (#1242)
* feat(providers): support sandbox provider attach lifecycle

Closes #1171

Adds sandbox provider list, attach, and detach API/CLI support while keeping provider policy and credential resolution derived from current sandbox attachments.

* fix(providers): refresh sandbox provider credentials

Adds provider environment revisions and generation-scoped sandbox credential snapshots so future SSH and exec launches pick up provider attach, detach, and credential updates without mutating already-running processes.

Also blocks provider deletion while attached to prevent stale sandbox provider references.

* fix(providers): serialize sandbox object mutations

* test(providers): cover sandbox provider attach lifecycle

* test(providers): accept versioned credential placeholders
2026-05-08 11:14:44 -07:00
Mrunal Patel 1f35abbefb feat(sandbox): add Kubernetes user namespace isolation (hostUsers: false) (#983)
Add opt-in support for Kubernetes user namespace isolation on sandbox
pods. When enabled, container UID 0 maps to an unprivileged host UID
and capabilities become namespaced, providing defense-in-depth for the
supervisor process.

Configuration is two-layered: a cluster-wide default via
OPENSHELL_ENABLE_USER_NAMESPACES (default false) and a per-sandbox
override via the new `user_namespaces` field on SandboxTemplate.

When user namespaces are active, the pod security context is extended
with SETUID, SETGID, and DAC_READ_SEARCH capabilities to match the
bounding-set requirements inside a user namespace.

Introduces SandboxPodParams struct to replace long argument lists on
sandbox_to_k8s_spec and sandbox_template_to_k8s.

Validated end-to-end on OCP 4.22 (K8s 1.35.3, CRI-O 1.35, RHEL
CoreOS, kernel 5.14) with full SSH tunnel and non-identity UID mapping.
2026-05-08 12:39:48 -04:00
John T. Myers cdb1de59ba feat(providers): add custom profile registry (#1170)
Add custom profile registry. Allow attaching custom profiles at sandbox start.
2026-05-07 09:41:25 -07:00
John T. Myers 043bde279a feat(providers): add profile-backed policy composition (#1037)
Foundation for providers v2. Add provider profiles and provider profile composition with user policies.
2026-05-04 18:34:33 -07:00
John T. Myers 6b21804258 feat(policy): add GraphQL L7 inspection (#1083)
Support GraphQL L7 policies
2026-05-04 11:50:09 -07:00
vince-brisebois 2adddaa6b2 feat: Adding qemu vm driver support with GPU pass-through (#992)
* Adding qemu vm driver support with GPU pass-through

Signed-off-by: Vincent Caux-Brisebois <vcauxbrisebo@nvidia.com>

* Add GPU rootfs variant, harden VFIO binding, and fix networking and supervisor reliability issues discovered during GPU VM bring-up.

Signed-off-by: Vincent Caux-Brisebois <vcauxbrisebo@nvidia.com>

---------

Signed-off-by: Vincent Caux-Brisebois <vcauxbrisebo@nvidia.com>
2026-04-29 09:18:27 -07:00
John T. Myers d414e69a20 refactor(server): unify policy persistence in objects table (#972)
* refactor(server): unify policy persistence in objects table

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

* fix(server): clean sandbox-owned records on reconcile delete

* refactor(server): use protos for stored policy records

* refactor(server): move policy persistence into policy_store

* fix(server): restore compute runtime merge compatibility

* fix(server): validate draft chunk sandbox ownership

---------

Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
2026-04-28 16:15:14 -07:00
Derek Carr 5e28ea3a4b feat(server): add object meta convention to top-level objects (#919)
- adds filterable label selectors on resources

Closes #864

Signed-off-by: Derek Carr <decarr@redhat.com>
2026-04-27 07:16:07 -07:00