Skip to content

Commit d51549f

Browse files
committed
feat(proxy): add discovery-driven application TLS passthrough
Signed-off-by: Taylor Mutch <taylormutch@gmail.com>
1 parent a6d0833 commit d51549f

25 files changed

Lines changed: 1253 additions & 10 deletions

File tree

‎.github/workflows/build-binaries.yml‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ jobs:
114114
binary: openshell-sandbox
115115
triple: aarch64-unknown-linux-musl
116116
runner: linux-arm64-cpu8
117+
- package: openshell-sandbox-proxy
118+
binary: openshell-sandbox-proxy
119+
triple: x86_64-unknown-linux-gnu
120+
runner: linux-amd64-cpu8
121+
- package: openshell-sandbox-proxy
122+
binary: openshell-sandbox-proxy
123+
triple: aarch64-unknown-linux-gnu
124+
runner: linux-arm64-cpu8
117125
- package: openshell-supervisor
118126
binary: openshell-supervisor
119127
triple: x86_64-unknown-linux-gnu

‎.github/workflows/build-images.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ jobs:
6464
runs-on: linux-amd64-cpu8
6565
timeout-minutes: 20
6666
steps: *build_steps
67+
68+
sandbox-proxy:
69+
env:
70+
COMPONENT: sandbox-proxy
71+
BINARY: openshell-sandbox-proxy
72+
TARGET_SUFFIX: unknown-linux-gnu
73+
runs-on: linux-amd64-cpu8
74+
timeout-minutes: 20
75+
steps: *build_steps

‎.github/workflows/package-release-binaries.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ jobs:
5454
- artifact: openshell-supervisor-aarch64-unknown-linux-gnu
5555
binary: openshell-supervisor
5656
package: supervisor-binary-linux-arm64
57+
- artifact: openshell-sandbox-proxy-x86_64-unknown-linux-gnu
58+
binary: openshell-sandbox-proxy
59+
package: sandbox-proxy-binary-linux-amd64
60+
- artifact: openshell-sandbox-proxy-aarch64-unknown-linux-gnu
61+
binary: openshell-sandbox-proxy
62+
package: sandbox-proxy-binary-linux-arm64
5763
- artifact: openshell-driver-vm-x86_64-unknown-linux-gnu
5864
binary: openshell-driver-vm
5965
package: driver-vm-linux-amd64

‎.github/workflows/release-dev.yml‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ jobs:
189189
run: |
190190
set -euo pipefail
191191
REGISTRY="ghcr.io/nvidia/openshell"
192-
for component in gateway sandbox supervisor; do
192+
for component in gateway sandbox supervisor sandbox-proxy; do
193193
echo "Tagging ${REGISTRY}/${component}:${{ github.sha }} as dev..."
194194
docker buildx imagetools create \
195195
--prefer-index=false \
@@ -321,6 +321,13 @@ jobs:
321321
path: release/
322322
merge-multiple: true
323323

