From 2ece12f1aec19b4b3bf53122011dea51d5ebc98d Mon Sep 17 00:00:00 2001 From: Piotr Mlocek Date: Tue, 18 Aug 2026 15:54:20 -0700 Subject: [PATCH] docs(sdk): clarify extension and E2E setup Signed-off-by: Piotr Mlocek --- architecture/gateway.md | 4 ---- crates/openshell-sdk/README.md | 15 ++++++--------- crates/openshell-sdk/src/extension/mod.rs | 6 +++--- docs/extensibility/supervisor-middleware.mdx | 2 +- e2e/with-docker-gateway.sh | 17 +++++++++++++++++ 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/architecture/gateway.md b/architecture/gateway.md index ba718ca89..32bca6a1f 100644 --- a/architecture/gateway.md +++ b/architecture/gateway.md @@ -87,10 +87,6 @@ rotates their in-memory slots without rebuilding clients, and publishes the public verification key at `/.well-known/jwks.json` alongside OIDC-shaped discovery metadata at `/.well-known/openid-configuration`. HTTPS extensions can pin an operator-provided CA while retaining endpoint-hostname verification. -Rust extension services use the opt-in `openshell-sdk::extension` surface to -verify these credentials from operator-provisioned Ed25519 public keys or JWKS -documents and normalize gateway and supervisor caller identities. Key refresh -remains a service deployment concern. Extension credentials reuse the sandbox signing key and are separated from sandbox-to-gateway admission tokens by exact audience and by an explicit diff --git a/crates/openshell-sdk/README.md b/crates/openshell-sdk/README.md index 3b4028a0f..b5f6f1b50 100644 --- a/crates/openshell-sdk/README.md +++ b/crates/openshell-sdk/README.md @@ -49,19 +49,16 @@ mTLS (client certificates) is not supported. SDK-shaped enums rather than raw proto integers. Failures map to a typed `SdkError` with a discriminable kind. -Enable the `extension` Cargo feature when implementing an operator-run -OpenShell extension service: +Enable the `extension` Cargo feature when building an OpenShell extension +service: ```toml openshell-sdk = { version = "...", features = ["extension"] } ``` -The `extension` module verifies gateway-minted EdDSA extension JWTs from a -trusted public key or JWKS document. `GatewayJwtAuthenticator` pins the token -type, algorithm, issuer, audience, lifetime, and caller identity shape, then -returns an `AuthenticatedCaller` that service handlers can authorize by caller -kind. This feature is opt-in so gateway-only SDK consumers do not compile the -extension verification dependencies. +The `extension` module provides common SDK building blocks for extension +services. It currently handles caller verification; shared service interfaces +and observability support will follow. ## Modules @@ -74,7 +71,7 @@ extension verification dependencies. | `oidc` | OIDC token handling at the transport layer. | | `refresh` | `Refresh` trait and single-flight refresh coalescing. | | `edge_tunnel` | Cloudflare Access tunnel dialer. | -| `extension` | Opt-in service-side authentication and identity types for operator-run extensions. | +| `extension` | Opt-in SDK building blocks for extension services. | | `error` | `SdkError` taxonomy. | | `types` | Curated request/response types and proto conversions. | | `raw` | Escape hatch re-exporting the generated tonic clients. | diff --git a/crates/openshell-sdk/src/extension/mod.rs b/crates/openshell-sdk/src/extension/mod.rs index f844d9a95..d1dcb7414 100644 --- a/crates/openshell-sdk/src/extension/mod.rs +++ b/crates/openshell-sdk/src/extension/mod.rs @@ -1,10 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -//! APIs for implementing operator-run `OpenShell` extension services. +//! APIs for building `OpenShell` extension services. //! -//! Enable the `extension` Cargo feature to verify gateway-minted extension -//! credentials and authorize callers without depending on gateway internals. +//! This feature is the home for shared extension interfaces, verification, and +//! observability. It currently provides gateway-minted credential verification. mod verification; diff --git a/docs/extensibility/supervisor-middleware.mdx b/docs/extensibility/supervisor-middleware.mdx index d9572ee03..877f81842 100644 --- a/docs/extensibility/supervisor-middleware.mdx +++ b/docs/extensibility/supervisor-middleware.mdx @@ -90,7 +90,7 @@ Return your expected audience in the `expected_audience` field of your `Describe Provision the trusted gateway URL, expected gateway ID, and public key or JWKS through the deployment. This operator-provisioned key material is the authoritative cold-start trust anchor. The expected issuer is exactly `openshell-gateway:`; fetching JWKS does not establish that identity by itself. After initial trust is established, `GET /.well-known/openid-configuration` and its `jwks_uri` provide steady-state key refresh and operational convenience. The document is OIDC-shaped rather than OIDC-compliant: `issuer` is the gateway identity, not the URL serving the document, so compare `iss` against the configured value and fetch updates only over authenticated TLS at the trusted gateway URL. -Rust services can enable the `extension` Cargo feature on `openshell-sdk` and use `openshell_sdk::extension::GatewayJwtAuthenticator`. Construct it from a trusted Ed25519 public key and `kid`, or from a trusted JWKS document, then call `authenticate_request` on each tonic request. The returned `AuthenticatedCaller` exposes the normalized caller kind, subject, and optional sandbox ID for RPC-specific authorization. Key refresh remains deployment-owned; rebuild the authenticator after accepting a refreshed key set from the trusted gateway URL. +Rust services can enable the `extension` Cargo feature on `openshell-sdk` for common extension interfaces, verification, and observability. Caller verification is available today through `openshell_sdk::extension::GatewayJwtAuthenticator`; additional SDK support will follow. Cache keys by `kid`. Validate, at minimum: diff --git a/e2e/with-docker-gateway.sh b/e2e/with-docker-gateway.sh index b85a22cb6..d56a650d3 100755 --- a/e2e/with-docker-gateway.sh +++ b/e2e/with-docker-gateway.sh @@ -279,6 +279,9 @@ connect_current_container_to_docker_network() { GATEWAY_HOST_ALIAS_IP="${container_ip}" } +# Build and launch an authenticated TLS extension service that is reachable +# from the host gateway and sandbox containers. The focused middleware E2E lane +# opts into this fixture; other Docker E2E callers retain the existing setup. start_middleware_fixture() { local manifest="${ROOT}/e2e/supervisor-middleware-fixture/Cargo.toml" local binary="${TARGET_DIR}/debug/openshell-e2e-middleware-fixture" @@ -290,8 +293,12 @@ start_middleware_fixture() { CARGO_TARGET_DIR="${TARGET_DIR}" cargo build --locked --manifest-path "${manifest}" if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then + # In GitHub Actions, the fixture and gateway run in the job container. The + # wrapper has already attached that container to the sandbox network. fixture_host="${GATEWAY_HOST_ALIAS_IP}" elif [ "$(uname -s)" = "Linux" ]; then + # In a native Linux run, the fixture and gateway run on the host. The + # bridge gateway address is reachable from both the host and containers. fixture_host="$(docker network inspect \ --format '{{(index .IPAM.Config 0).Gateway}}' \ "${DOCKER_NETWORK_NAME}")" @@ -306,6 +313,8 @@ start_middleware_fixture() { fi mkdir -p "${fixture_dir}" + # Generate a one-run CA and a CA-signed server certificate whose IP SAN + # matches the shared address used by the gateway and sandbox supervisor. openssl req -x509 -newkey rsa:2048 -nodes \ -keyout "${fixture_dir}/ca.key" \ -out "${fixture_dir}/ca.crt" \ @@ -335,6 +344,8 @@ start_middleware_fixture() { fixture_port="$(e2e_pick_port)" echo "Starting supervisor middleware E2E fixture on ${fixture_host}:${fixture_port}..." + # Give the fixture both sides of the trust relationship: its TLS identity and + # the gateway identity/audience required to verify extension bearer tokens. OPENSHELL_E2E_MIDDLEWARE_LISTEN="0.0.0.0:${fixture_port}" \ OPENSHELL_E2E_MIDDLEWARE_TLS_CERT="${fixture_dir}/tls.crt" \ OPENSHELL_E2E_MIDDLEWARE_TLS_KEY="${fixture_dir}/tls.key" \ @@ -345,6 +356,8 @@ start_middleware_fixture() { "${binary}" >"${MIDDLEWARE_FIXTURE_LOG}" 2>&1 & MIDDLEWARE_FIXTURE_PID=$! + # A TCP readiness probe is sufficient here: the gateway performs the + # authenticated gRPC Describe call when it starts immediately afterward. local attempt for attempt in $(seq 1 100); do if ! kill -0 "${MIDDLEWARE_FIXTURE_PID}" 2>/dev/null; then @@ -587,6 +600,8 @@ echo "Starting openshell-gateway on port ${HOST_PORT} (namespace: ${E2E_NAMESPAC echo "Using sandbox image: ${SANDBOX_IMAGE} (pull policy: ${SANDBOX_IMAGE_PULL_POLICY})" e2e_generate_gateway_jwt "${JWT_DIR}" if [ "${OPENSHELL_E2E_MIDDLEWARE_FIXTURE:-0}" = "1" ]; then + # The gateway validates middleware with Describe during startup, so the + # extension service and its trust material must exist first. start_middleware_fixture fi @@ -615,6 +630,8 @@ GATEWAY_CONFIG="${STATE_DIR}/gateway.toml" fi fi if [ -n "${MIDDLEWARE_FIXTURE_ENDPOINT}" ]; then + # Register only the opt-in fixture, pinning its ephemeral CA and the exact + # audience that both the gateway token issuer and fixture verifier expect. printf '[[openshell.supervisor.middleware]]\n' printf 'name = "e2e-scripted"\n' printf 'grpc_endpoint = %s\n' "$(toml_string "${MIDDLEWARE_FIXTURE_ENDPOINT}")"