Skip to content

fix(security): authenticate extension services - #2638

Draft
pimlock wants to merge 6 commits into
mainfrom
feat/2623-extension-auth/pimlock
Draft

fix(security): authenticate extension services#2638
pimlock wants to merge 6 commits into
mainfrom
feat/2623-extension-auth/pimlock

Conversation

@pimlock

@pimlock pimlock commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Authenticate remote supervisor middleware and gateway-interceptor RPCs with short-lived, exact-audience gateway-signed JWTs. Add a shared extension client foundation so both mechanisms use the same bearer rotation and TLS/custom-CA implementation.

Related Issue

Closes #2623

Security Fix

Remote extension services previously could not cryptographically distinguish OpenShell gateway or sandbox-supervisor calls from direct network callers. This change adds authenticated caller identity and policy-constrained token distribution while preserving the existing Ed25519 signing authority and sandbox refresh API.

Severity Assessment

  • Impact is high for network-reachable extensions that perform privileged transformations or actions, and medium when trusted network isolation is enforced.
  • Exploitability is moderate because exploitation requires extension endpoint reachability but not compromise of gateway signing material.
  • Alpha residual risks remain: bearer replay until short expiry and single-key JWKS rotation. Multi-key overlap, mTLS, and stronger replay resistance remain deferred hardening.

Changes

  • Add openshell-extension-core, a shared foundation for extension identity, claims, bearer rotation, and TLS/custom-CA transport used by both supervisor middleware and gateway interceptors.
  • Mint short-lived, exact-audience JWTs for gateway and sandbox-supervisor callers from the gateway's existing Ed25519 signing authority.
  • Distribute policy-authorized credentials to supervisors through RefreshSandboxToken, held in a supervisor-owned store that rotates slots in place.
  • Publish verification material at /.well-known/jwks.json and /.well-known/openid-configuration.
  • Agree the expected audience through the Describe manifest at startup rather than relying on configuration matching on both sides of the boundary.
  • Add allow_insecure_transport per registration for deployments that keep a plaintext endpoint.
  • Update architecture, RFC appendices, user documentation, and generated Go bindings.

Review notes

Compatibility. With gateway_jwt configured, extension endpoints use https://; interceptors may also use unix://, middleware may not, because a middleware endpoint has to be reachable from every sandbox supervisor. Deployments that keep a plaintext endpoint set allow_insecure_transport = true on that registration. OpenShell then attaches no credential, refuses to mint one if a supervisor asks, and names the registration in a warning at every gateway startup.

Rotation cadence. Configuration polling runs every 10s while credentials last 15 minutes. Supervisors reuse their installed slots and rotate only when one is missing or has passed four fifths of its lifetime, so a routine poll does not re-run gateway effective-policy resolution or re-mint the gateway token.

Authorization resolution. handle_get_sandbox_config is the single resolver deciding which registrations a sandbox may hold credentials for. A cheaper parallel resolver would duplicate ~120 lines of policy composition and backfill side effects on a security-critical path, where drift fails open.

RFC layout. Extension-authentication detail lives in appendices/extension-authentication.md under RFC 0009 (the shared contract) and RFC 0010 (interceptor differences), leaving both accepted RFC bodies intact. RFC 0010's appendix is currently reachable only by path, since its body has no appendix reference to link from.

Out of scope: the service-side verification SDK (tracked separately; Rust and Python first), a separate signing key per trust domain, and mTLS. Residual risks are recorded in the RFC 0009 appendix.

Testing

  • mise run pre-commit passes
  • Affected Rust unit and integration suites pass, including gateway, sandbox, shared core, middleware, and interceptor crates
  • Workspace and E2E Rust targets pass strict Clippy checks
  • Buf lint and generated Go binding checks pass
  • Private-CA success, wrong CA, and hostname-mismatch tests added
  • Authorization, exact-audience, expiry, rotation, detachment, secret-descriptor, JWKS, and discovery-document tests added or updated
  • Explicit-typing, audience-handshake, insecure-opt-out, credential-store, and mint-bound tests added
  • Full Docker-backed E2E suite not run; no external extension fixture currently exists in that lane

Checklist

  • Follows Conventional Commits
  • Commit is signed off (DCO)
  • User-facing and architecture documentation updated
  • Agent infrastructure inventory synchronized for the new crate

Example branch

The runnable authenticated middleware example is intentionally kept on the separate 2623-authenticated-middleware-example/pimlock branch. Its service-side JWT verifier is a dedicated example module marked for future extraction into an extension-building SDK, keeping inbound verification concerns out of openshell-extension-core. After this PR lands, the branch can be rebased and merged separately or folded into later work. Its verifier must require typ: openshell-ext+jwt and return expected_audience from Describe.

Follow-up: JWKS bootstrap

Verification keys are served once the gateway is up, so a service's first contact still depends on out-of-band provisioning. /.well-known/openid-configuration covers discovery for services that can reach the gateway. The open question is whether to bind the primary listener early and serve only the well-known routes until ready, or to treat operator provisioning of the public key that certgen already writes to disk as the supported bootstrap and leave both endpoints as convenience. The latter matches how Kubernetes handles the same problem and avoids the listener-swap machinery.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
@pimlock pimlock added area:supervisor Proxy and routing-path work topic:security Security issues area:gateway Gateway server and control-plane work labels Aug 6, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

pimlock added 2 commits August 6, 2026 14:30
Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
This should become an extension SDK package.

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
@pimlock
pimlock force-pushed the feat/2623-extension-auth/pimlock branch from 6763e56 to 4dc58b4 Compare August 7, 2026 21:19
@pimlock

pimlock commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

/ok to test 4dc58b4

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

pimlock added 3 commits August 7, 2026 15:43
Follow-up hardening on the alpha extension authentication mechanism.

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

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

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

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

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
Restore the RFC 0009 and 0010 bodies to their accepted text and move
every extension-authentication update into appendices instead. An RFC
records a decision at a point in time; superseding detail belongs
alongside it rather than rewritten into it.

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

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

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

pimlock commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

/ok to test 32f93e1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:gateway Gateway server and control-plane work area:supervisor Proxy and routing-path work topic:security Security issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add shared alpha extension authentication and custom CA transport

1 participant