324+
- name: Download sandbox proxy binary artifacts
325+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
326+
with:
327+
pattern: sandbox-proxy-binary-*
328+
path: release/
329+
merge-multiple: true
330+
324331
- name: Download sandbox binary artifacts
325332
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
326333
with:
@@ -441,6 +448,10 @@ jobs:
441448
openshell-supervisor-x86_64-unknown-linux-gnu.tar.gz \
442449
openshell-supervisor-aarch64-unknown-linux-gnu.tar.gz > openshell-supervisor-checksums-sha256.txt
443450
cat openshell-supervisor-checksums-sha256.txt
451+
sha256sum -- \
452+
openshell-sandbox-proxy-x86_64-unknown-linux-gnu.tar.gz \
453+
openshell-sandbox-proxy-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-proxy-checksums-sha256.txt
454+
cat openshell-sandbox-proxy-checksums-sha256.txt
444455
445456
- name: Generate Homebrew formula
446457
run: |
@@ -547,7 +558,9 @@ jobs:
547558
release/openshell-sandbox-x86_64-unknown-linux-musl.tar.gz
548559
release/openshell-sandbox-aarch64-unknown-linux-musl.tar.gz
549560
release/openshell-supervisor-x86_64-unknown-linux-gnu.tar.gz
561+
release/openshell-sandbox-proxy-x86_64-unknown-linux-gnu.tar.gz
550562
release/openshell-supervisor-aarch64-unknown-linux-gnu.tar.gz
563+
release/openshell-sandbox-proxy-aarch64-unknown-linux-gnu.tar.gz
551564
release/openshell-driver-vm-x86_64-unknown-linux-gnu.tar.gz
552565
release/openshell-driver-vm-aarch64-unknown-linux-gnu.tar.gz
553566
release/openshell-driver-vm-aarch64-apple-darwin.tar.gz
@@ -560,6 +573,7 @@ jobs:
560573
release/openshell-gateway-checksums-sha256.txt
561574
release/openshell-sandbox-checksums-sha256.txt
562575
release/openshell-supervisor-checksums-sha256.txt
576+
release/openshell-sandbox-proxy-checksums-sha256.txt
563577
release/openshell-prover-checksums-sha256.txt
564578
565579
release-helm:

‎.github/workflows/release-publish.yml‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ jobs:
103103
path: release/
104104
merge-multiple: true
105105

106+
- name: Download sandbox proxy binary artifacts
107+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
108+
with:
109+
pattern: sandbox-proxy-binary-*
110+
path: release/
111+
merge-multiple: true
112+
106113
- name: Download sandbox binary artifacts
107114
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
108115
with:
@@ -198,6 +205,10 @@ jobs:
198205
openshell-supervisor-x86_64-unknown-linux-gnu.tar.gz \
199206
openshell-supervisor-aarch64-unknown-linux-gnu.tar.gz > openshell-supervisor-checksums-sha256.txt
200207
cat openshell-supervisor-checksums-sha256.txt
208+
sha256sum -- \
209+
openshell-sandbox-proxy-x86_64-unknown-linux-gnu.tar.gz \
210+
openshell-sandbox-proxy-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-proxy-checksums-sha256.txt
211+
cat openshell-sandbox-proxy-checksums-sha256.txt
201212
202213
- name: Generate Homebrew formula
203214
run: |
@@ -262,7 +273,7 @@ jobs:
262273
VERSION: ${{ inputs.semver }}
263274
run: |
264275
set -euo pipefail
265-
for component in gateway sandbox supervisor; do
276+
for component in gateway sandbox supervisor sandbox-proxy; do
266277
image="ghcr.io/nvidia/openshell/${component}:${VERSION}"
267278
# Archives are build artifacts from this run, not externally signed images.
268279
nix shell --inputs-from . nixpkgs#skopeo -c \
@@ -296,7 +307,7 @@ jobs:
296307
run: |
297308
set -euo pipefail
298309
REGISTRY="ghcr.io/nvidia/openshell"
299-
for component in gateway sandbox supervisor; do
310+
for component in gateway sandbox supervisor sandbox-proxy; do
300311
if [[ "${IS_PRERELEASE}" == "true" ]]; then
301312
echo "Tagging ${REGISTRY}/${component}:${SOURCE_TAG} as ${VERSION}..."
302313
docker buildx imagetools create \
@@ -429,7 +440,9 @@ jobs:
429440
release/openshell-sandbox-x86_64-unknown-linux-musl.tar.gz
430441
release/openshell-sandbox-aarch64-unknown-linux-musl.tar.gz
431442
release/openshell-supervisor-x86_64-unknown-linux-gnu.tar.gz
443+
release/openshell-sandbox-proxy-x86_64-unknown-linux-gnu.tar.gz
432444
release/openshell-supervisor-aarch64-unknown-linux-gnu.tar.gz
445+
release/openshell-sandbox-proxy-aarch64-unknown-linux-gnu.tar.gz
433446
release/openshell-driver-vm-x86_64-unknown-linux-gnu.tar.gz
434447
release/openshell-driver-vm-aarch64-unknown-linux-gnu.tar.gz
435448
release/openshell-driver-vm-aarch64-apple-darwin.tar.gz
@@ -442,6 +455,7 @@ jobs:
442455
release/openshell-gateway-checksums-sha256.txt
443456
release/openshell-sandbox-checksums-sha256.txt
444457
release/openshell-supervisor-checksums-sha256.txt
458+
release/openshell-sandbox-proxy-checksums-sha256.txt
445459
release/openshell-prover-checksums-sha256.txt
446460
447461
- name: Upload amd64 Debian prerelease package

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Do not rely on this file for a full inventory. The detailed public and contribut
2525
| `crates/openshell-conformance/` | CLI conformance library | Reusable driver-agnostic scenarios and command runner |
2626
| `crates/openshell-server/` | Gateway server | Control-plane API, sandbox lifecycle, auth boundary |
2727
| `crates/openshell-sandbox/` | Sandbox runtime | Capability-free workload launcher, process identity, and seccomp-mediated I/O |
28+
| `crates/openshell-sandbox-proxy/` | Optional application proxy | Discovery-driven TLS passthrough directly to supervisor app listeners |
2829
| `crates/openshell-supervisor/` | Supervisor runtime | Gateway session, policy evaluation, credentials, and upstream networking |
2930
| `crates/openshell-supervisor-control/` | Shared supervisor control | Gateway-owned assignment controller, launch registrations, recovery coordination, and host observations for a supervisor shared by many sandboxes |
3031
| `crates/openshell-binary-identity/` | Binary identity | Shared trusted procfs executable identity resolution for isolation backends |

