The Maturin-based wheel packaging was a historical remnant from when the local gateway launch path and OpenShell CLI were coupled in one binary. The gateway and CLI now ship as standalone artifacts, so the Python distribution should contain only the SDK.
Build a single platform-independent setuptools wheel, verify that it cannot contain native code or an openshell entry point, and simplify the release jobs and documentation for SDK-only PyPI installs.
Signed-off-by: Simon Scatton <sscatton@nvidia.com>
* feat(provider): add ability to request token exchange instead of client credentials as OAuth grant_type
Signed-off-by: Gordon Sim <gsim@redhat.com>
* test(proxy): add further tests for token exchange
Signed-off-by: Gordon Sim <gsim@redhat.com>
* test(provider): add runnable example for token exchange
Signed-off-by: Gordon Sim <gsim@redhat.com>
* test(e2e): cover Podman token exchange grants
Signed-off-by: Gordon Sim <gsim@redhat.com>
* refactor(oauth): extract duplicated functionality from server and supervisor
Signed-off-by: Gordon Sim <gsim@redhat.com>
* fix(provider): evict nearest-to-expiry entry from intermediate token cache
Signed-off-by: Gordon Sim <gsim@redhat.com>
* doc(supervisor): add podman example for token exchange
Signed-off-by: Gordon Sim <gsim@redhat.com>
* fix(provider): withhold token-exchange subject credentials
Signed-off-by: Gordon Sim <gsim@redhat.com>
---------
Signed-off-by: Gordon Sim <gsim@redhat.com>
* 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>
* fix(core): trust public root CAs alongside the sandbox mTLS CA
The supervisor gRPC client only trusted the CA configured via
OPENSHELL_TLS_CA, since tonic ClientTlsConfig starts with an empty root
store unless with_native_roots()/with_webpki_roots() is also enabled.
Deployments where the gateway server certificate is issued by a public CA
(e.g. cert-manager against an ACME issuer) caused every supervisor
connection to fail the TLS handshake with "UnknownCA", since the sandbox
mTLS CA and the server cert issuer were no longer the same.
Enable both native and webpki roots in addition to the configured CA.
tonic root store is a union of all configured sources, so this does not
weaken verification for existing self-signed deployments. webpki-roots
(compiled in) is enabled alongside native-roots since the supervisor
binary may run in minimal sandbox images without a populated system CA
bundle.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* feat(helm): support external cert-manager issuers and OpenShift Route passthrough
Add certManager.serverIssuerRef/clientIssuerRef so the gateway and mTLS
client certificates can be issued by a real Issuer/ClusterIssuer (e.g.
ACME) instead of only the chart built-in self-signed CA.
Add openshiftRoute template for exposing the gateway via a TLS
passthrough Route so the gateway keeps terminating its own TLS/mTLS.
The server Certificate excludes internal-only SANs (cluster-local,
localhost, loopback) when an external issuer is configured, since ACME
issuers reject those per CA/Browser Forum baseline requirements. A
template-time fail guard catches the misconfiguration at helm install
time rather than asynchronously at cert-manager issuance time.
Includes Helm unittest coverage for both issuerRef overrides and Route
rendering, plus a CI values overlay for lint coverage.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* docs: document cert-manager external issuer and OpenShift Route
Update managing-certificates.mdx with the serverIssuerRef workflow and
install-time validation behavior. Add a production section to the
OpenShift guide covering passthrough Route with a real certificate.
Regenerate Helm README for new certManager and openshiftRoute values.
Sync debug-openshell-cluster skill with new troubleshooting steps for
ACME issuance failures and supervisor UnknownCA from mismatched CAs.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* fix(helm,core): address PR review feedback on cert-manager external issuer
Addresses all five blocking review items from #2468:
1. Remove .with_native_roots() from supervisor gRPC client -- the
supervisor runs inside the user-selected sandbox image, so the
image CA bundle is not operator-controlled. Keep .with_webpki_roots()
(compiled-in, not user-controlled) alongside the configured CA.
2. Fail at render time when serverIssuerRef.name is set but
clientCaFromServerTlsSecret is still true. Add negative Helm test.
3. Remove clientIssuerRef -- changing only clientIssuerRef breaks both
directions because trust bundles are not modeled separately. Change
serverIssuerRef.kind default from ClusterIssuer to Issuer.
4. Add server.oidc.issuer and server.oidc.audience to the documented
OpenShift production Helm command. Add Access Control prerequisite.
5. Fail at render time when openshiftRoute.enabled and disableTls are
both true. Add negative Helm test.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* fix(drivers): strip GATEWAY_TLS_SERVER_NAME from Docker and Podman env
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* fix(helm,drivers): guard default clientCaSecretName and add env-strip tests
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
* feat(tls): SNI-based dual certificate for internal and external server TLS
Split the gateway server certificate into two: an internal cert issued by
the chart's own CA (for supervisor connections via cluster-local SANs) and
an external cert issued by an operator-configured Issuer such as ACME/Let's
Encrypt (for CLI and Route access via public SANs).
The gateway uses SNI-based certificate selection: connections whose SNI
hostname matches external_server_names receive the external cert; all
others (including those with no SNI) receive the internal cert.
Security improvement: remove .with_webpki_roots() from the supervisor
gRPC client so supervisors trust only the chart CA, closing a MITM vector
via publicly-trusted certificates in user-supplied container images.
Key changes:
- Add DualCertResolver with SNI-based cert selection and full test coverage
- Add external_cert_path, external_key_path, external_server_names to TlsConfig
- Validate partial external cert config (error on cert-without-key or vice versa)
- Validate empty external_server_names when external cert is configured
- Split cert-manager templates into internal + external Certificate resources
- Add Helm guards for misconfigured external issuer (empty serverDnsNames,
internal-only SANs with external issuer, conflicting clientCaFromServerTlsSecret)
- Update gateway-config.mdx, managing-certificates.mdx, openshift.mdx docs
- Update debug-openshell-cluster skill for dual-cert troubleshooting
Signed-off-by: Pi Agent <agent@openshell.local>
* fix(drivers): strip GATEWAY_TLS_SERVER_NAME in VM driver and correct comments
Add the same GATEWAY_TLS_SERVER_NAME environment stripping to the VM
compute driver that Docker, Podman, and Kubernetes drivers already
perform. Without this, a sandbox user on the VM driver could override
the TLS server name the supervisor verifies.
Fix stale comments in Docker and Podman drivers that referenced
'with WebPKI roots trusted' — WebPKI roots are explicitly not trusted
after the tls-webpki-roots removal.
Use tls-ring instead of bare channel for tonic in openshell-core so the
TLS API (ClientTlsConfig, Endpoint::tls_config) is available without
pulling in any root certificate store.
Signed-off-by: Pi Agent <agent@openshell.local>
* fix(tls,helm): wildcard SNI matching and Route host validation
Add RFC 6125 single-level wildcard matching to DualCertResolver so
external_server_names entries like *.example.com correctly match SNI
hostnames like gw.example.com. Previously only exact matches worked,
silently falling back to the internal cert for wildcard configurations.
Add a Helm fail guard in route.yaml that rejects openshiftRoute.host
values not listed in certManager.serverDnsNames when an external issuer
is configured — catches cert/route hostname mismatches at install time
instead of at TLS connect time.
Quote the host field in route.yaml for robustness.
Signed-off-by: Pi Agent <agent@openshell.local>
* fix(helm): address blocking review items — client-CA guard, wildcard Route, serverIssuerRef gate
1. Remove the obsolete guard rejecting serverIssuerRef + clientCaFromServerTlsSecret=true.
The internal server certificate is always signed by the chart CA (the same
CA that signs the client cert), so clientCaFromServerTlsSecret=true is
correct — its filtered ca.crt is exactly the right trust anchor. The old
workaround (mounting openshell-ca-tls directly) unnecessarily exposed the
CA private key to the gateway container. Remove the client-CA overrides
from docs, CI overlay, and production examples.
2. Route host validation now supports wildcard certificates per RFC 6125:
single-level wildcards like *.example.com match gateway.example.com but
not deep.sub.example.com. Require an explicit openshiftRoute.host when
an external issuer is configured — without one, OpenShift generates a
hostname absent from serverDnsNames.
3. Reject serverIssuerRef.name when certManager.enabled is false — the
external certificate, its Secret mount, and the gateway TLS config all
require cert-manager to be enabled.
Validated on ROSA (dev.dyee.p3) with branch-built images:
- Fresh install with letsencrypt-prod ClusterIssuer
- SNI dual-cert: external hostname served Let's Encrypt cert
- Supervisor mTLS via internal cert path: ConnectSupervisor accepted
- Client CA volume: filtered ca.crt from internal server secret (no key)
- CLI connected via Route + OIDC
Helm tests: 81 pass across 7 suites.
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
---------
Signed-off-by: Jesse Jaggars <jjaggars@redhat.com>
Signed-off-by: Pi Agent <agent@openshell.local>
* fix(sbom): detect sha256 hashes in expression-form licenses in needs_fix
CycloneDX allows licenses as either {"license": {"id": "..."}} or
{"expression": "..."}. needs_fix only checked the license form, so
expression entries with sha256 hashes were silently skipped.
Add expression-form check to needs_fix, mirroring the fix in
extract_licenses (#1898). Add tests covering both forms.
* fix(sbom): align license checks with current test layout
Reuse needs_fix from sbom:check so expression-form hashes are detected. Fold coverage into the existing SBOM test module and task introduced on main.
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
---------
Signed-off-by: John Myers <9696606+johntmyers@users.noreply.github.com>
The supervisor binary runs inside sandbox images whose libc and glibc
version are unknown at build time, so it must be statically linked. Add
SUPERVISOR_LIBC to select between the default musl variant and a new
glibc-static variant that builds the GNU target with +crt-static.
glibc-static has no cross-compile path: zig cc accepts -static for
*-linux-gnu targets and emits a dynamically linked binary anyway. The
staging script therefore refuses a cross-arch request for that variant
rather than silently degrading linkage, and requires a native
per-architecture build.
Add verify-static-binary.sh, run after every supervisor build in both the
staging script and CI so linkage cannot regress unnoticed for either
variant. It inspects via readelf (or greadelf/llvm-readelf) and fails closed
rather than trusting the tool's exit status: every inspection must produce no
diagnostics, the input must be an executable ELF (ET_EXEC, or ET_DYN with
DF_1_PIE) whose PT_LOAD segments all lie within the file, whose dynamic table
agrees with PT_DYNAMIC, and which carries no PT_INTERP and no DT_NEEDED. That
rejects a dynamically linked, truncated, corrupt, non-ELF, or shared-object
input that naive parsing would misread as static. Hosts without any inspector
(e.g. macOS, which ships no binutils) skip with a warning; Linux, including
CI, requires one and fails closed.
No image or release workflow builds the glibc-static variant, so add a
dedicated supervisor-static-validate workflow that builds it on both
architectures and runs the verifier. rust-native-build.yml uses self-hosted
runners, which reject pull_request-triggered jobs, so it validates in the merge
queue and on pushes to main that touch the build inputs, plus a nightly
schedule, so the GNU + crt-static build branch cannot regress unnoticed.
The default is unchanged, so image, release, and CI behavior is identical.
Selecting glibc-static statically links LGPL glibc into a redistributed
binary, which is why it is opt-in.
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
Co-authored-by: Mrunal Patel <mrunalp@gmail.com>
* chore(ci): disable telemetry in internal test runs
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
* test(ci): remove brittle telemetry wiring test
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
* docs: trim CI telemetry guidance
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
* test(e2e): share telemetry default with OpenShift
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
---------
Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
* 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>
The Kubernetes driver's default workspace PVC never set storageClassName,
so on clusters with no default StorageClass the PVC stayed Pending and
sandbox creation failed.
Add a workspace_storage_class option to KubernetesComputeConfig, wired
through SandboxPodParams into the generated volumeClaimTemplates. When
non-empty it sets storageClassName; empty preserves the current behavior
of relying on the cluster default StorageClass.
Expose it via the OPENSHELL_K8S_WORKSPACE_STORAGE_CLASS env var on both
the standalone driver and the embedded gateway runtime defaults, and via
the server.workspaceStorageClass Helm value.
Closes#2442
Signed-off-by: lr90 <qiuweimin@matrixorigin.cn>
The OpenClaw community sandbox was removed from NVIDIA/OpenShell-Community
in PR #73 (May 16, 2026). The Docker Compose tutorial and docker-compose.yml
comment still referenced the stale --from openclaw command and GHCR image.
Replace the broken OpenClaw tab with a redirect to the NemoClaw Quickstart,
which is the supported path per docs/about/supported-agents.mdx. Remove the
stale pre-pull command for the removed image.
Fixes#2404
Signed-off-by: Matias Schimuneck <schimuneck.matias@gmail.com>
* feat(kubernetes): add sidecar supervisor topology
Add the Kubernetes sidecar supervisor topology, its Helm/Skaffold configuration, topology documentation, and sidecar e2e matrix coverage. Skip root-only sandbox identity rewriting when process enforcement is network-only so the low-permission sidecar process container can start successfully.
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(supervisor): avoid similar process id names
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(supervisor): avoid similar process id names
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(sandbox): avoid similar proxy id names
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* docs(kubernetes): clarify sidecar topology limits
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): keep sidecar process leaf capless
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): refresh sidecar provider env snapshots
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* test(supervisor): align hot-swap identity regression
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): stage sidecar mtls files before proxy chown
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): simplify sidecar supervisor topology
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* chore(helm): reuse sidecar skaffold values
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(supervisor): avoid similar iptables helper names
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(e2e): harden kube gateway wrapper setup
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(supervisor): avoid nft batch rollback on OCP
Run nftables setup as individual commands so optional conntrack and log expressions can fail without rolling back required table, chain, and reject rules.
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): preserve process identity in sidecar topology
Render sidecar pods with a shared process namespace, keep binary-aware network policy enabled, and move Kubernetes sidecar settings under the nested sidecar config table.
Also apply unprivileged Landlock/seccomp setup in NetworkOnly supervisor mode so sidecar topology keeps sandbox child hardening without privileged process setup.
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* refactor(kubernetes): replace sidecar snapshots with control socket
Coordinate sidecar policy and provider bootstrap over a local Unix socket so the process leaf no longer reads policy/provider snapshot files.
Report entrypoint startup through the control channel and keep gateway credentials confined to the network sidecar.
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* feat(kubernetes): support relaxed sidecar network identity
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(sandbox): satisfy sidecar clippy lint
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* refactor(kubernetes): standardize topology naming
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(sandbox): satisfy linux clippy timeout import
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): support kata sidecar on ipv4 pods
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): satisfy linux clippy for sidecar fallback
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* chore(kubernetes): remove stale supervisor topology references
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): enable sidecar binary policy inspection
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): harden sidecar control boundary
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(kubernetes): couple sidecar supervisor lifecycles
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
---------
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Co-authored-by: Seth Jennings <sjenning@redhat.com>
* feat(policy): accept numeric UIDs in sandbox process identity validation
Allow run_as_user and run_as_group to be either the literal 'sandbox'
or a numeric UID/GID within [1000, 2_000_000_000]. This removes the
hard dependency on a baked-in 'sandbox' user in container images,
enabling compute drivers to inject resolved UIDs at sandbox creation.
Phase 1 of #1959.
Signed-off-by: Seth Jennings <sjenning@redhat.com>
* feat(supervisor): accept numeric UIDs for process identity dropping
Allow run_as_user and run_as_group to be numeric UIDs/GIDs, removing
the hard dependency on a baked-in 'sandbox' user in container images.
Changes:
- validate_sandbox_user(): accepts numeric UIDs without passwd lookup
(logs OCSF event); keeps passwd check for "sandbox" name; rejects
non-numeric non-sandbox strings that fail passwd lookup
- prepare_filesystem(): passes numeric UIDs/GIDs directly to chown()
instead of requiring a passwd entry
- drop_privileges(): resolves numeric UIDs/GIDs directly via UID::from_raw
/ Gid::from_raw; skips initgroups when target uid matches current euid;
uses guard conditions before setgid/setuid calls
- session_user_and_home(): falls back to ("{uid}", "/sandbox") for
numeric UIDs, avoiding a passwd lookup that will fail
Re-exports MIN_SANDBOX_UID and MAX_SANDBOX_UID from openshell-policy
so callers have consistent range constants.
Phase 2 of #1959.
Signed-off-by: Seth Jennings <sjenning@redhat.com>
* feat(driver-kubernetes): resolve sandbox UID/GID from config or OpenShift SCC annotations
Phase 3 of the numeric-UID plan: allow operators to specify explicit
sandbox_uid/sandbox_gid in Kubernetes driver config, auto-detect from
OpenShift SCC namespace annotations, and propagate resolved values to
supervisor container env vars and PVC init container securityContext.
Changes:
- Add sandbox_uid/sandbox_gid fields to KubernetesComputeConfig
- Add SANDBOX_UID/SANDBOX_GID env var constants to openshell-core
- Implement resolve_sandbox_identity() to fetch namespace annotations
and auto-detect OpenShift SCC UID ranges (sa.scc.uid-range)
- Pass resolved UID/GID through SandboxPodParams to pod spec builder
- Inject SANDBOX_UID/SANDBOX_GID env vars into supervisor container
- Update PVC init container securityContext with resolved UID/GID
instead of hard-coded root
- Add comprehensive unit tests for resolution logic and annotation
parsing (resolve_sandbox_uid, resolve_sandbox_gid, OpenShift SCC
annotation parsing)
Signed-off-by: Seth Jennings <sjenning@redhat.com>
* feat(driver-vm): add configurable sandbox UID/GID and update docs/examples
Phase 4 of the numeric-UID plan: replace hardcoded SANDBOX_UID (10001)
in VM rootfs preparation with configurable sandbox_uid/sandbox_gid fields.
Changes:
- Add sandbox_uid/sandbox_gid to VmDriverConfig with serde derives
- Pass resolved UID/GID through prepare_sandbox_rootfs_from_image_root
to ensure_sandbox_guest_user which writes /etc/passwd/group/gshadow
- Update BYOC Dockerfile: remove groupadd/useradd, document runtime UID
injection and the ability to skip baked-in sandbox user
- Update gateway-config.mdx: document sandbox_uid/sandbox_gid for both
Kubernetes (with OpenShift SCC autodetection) and VM drivers
- Update sandbox-compute-drivers.mdx: add Sandbox User Identity section
explaining numeric UID support across all compute drivers
- Update rootfs tests to use non-default UIDs, verify config passthrough
Signed-off-by: Seth Jennings <sjenning@redhat.com>
* code review changes
* fix(supervisor): harden tests for restricted CI container environments
Guard tests against CI-specific constraints: root without CAP_SETPCAP,
UIDs with no /etc/passwd entry, and restricted /proc access.
Signed-off-by: Seth Jennings <sjennings@nvidia.com>
Signed-off-by: Seth Jennings <sjenning@redhat.com>
---------
Signed-off-by: Seth Jennings <sjenning@redhat.com>
Signed-off-by: Seth Jennings <sjennings@nvidia.com>
The chart's optional Gateway API ingress only rendered a plaintext HTTP
listener, so the gateway could not be exposed over TLS. Add an HTTPS
listener option that terminates TLS at the Envoy Gateway and forwards
plaintext gRPC to the gateway pod.
- gateway.yaml renders an HTTPS listener with `tls.mode: Terminate` and
`certificateRefs` when `grpcRoute.gateway.listener.protocol=HTTPS`,
keeping the default HTTP listener unchanged. Guards fail the render when
`certificateRefs` is empty or `server.disableTls` is not true (the chart
does not render a BackendTLSPolicy for re-encryption).
- values.yaml adds `grpcRoute.gateway.listener.tls.certificateRefs`.
- ci/values-gateway-tls.yaml exercises the HTTPS branch in lint/render.
- docs/kubernetes/ingress.mdx documents HTTPS setup and clarifies that
Envoy Gateway only terminates TLS (no OIDC SecurityPolicy); client
identity uses OIDC bearer tokens, with the client-credentials grant for
headless agents.
- debug-openshell-cluster skill gains HTTPS-ingress troubleshooting rows.
- Regenerated the chart README values table.
Signed-off-by: Huabing (Robin) Zhao <zhaohuabing@gmail.com>
The certgen hook and cert-manager Certificate template hardcoded
openshell.openshell.svc.cluster.local in server certificate SANs,
breaking deployments in any namespace other than openshell. Use
.Release.Namespace in the templates so the SANs match the actual
service FQDN regardless of the target namespace.
Closes#2060
Signed-off-by: Akram <akram.benaissi@gmail.com>
CycloneDX allows licenses as either {"license": {"id": "..."}} or
{"expression": "MIT OR Apache-2.0"}. The expression form was silently
dropped, producing an empty license field in the CSV output.
* fix(sbom): release lock before sleeping in _rate_limit
time.sleep was called inside the _rate_lock block, blocking all threads
from checking their own domain rate limit while one thread slept.
With _MAX_WORKERS=12 querying crates.io, npm, and pypi concurrently,
this made the thread pool effectively serial.
Move the sleep outside the lock so threads for different domains
can proceed concurrently.
* fix(sbom): reserve next slot in _rate_limit to prevent same-domain races
Use time.monotonic() and store next_req = max(now, last + interval) so
concurrent same-domain callers each get a unique future slot rather than
all sleeping the same amount. Add regression tests for same-domain
spacing and different-domain non-blocking.
* fix(sbom): wire rate-limit tests into test suite via test:sbom task
Rename test_resolve_licenses.py to resolve_licenses_test.py to match
repo *_test.py convention. Add test:sbom task to tasks/test.toml and
include it in the top-level test depends so mise run test picks it up.
* fix(sbom): add SPDX header, fix cross-domain regression test to exercise lock contention
* fix(sbom): replace zip with itertools.pairwise to satisfy ruff lint
* feat(helm): support Deployment kind in HA gateway workloads
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(helm): handle null workload values
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
---------
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* 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>
Drop the vendored deploy/kube/manifests/agent-sandbox.yaml. CI and e2e
scripts now apply manifest.yaml directly from
github.com/kubernetes-sigs/agent-sandbox releases, pinned via
AGENT_SANDBOX_VERSION env (default v0.4.6, overridable) so internal runs
stay reproducible.
Public docs and the helm chart README continue to reference
/releases/latest/download/ — no OpenShell/agent-sandbox support matrix
exists yet (see #1649).
Adds an air-gap note in docs/kubernetes/setup.mdx enumerating the
manifest and image operators need to mirror to an internal registry.
Signed-off-by: Roshni Malani <rmalani@nvidia.com>
Allow operators to configure a default Kubernetes runtimeClassName that
is applied to sandbox pods when the CreateSandbox request does not
specify one. This avoids requiring every API caller to explicitly set the
runtime class for clusters that always need a specific RuntimeClass
(e.g. kata-containers, nvidia).
The fallback is applied in the Kubernetes driver only — per-request
values still take priority, and an empty default (the built-in) preserves
existing behavior (field omitted, cluster default applies).
* fix(helm): create sandbox JWT secret under cert-manager
The cert-manager install path (certManager.enabled=true,
pkiInitJob.enabled=false) left the gateway StatefulSet unable to start
because nothing created the openshell-jwt-keys Secret: cert-manager owns
TLS Secrets but does not mint the sandbox JWT signing key, and the
certgen hook only rendered when pkiInitJob.enabled was true.
Separate JWT signing-key provisioning from TLS PKI provisioning:
- certgen: add a --jwt-only mode that creates only the Opaque JWT
signing Secret, for use when another controller owns TLS Secrets.
- certgen.yaml: render the hook when pkiInitJob.enabled OR
certManager.enabled is true. cert-manager takes precedence and runs
the hook with --jwt-only even if pkiInitJob.enabled remains true.
Remove the mutual-exclusion failure between the two values.
- _helpers.tpl: add openshell.sandboxJwtSecretName, shared by the hook
and the StatefulSet mount.
- Update values, README, docs, architecture, and the
debug-openshell-cluster skill to reflect the new precedence; the
documented cert-manager install no longer needs pkiInitJob.enabled=false.
Closes#1691
* fix(helm): honor cert-manager precedence for client CA volume
The client CA volume logic treated pkiInitJob.enabled as proof that
built-in PKI owns the client CA. With cert-manager precedence now
allowing certManager.enabled=true alongside the default
pkiInitJob.enabled=true, that assumption mounts the server TLS cert
secret as the client CA and ignores
certManager.clientCaFromServerTlsSecret=false, which can break mTLS or
trust the wrong CA.
Gate the pkiInitJob.enabled term with (not certManager.enabled) in all
three client CA conditions (volume mount, volume definition, and secret
selection) so cert-manager owns TLS when enabled. Add a Helm test suite
covering built-in PKI, cert-manager shared CA, the regression config
(cert-manager + clientCaFromServerTlsSecret=false + default pkiInitJob),
and the no-client-CA case.
The existing docs omitted or misstated several requirements when running
the gateway as a container with the Docker compute driver:
- OPENSHELL_GRPC_ENDPOINT is required; the Docker driver uses only the
scheme (http/https) — host and port are substituted automatically with
host.openshell.internal and the gateway's own bind port
- Supervisor binary must be extracted to a host path before starting the
gateway; bind-mount sources are resolved by the host Docker daemon so
the path must be identical inside and outside the gateway container
- Docker socket access requires adding the docker group (UID 1000 default)
- Port binding should remain 127.0.0.1; Docker driver adds a bridge
listener automatically
- add --server-san host.openshell.internal to generate-certs for mTLS
- Complete the mTLS docker run with all Docker driver requirements
- Add deploy/docker/gateway.toml — TOML config for the Docker driver
- Add deploy/docker/docker-compose.yml referencing the TOML
- Add docs/get-started/tutorials/docker-compose.mdx tutorial page
- Remote gateway registration instructions (--remote flag)
Address reviewer feedback:
- Move Docker Compose tutorials card to the bottom of the list
- Replace inline YAML snippet in Docker Compose section with a reference
to deploy/docker/ to avoid drift
- Clarify OPENSHELL_DB_URL is safe in compose.yml (plain SQLite path,
no credentials); the TOML block targets credential-bearing DSNs
- Note that ./ in source: resolves relative to the compose file directory
- Clarify that only the scheme from OPENSHELL_GRPC_ENDPOINT matters
- Add note that the tilde volume mount resolves to the same absolute
path on both host and container
* ci(kubernetes): pin mise in e2e workflow
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* ci(kubernetes): mirror postgres image for ha e2e
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* ci(kubernetes): reuse e2e workflow for ha
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
---------
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* fix(gateway): align package TLS bootstrap path
Closes#1593
Default package-managed gateway services to a stable local TLS directory and use that same value for certificate generation and runtime startup.
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* test(packaging): validate package asset paths exist
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* ci(e2e): pin mise in kubernetes job
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
---------
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
* feat(helm): add optional PostgreSQL backing store with Secret-based credentials
- Add postgres.enabled and postgres.deploy values to control database
backend (SQLite vs PostgreSQL) and subchart deployment independently.
- Introduce db-secret.yaml template for Opaque Secret with assembled
postgresql:// connection string injected via OPENSHELL_DB_URL env var.
- Add Bitnami PostgreSQL as optional subchart dependency keyed on
postgres.deploy to prevent subchart deployment in external mode.
- Externalize JWT signing key file mode via sandboxJwt.secretDefaultMode
with 0400 default matching upstream.
- Add validation guard for postgres.deploy=true without postgres.enabled.
- Add helm unit tests covering internal, external, URL-override, special
character encoding, and misconfiguration error paths.
- Update README with Kubernetes and OpenShift install examples for
bundled and external PostgreSQL configurations.
- Add helm dependency build to lint and unittest tasks.
* fix(helm): add database backend docs to README.md.gotmpl and regenerate
The helm-docs CI check failed because the Database backend section was
added directly to README.md instead of README.md.gotmpl. Move the
content to the template and regenerate so the check passes.
* fix(helm): use Secret-based DB credentials and support existingSecret
Replace the inline db-url stringData pattern with a proper Secret
containing individual fields plus a uri key. When postgres.deploy=true
the Bitnami service-binding secret is referenced directly; when
deploy=false users can supply postgres.external.existingSecret to
bring their own Secret, or let the chart generate one from the external
field values.
Also restructures the README database section for clarity, adds
helm-unittest coverage for the new secret resolution paths, and
fixes a markdown lint issue in the root README.
* refactor(helm): move OpenShift e2e script to e2e/rust/ and add mise task
Move test-openshift-scenarios.sh from deploy/helm/openshell/ci/ to
e2e/rust/e2e-openshift.sh, matching the existing e2e script naming
convention. Register it as `e2e:openshift` in tasks/test.toml — not
wired into the `test` or `e2e` aggregates so it only runs on explicit
invocation against a live OpenShift cluster.
* feat(e2e): add database backend scenarios to Kubernetes e2e
Extend with-kube-gateway.sh with an optional multi-scenario loop gated
by OPENSHELL_E2E_KUBE_DB_SCENARIOS=1. When enabled, the script installs
the Helm chart three times — SQLite (default), bundled PostgreSQL, and
external PostgreSQL with existingSecret — running the full test suite
against each backend. When unset, existing single-install behavior is
unchanged.
Also adds helm dependency build before helm install, fixing CI failures
caused by the missing PostgreSQL subchart dependency.
* refactor(helm): simplify PostgreSQL config to two orthogonal controls
Replace postgres.deploy and postgres.external.* with two simple controls:
- postgres.enabled: deploy the bundled Bitnami PostgreSQL subchart
- server.externalDbSecret: name of a pre-existing Secret with a uri key
Delete db-secret.yaml — the chart no longer generates Secrets from
individual credential fields. Users either get the Bitnami service-binding
secret (bundled) or bring their own via server.externalDbSecret.
Add validation that postgres.serviceBindings.enabled must stay true
when using bundled PostgreSQL, preventing a confusing runtime failure.
After #1415 ships, users upgrading from previous releases need guidance
on the gateway.env deprecation, port/bind/database path changes, and
the podman.socket restart requirement.
- docs(rpm): add 'Migrating from gateway.env' section to TROUBLESHOOTING
covering backward compatibility, env-to-TOML key mapping, and three
breaking changes (default port 8080->17670, bind address 0.0.0.0->127.0.0.1,
database path move). Add podman.socket restart step to upgrade procedure.
- docs(rpm): add upgrade callout to CONFIGURATION.md pointing at migration
section.
- fix(podman): retry PodmanComputeDriver ping up to 5 times with 2s delay
to tolerate transient socket unavailability after package upgrades.
The systemd unit uses Wants=podman.socket (not Requires) so the gateway
can start while the socket is briefly re-activating after an RPM upgrade
changes its unit file on disk.
- chore(rpm): update EnvironmentFile comment in RPM spec to explain
backward-compatibility intent.
Signed-off-by: Adam Miller <admiller@redhat.com>
The Kubernetes driver hardcoded the workspace PVC size to 2Gi. Add a
workspace_default_storage_size field to KubernetesComputeConfig so
operators can tune it via TOML config, Helm values, or the
OPENSHELL_K8S_WORKSPACE_DEFAULT_STORAGE_SIZE environment variable.
* fix(rpm): restore 0.0.0.0 bind address for Podman via default gateway.toml
The gateway binary default changed to 127.0.0.1 in recent commits
(b61a98db, f257ed01). This breaks the Podman compute driver because
sandbox containers reach the gateway over the host network bridge and
cannot connect to the loopback address.
Ship a default TOML config template that the RPM systemd unit seeds
into ~/.config/openshell/gateway.toml on first start. The template
sets bind_address = "0.0.0.0:17670" and pins compute_drivers =
["podman"] to prevent unexpected driver selection when Docker is also
installed. The binary default remains 127.0.0.1 (secure-by-default
for non-RPM installs).
Changes:
- deploy/rpm/gateway.toml.default: new default config template
- openshell.spec: install template to %{_datadir}/openshell-gateway/;
add ExecStartPre to seed ~/.config/openshell/gateway.toml on first
start; add %check assertions for template presence and unit reference
- deploy/rpm/CONFIGURATION.md: document default config, override paths,
and updated bind address throughout
- deploy/rpm/QUICKSTART.md: update bind address note for RPM installs
- crates/openshell-server/src/config_file.rs: contract test that parses
the RPM template through load() and asserts bind_address=0.0.0.0
and compute_drivers=[podman]
- e2e/with-podman-gateway.sh: start from RPM template as base config
so e2e exercises the same TOML path RPM users get on first start
* fix(rpm): restore openshell_python_version macro in dist-info metadata
* fix(rpm): reset Packit-managed version fields to match main baseline
Version, Source0, and Source1 were stamped to 0.0.43 by Packit CI
during branch builds. Reset to 0.0.37 (current main baseline) so
the spec diff only contains our intentional changes. Packit's
fix-spec-file action will re-stamp these fields at build time.
* Revert "fix(rpm): reset Packit-managed version fields to match main baseline"
This reverts commit 8ef9d7ad8e.
* fix(rpm): introduce openshell_version macro; remove hardcoded versions
Add %global openshell_version as the single source of truth for the
package version. Version:, Source0:, Source1:, openshell_cargo_version,
and openshell_python_version all expand from this one macro.
Update .packit.yaml fix-spec-file to patch %global openshell_version
instead of the Version:, Source0:, and Source1: lines individually.