Follow-up to #1792, tracking the Kubernetes driver the way #3088 tracked the VM driver.
User Story
As a platform operator running OpenShell on Kubernetes behind a corporate egress proxy, I want to supply the corporate CA bundle to sandbox supervisors, so that agents can reach policy-approved external APIs through an https:// forward proxy or a TLS-intercepting proxy whose certificates are signed by a private CA.
Problem Statement
#1792 added corporate forward-proxy egress. The Podman (#2512) and VM (#3090) drivers both expose proxy_ca_bundle; the Kubernetes driver does not.
[openshell.drivers.kubernetes] accepts https_proxy, no_proxy, proxy_auth_secret_name, proxy_auth_secret_key, proxy_auth_allow_insecure, and proxy_connect_by_hostname (crates/openshell-driver-kubernetes/src/config.rs:201-219), and the supervisor command it builds never includes --upstream-proxy-ca-bundle (crates/openshell-driver-kubernetes/src/sandbox_runtime.rs:283-298). The Helm chart has no matching value (deploy/helm/openshell/values.yaml:58).
An https:// proxy URL is accepted by Kubernetes-driver validation — parse_upstream_proxy_url accepts both schemes (crates/openshell-core/src/driver_utils.rs:289) — and reaches the supervisor, which does TLS-wrap the connection. Verification then uses only the built-in Mozilla roots plus the supervisor image's system bundle, so a proxy certificate issued by a private corporate CA fails the handshake with no way to supply the anchor. The same gap applies to a TLS-intercepting proxy reached over http://, which re-signs tunneled upstream certificates with its own CA.
Two secondary defects in the same code path:
- The Kubernetes driver hand-rolls its proxy validation (
config.rs:379) instead of calling the shared validate_upstream_proxy_settings (driver_utils.rs:651), so the two can drift.
- As a result it requires
proxy_auth_allow_insecure = true whenever a credential Secret is configured, including for an https:// proxy (config.rs:447), where the credential travels inside a verified TLS session. The shared validator correctly waives the acknowledgement in that case (driver_utils.rs:691).
Impact / Why This Matters
Operators on Kubernetes whose proxy presents a private-CA certificate, or who sit behind a TLS-intercepting proxy, have no working configuration. Setting https_proxy = "https://..." passes startup validation and then fails at sandbox egress, which reads as a runtime networking fault rather than a missing capability.
There is no workaround. The Kubernetes driver exposes no pod-template or volume override, so an operator cannot mount a CA over the supervisor container's system trust store, and sandbox pods run with automountServiceAccountToken: false (sandbox_runtime.rs:401). The only remaining option is forking the supervisor image per corporate environment.
The documentation is currently accurate — "Only http:// proxy endpoints and TLS CONNECT traffic are supported" (docs/reference/gateway-config.mdx:563) — so this is a documented capability gap rather than a silent bug. But it blocks exactly the enterprise Kubernetes deployments #1792 was opened for, and it was explicitly requested there: "If the server address uses https, we need option to pass custom CA."
Proposed Design
Add proxy_ca_bundle to [openshell.drivers.kubernetes], keeping the operator-facing key identical to the Podman and VM drivers so the corporate-proxy configuration surface stays uniform across compute backends.
The value is a path the gateway Pod can read — supplied by the operator the same way any other gateway file is, not a reference to an object in the sandbox namespace. The gateway is responsible for getting those bytes to each sandbox supervisor.
Observable behavior:
- With
proxy_ca_bundle set, the supervisor trusts the bundle for the TLS handshake with an https:// proxy, for upstream certificates re-signed by a TLS-intercepting proxy, and in the trust bundle exposed to sandbox processes — matching the Podman and VM drivers exactly.
- Configuration is fail-closed and consistent with the existing drivers: an empty value, a path that cannot be read, a non-regular file, a bundle over the size bound, a bundle contributing no usable trust anchor, or
proxy_ca_bundle without https_proxy is a startup or sandbox-create error naming the offending key — never a silent fall-back to direct dialing or to the built-in roots.
- The trust anchor a sandbox starts with does not change underneath it for that sandbox's lifetime.
- The setting works in all three workspace modes (
shared, managed, operator), including namespaces the gateway creates itself.
- The Helm chart exposes the bundle under
upstreamProxy, mounts it into the gateway Pod, and renders the resulting proxy_ca_bundle path into gateway.toml.
- The Kubernetes driver's proxy validation is replaced by the shared
validate_upstream_proxy_settings, which also drops the incorrect proxy_auth_allow_insecure requirement for an https:// proxy.
Security note for the implementation: the corporate CA is folded into three trust stores (crates/openshell-supervisor-network/src/run.rs:354-381) — the proxy-listener handshake, the L7 upstream verification store, and the sandbox trust bundle. Whoever controls those bytes can transparently intercept all sandbox egress, and OpenShell's L7 enforcement can no longer distinguish a genuine upstream certificate from a re-signed one. That is inherent to TLS interception and already accepted for Podman and VM, but it is the reason the bundle must be anchored in the gateway's own trust domain rather than in the workload namespace.
Acceptance Criteria
Alternatives Considered
Reference a pre-existing ConfigMap in the sandbox namespace by name/key, mirroring how proxy_auth_secret_name handles proxy credentials. Rejected on three grounds:
- It widens the trust boundary from the gateway's trust domain to anyone holding
update on ConfigMaps in the workload namespace. Given the three-store fold described above, that is control over interception of all sandbox egress.
- Kubelet live-updates mounted ConfigMaps, so the trust anchor is not pinned.
- It cannot work in
managed or operator workspace mode: the gateway auto-creates per-workspace namespaces (crates/openshell-driver-kubernetes/src/driver.rs:961) and deploy/helm/openshell/templates/role.yaml grants it no ConfigMap verbs at all, so no operator can pre-provision an object into a namespace that does not yet exist.
The credential precedent does not fully transfer. In shared mode the gateway holds no get on Secrets at all (role.yaml:54-58 grants create, list, delete). In multi-namespace modes it does hold name-restricted get + patch on explicitly configured Secrets (clusterrole.yaml:105-119), which is how ensure_tls_secret copies TLS and image-pull Secrets into workspace namespaces. That mechanism could in principle carry a credential too, but a CA certificate is not secret, so reading it from the gateway's own filesystem is simpler and avoids widening what the gateway may read.
Inline the PEM in gateway.toml. Works, and needs no new RBAC, but bloats the rendered config, diverges from the proxy_ca_bundle path semantics the other drivers use, and gives up the existing bounded-read and regular-file checks.
Document the gap and require a forked supervisor image with the corporate CA baked into its system trust store. Rejected: it pushes image maintenance onto every affected operator, ties trust-anchor rotation to an image rebuild, and would still leave the https:// proxy URL misleadingly accepted by validation.
Agent Investigation
Supervisor-side support is already complete and driver-independent. upstream_proxy.rs TLS-wraps the proxy connection for a secure URL and builds its trust store from the built-in Mozilla roots, the system bundle, and the optional corporate bundle (upstream_proxy.rs:644-660). Validation is shared between host and guest via read_upstream_proxy_ca_bundle_file (driver_utils.rs:484), which bounds the read at 1 MiB, rejects non-regular files, and requires rustls to accept at least one anchor rather than merely parsing PEM framing (validate_upstream_proxy_ca_bundle_pem, driver_utils.rs:520). The bundle is validated even for an http:// proxy, because an intercepting proxy reached in plaintext still re-signs tunneled certificates (upstream_proxy.rs:530). Only the Kubernetes driver's plumbing is missing.
Related pre-existing gap, same feature, tracked separately: proxy_auth_secret_name is documented as "an existing Secret in the sandbox namespace," which no operator can pre-provision in managed or operator workspace mode for the same auto-created-namespace reason described above. Scoped out of this issue rather than addressed here; the ensure_tls_secret copying mechanism (driver.rs:1121) that already carries TLS and image-pull Secrets into workspace namespaces looks directly reusable for it.
Follow-up to #1792, tracking the Kubernetes driver the way #3088 tracked the VM driver.
User Story
As a platform operator running OpenShell on Kubernetes behind a corporate egress proxy, I want to supply the corporate CA bundle to sandbox supervisors, so that agents can reach policy-approved external APIs through an
https://forward proxy or a TLS-intercepting proxy whose certificates are signed by a private CA.Problem Statement
#1792 added corporate forward-proxy egress. The Podman (#2512) and VM (#3090) drivers both expose
proxy_ca_bundle; the Kubernetes driver does not.[openshell.drivers.kubernetes]acceptshttps_proxy,no_proxy,proxy_auth_secret_name,proxy_auth_secret_key,proxy_auth_allow_insecure, andproxy_connect_by_hostname(crates/openshell-driver-kubernetes/src/config.rs:201-219), and the supervisor command it builds never includes--upstream-proxy-ca-bundle(crates/openshell-driver-kubernetes/src/sandbox_runtime.rs:283-298). The Helm chart has no matching value (deploy/helm/openshell/values.yaml:58).An
https://proxy URL is accepted by Kubernetes-driver validation —parse_upstream_proxy_urlaccepts both schemes (crates/openshell-core/src/driver_utils.rs:289) — and reaches the supervisor, which does TLS-wrap the connection. Verification then uses only the built-in Mozilla roots plus the supervisor image's system bundle, so a proxy certificate issued by a private corporate CA fails the handshake with no way to supply the anchor. The same gap applies to a TLS-intercepting proxy reached overhttp://, which re-signs tunneled upstream certificates with its own CA.Two secondary defects in the same code path:
config.rs:379) instead of calling the sharedvalidate_upstream_proxy_settings(driver_utils.rs:651), so the two can drift.proxy_auth_allow_insecure = truewhenever a credential Secret is configured, including for anhttps://proxy (config.rs:447), where the credential travels inside a verified TLS session. The shared validator correctly waives the acknowledgement in that case (driver_utils.rs:691).Impact / Why This Matters
Operators on Kubernetes whose proxy presents a private-CA certificate, or who sit behind a TLS-intercepting proxy, have no working configuration. Setting
https_proxy = "https://..."passes startup validation and then fails at sandbox egress, which reads as a runtime networking fault rather than a missing capability.There is no workaround. The Kubernetes driver exposes no pod-template or volume override, so an operator cannot mount a CA over the supervisor container's system trust store, and sandbox pods run with
automountServiceAccountToken: false(sandbox_runtime.rs:401). The only remaining option is forking the supervisor image per corporate environment.The documentation is currently accurate — "Only
http://proxy endpoints and TLS CONNECT traffic are supported" (docs/reference/gateway-config.mdx:563) — so this is a documented capability gap rather than a silent bug. But it blocks exactly the enterprise Kubernetes deployments #1792 was opened for, and it was explicitly requested there: "If the server address uses https, we need option to pass custom CA."Proposed Design
Add
proxy_ca_bundleto[openshell.drivers.kubernetes], keeping the operator-facing key identical to the Podman and VM drivers so the corporate-proxy configuration surface stays uniform across compute backends.The value is a path the gateway Pod can read — supplied by the operator the same way any other gateway file is, not a reference to an object in the sandbox namespace. The gateway is responsible for getting those bytes to each sandbox supervisor.
Observable behavior:
proxy_ca_bundleset, the supervisor trusts the bundle for the TLS handshake with anhttps://proxy, for upstream certificates re-signed by a TLS-intercepting proxy, and in the trust bundle exposed to sandbox processes — matching the Podman and VM drivers exactly.proxy_ca_bundlewithouthttps_proxyis a startup or sandbox-create error naming the offending key — never a silent fall-back to direct dialing or to the built-in roots.shared,managed,operator), including namespaces the gateway creates itself.upstreamProxy, mounts it into the gateway Pod, and renders the resultingproxy_ca_bundlepath intogateway.toml.validate_upstream_proxy_settings, which also drops the incorrectproxy_auth_allow_insecurerequirement for anhttps://proxy.Security note for the implementation: the corporate CA is folded into three trust stores (
crates/openshell-supervisor-network/src/run.rs:354-381) — the proxy-listener handshake, the L7 upstream verification store, and the sandbox trust bundle. Whoever controls those bytes can transparently intercept all sandbox egress, and OpenShell's L7 enforcement can no longer distinguish a genuine upstream certificate from a re-signed one. That is inherent to TLS interception and already accepted for Podman and VM, but it is the reason the bundle must be anchored in the gateway's own trust domain rather than in the workload namespace.Acceptance Criteria
[openshell.drivers.kubernetes] proxy_ca_bundleis accepted and documented alongside the other corporate-proxy keys.https_proxy = "https://..."with a private-CA proxy completes policy-approved TLS egress; withoutproxy_ca_bundlethe same configuration fails closed with a clear error.http://works when its re-signing CA is supplied viaproxy_ca_bundle.proxy_ca_bundlevalues (empty, unreadable, non-regular file, oversized, certificate-free, or set withouthttps_proxy) fail closed with an error naming the key.shared,managed, andoperatorworkspace modes.upstreamProxyand rendersproxy_ca_bundle; chart tests cover the rendered output.openshell_core::driver_utils::validate_upstream_proxy_settings, and a credential Secret with anhttps://proxy no longer requiresproxy_auth_allow_insecure.docs/reference/gateway-config.mdxanddocs/reference/sandbox-compute-drivers.mdxno longer state that onlyhttp://proxy endpoints are supported for Kubernetes.Alternatives Considered
Reference a pre-existing ConfigMap in the sandbox namespace by name/key, mirroring how
proxy_auth_secret_namehandles proxy credentials. Rejected on three grounds:updateon ConfigMaps in the workload namespace. Given the three-store fold described above, that is control over interception of all sandbox egress.managedoroperatorworkspace mode: the gateway auto-creates per-workspace namespaces (crates/openshell-driver-kubernetes/src/driver.rs:961) anddeploy/helm/openshell/templates/role.yamlgrants it no ConfigMap verbs at all, so no operator can pre-provision an object into a namespace that does not yet exist.The credential precedent does not fully transfer. In shared mode the gateway holds no get on Secrets at all (role.yaml:54-58 grants create, list, delete). In multi-namespace modes it does hold name-restricted get + patch on explicitly configured Secrets (clusterrole.yaml:105-119), which is how ensure_tls_secret copies TLS and image-pull Secrets into workspace namespaces. That mechanism could in principle carry a credential too, but a CA certificate is not secret, so reading it from the gateway's own filesystem is simpler and avoids widening what the gateway may read.
Inline the PEM in
gateway.toml. Works, and needs no new RBAC, but bloats the rendered config, diverges from theproxy_ca_bundlepath semantics the other drivers use, and gives up the existing bounded-read and regular-file checks.Document the gap and require a forked supervisor image with the corporate CA baked into its system trust store. Rejected: it pushes image maintenance onto every affected operator, ties trust-anchor rotation to an image rebuild, and would still leave the
https://proxy URL misleadingly accepted by validation.Agent Investigation
Supervisor-side support is already complete and driver-independent.
upstream_proxy.rsTLS-wraps the proxy connection for asecureURL and builds its trust store from the built-in Mozilla roots, the system bundle, and the optional corporate bundle (upstream_proxy.rs:644-660). Validation is shared between host and guest viaread_upstream_proxy_ca_bundle_file(driver_utils.rs:484), which bounds the read at 1 MiB, rejects non-regular files, and requires rustls to accept at least one anchor rather than merely parsing PEM framing (validate_upstream_proxy_ca_bundle_pem,driver_utils.rs:520). The bundle is validated even for anhttp://proxy, because an intercepting proxy reached in plaintext still re-signs tunneled certificates (upstream_proxy.rs:530). Only the Kubernetes driver's plumbing is missing.Related pre-existing gap, same feature, tracked separately:
proxy_auth_secret_nameis documented as "an existing Secret in the sandbox namespace," which no operator can pre-provision inmanagedoroperatorworkspace mode for the same auto-created-namespace reason described above. Scoped out of this issue rather than addressed here; theensure_tls_secretcopying mechanism (driver.rs:1121) that already carries TLS and image-pull Secrets into workspace namespaces looks directly reusable for it.