‎CONTRIBUTING.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ still miss across worktrees.
303303

304304
## Container Image Base Overrides
305305

306+
The optional application proxy image can be built with `mise run docker:build:sandbox-proxy`. Its standalone deployment chart is in `deploy/helm/openshell-sandbox-proxy`.
307+
306308
The gateway and supervisor Dockerfiles accept `GATEWAY_BASE_IMAGE` and
307309
`SUPERVISOR_BASE_IMAGE` build arguments. Omitting them keeps the pinned
308310
distroless defaults. To build with your own bases, pass the argument through

‎Cargo.lock‎

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
[package]
5+
name = "openshell-sandbox-proxy"
6+
description = "Optional discovery-driven TLS passthrough proxy for sandbox applications"
7+
version.workspace = true
8+
edition.workspace = true
9+
rust-version.workspace = true
10+
license.workspace = true
11+
repository.workspace = true
12+
13+
[dependencies]
14+
openshell-core = { path = "../openshell-core" }
15+
openshell-sdk = { path = "../openshell-sdk" }
16+
axum = { workspace = true }
17+
clap = { workspace = true }
18+
rustls = { workspace = true }
19+
tokio = { workspace = true }
20+
tonic = { workspace = true }
21+
tracing = { workspace = true }
22+
tracing-subscriber = { workspace = true }
23+
url = { workspace = true }
24+
25+
[dev-dependencies]
26+
tokio = { workspace = true, features = ["test-util"] }
27+
openshell-crypto = { workspace = true }
28+
29+
[lints]
30+
workspace = true
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<!--
2+
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
SPDX-License-Identifier: Apache-2.0
4+
-->
5+
6+
# Sandbox application proxy
7+
8+
`openshell-sandbox-proxy` is an optional consumer of the gateway service discovery
9+
API. It selects the exact discovered hostname from TLS SNI and forwards TLS bytes
10+
to the owning supervisor's app listener. Application certificates and private
11+
keys remain at supervisors. HTTP, WebSockets, application authentication, and
12+
sandbox policy remain enforced by the existing listener and application.
13+
14+
```shell
15+
openshell-sandbox-proxy \
16+
--gateway-endpoint https://gateway.example.com \
17+
--workspace default \
18+
--gateway-ca /run/discovery/ca.crt \
19+
--discovery-token-file /run/discovery/token \
20+
--listen-address 0.0.0.0:8443
21+
```
22+
23+
Use an identity already authorized for the selected workspace. Client mTLS can
24+
use `--discovery-client-cert` and `--discovery-client-key`. Credentials and CA
25+
files reload on reconnect. This version supports the explicit-workspace API;
26+
discovery-only platform machine grants and all-workspaces aggregation are
27+
separate work (#4668). It does not require operator credentials. Plaintext
28+
credential-free loopback development requires `--allow-insecure-gateway`.
29+
30+
A snapshot initializes the cache and bounded watches replace it. Reconnects
31+
relist; transient errors retain routes only until their published expiry.
32+
Authorization/configuration/data-integrity failures clear the cache. Monotonic
33+
local deadlines are derived from absolute expiries and capped at fifteen seconds;
34+
operators must synchronize gateway and proxy clocks. Snapshot revisions are
35+
opaque and installation versions identify replacement independently of renewal.
36+
37+
Only exact, current routes can dial a backend. A route withdrawal, installation
38+
replacement, or expiry closes affected TCP streams; renewal preserves them.
39+
There is no gateway payload fallback, wildcard route fallback, or caller-selected
40+
backend. Limits are 4,096 cached routes, 1,024 concurrent connections, 64 per
41+
hostname, a 64 KiB ClientHello, and five-second hello/connect/setup deadlines.
42+
Each relay uses Tokio's bounded bidirectional copy buffers. ClientHello parsing
43+
is dependency-owned rustls protocol parsing, not a first-party cryptographic
44+
operation. Authenticated discovery transport uses the shared SDK/crypto facade.
45+
46+
The health listener defaults to port 9091: `/healthz` is process liveness,
47+
`/readyz` reflects discovery availability, and `/metrics` exposes connection
48+
counts. During transient discovery failures readiness fails while existing
49+
unexpired connections can continue. Expiry still closes streams independently
50+
of the discovery connection. Multiple replicas keep independent caches and need
51+
no Kubernetes ownership or leader election.
52+
53+
Build with `mise run docker:build:sandbox-proxy`. The standalone Helm chart at
54+
`deploy/helm/openshell-sandbox-proxy` mounts only discovery identity/trust Secrets;
55+
it needs no Kubernetes API permissions. Configure NetworkPolicy to allow the
56+
proxy to reach the gateway API and discovered supervisor app endpoints, and
57+
restrict supervisor ingress where edge authentication is required. The proxy
58+
never authenticates application users using its discovery identity.
59+
60+
For Gateway API TLS passthrough, attach a TLSRoute to a Gateway listener in
61+
`Passthrough` mode and target the proxy Service on port 443. Application TLS
62+
terminates at the supervisor. An HTTPRoute requires a separate edge termination
63+
and backend-TLS contract. A controller can instead consume the same discovery
64+
API and reconcile exact-host routes directly to supervisor endpoints; it does
65+
not need this proxy.
66+
67+
## Verification
68+
69+
```shell
70+
cargo test -p openshell-sandbox-proxy
71+
cargo clippy -p openshell-sandbox-proxy --all-targets -- -D warnings
72+
helm lint deploy/helm/openshell-sandbox-proxy \
73+
--set gatewayEndpoint=https://gateway.example.com --set workspace=default
74+
```
75+
76+
Initial live support follows the advertisement implementation: dedicated
77+
Kubernetes supervisors. Shared publication remains withheld until HA ownership
78+
term/member mapping is integrated. Hashed multi-gateway hostnames also require
79+
the common listener/discovery hostname integration; the proxy consumes returned
80+
hostnames and never reconstructs them.

0 commit comments

Comments
 (0)