Skip to content

feat(controller): serve the gateway JWKS off the management listener - #240

Open
yuval-k wants to merge 4 commits into
mainfrom
agents/implement-item-a4
Open

yuval-k wants to merge 4 commits into
mainfrom
agents/implement-item-a4

Conversation

@yuval-k

@yuval-k yuval-k commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

LLM gateways need the controller's JWKS to verify the JWTs it issues. The management router also served /.well-known/jwks.json, so a gateway configured against it had to reach the management API, which can read the device inventory and delete devices. This is item A4 of the control plane hardening plan.

  • The management router no longer serves the JWKS. The fleet listener still does, so existing gateway configurations (including the Kubernetes example) keep working.
  • New optional gatewayJwt.jwksListener (listen, tls) serves only the JWKS: plain HTTP by default, or HTTPS with the controller certificate from tls. Point gateways at it so a NetworkPolicy or firewall can allow them that one port. Gateways that export usage still need the fleet port. The listener must use a port other than fleetListen and adminListen.
  • Helm: when gatewayJwt (or existingConfigSecret) is set, the chart adds a <release>-jwks ClusterIP Service, a jwks container port and the jwksListener config, controlled by new gatewayJwks values (port 8081; Service port 80, or 443 with tls). With existingConfigSecret, that configuration must set jwksListener itself.
  • examples/claude fetches the JWKS from the new listener on 127.0.0.1:8081 instead of the management port.
  • README, docs/device-identity.md and the generated controller config reference describe where gateways fetch the JWKS and why.

Testing

  • New tests: config parsing and port validation; the dedicated listener over HTTP and HTTPS (JWKS served, other paths 404, plaintext rejected on the TLS listener); and the management router no longer returning a JWKS.
  • cargo test -p agentdesktop-controller -p agentdesktop-core, clippy with -D warnings, cargo fmt --check, and cargo xtask schema with no diff.
  • helm lint, plus helm template for each variant (default, tls: true, disabled, no gatewayJwt, examples/kubernetes/values.yaml); the rendered controller.yaml validates against the generated schema.
  • Ran the controller binary in both modes: the dedicated listener and the fleet listener return the JWKS, and the management listener returns the UI page at that path.

Not included

The controller settings page does not show the new listener yet.

LLM gateways need the controller's JWKS to verify the JWTs it issues.
Until now the management router also served /.well-known/jwks.json, so a
gateway configured against it had to reach the management API, which
can read the device inventory and delete devices.

- The management router no longer serves the JWKS. The fleet listener
  still does, so existing gateway configurations keep working.
- New optional `gatewayJwt.jwksListener` (`listen`, `tls`) serves only
  the JWKS, over plain HTTP or over HTTPS with the controller certificate.
  Point gateways at it so a NetworkPolicy or firewall can allow them that
  one port. Gateways that export usage still need the fleet port.
- Helm: a `<release>-jwks` ClusterIP Service, container port and
  `jwksListener` config, controlled by `gatewayJwks` values.
- examples/claude fetches the JWKS from the new listener on 8081.

Hardening plan item A4.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@yuval-k
yuval-k marked this pull request as ready for review November 11, 2026 20:39
yuval-k and others added 3 commits November 11, 2026 16:02
`RustlsConfig::from_pem_file` relies on the process-default rustls
CryptoProvider. Workspace builds enable both the aws-lc-rs and ring
features, so rustls cannot pick one and the HTTPS JWKS listener panicked
at startup (and in its test under `cargo test --workspace`).

Reuse the management listener's explicit aws-lc-rs config, renamed to
`tls_server_config` with a label for its error messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move `tls_server_config` from the controller's management module to
`agentdesktop_core::tls::server_config` so any crate can build a rustls
server config without relying on the process-default CryptoProvider.
Core already depends on rustls with aws-lc-rs through reqwest, so the
direct dependency adds nothing to the build; the controller no longer
needs its own.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
#226 bumped react and lucide-react in the controller and desktop apps
but not in the shared @agentdesktop/ui package, so pnpm installed a
second copy (react 19.2.8, lucide-react 1.31.0) for it. With a cold
Vite cache, the controller storybook tests loaded both copies and 89 of
92 failed with "Invalid hook call"; runs that reused a warm cache passed.

Raise the peer ranges to react ^19.3.1 and lucide-react ^1.67.0 so the
lockfile resolves a single copy of each.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A 63-character fullnameOverride causes the JWKS Service name to collide with the fleet Service name, preventing independent deployment when JWKS is enabled.

1 open issue
1 file not reviewed
  • frontend/pnpm-lock.yaml: Generated file
What changed in this PR

Separates gateway key discovery from the controller’s management API. An optional HTTP/HTTPS listener serves only the gateway JWKS, while the fleet endpoint remains available for compatibility.

Changes:

  • Removes management JWKS routing, adds the dedicated listener and validation, and shares TLS setup through the core crate.
  • Adds Helm configuration and Service support, updates examples and documentation, and adds listener tests.
  • Aligns shared UI peer dependencies and lockfile entries.
File Description
schema/​controller-config.md Documents dedicated listener settings.
schema/​controller-config.json Defines the listener configuration schema.
README.md Explains JWKS endpoints and network isolation.
frontend/​ui/​package.json Updates React and icon peer dependencies.
frontend/​pnpm-lock.yaml Aligns resolved frontend dependencies.
examples/​claude/​controller.yaml Enables the dedicated local listener.
examples/​claude/​agentgateway.yaml Points JWKS requests at the new port.
docs/​device-identity.md Clarifies where gateways retrieve public keys.
deploy/​helm/​agentdesktop-controller/​values.yaml Adds JWKS listener and Service options.
deploy/​helm/​agentdesktop-controller/​templates/​service-jwks.yaml Defines the dedicated JWKS Service.
deploy/​helm/​agentdesktop-controller/​templates/​NOTES.txt Displays the gateway JWKS URL.
deploy/​helm/​agentdesktop-controller/​templates/​deployment.yaml Declares the optional JWKS container port.
deploy/​helm/​agentdesktop-controller/​templates/​configmap.yaml Renders dedicated listener configuration.
deploy/​helm/​agentdesktop-controller/​templates/​_helpers.tpl Adds JWKS enablement, naming, and port helpers.
crates/​core/​src/​tls.rs Adds shared server TLS configuration and tests.
crates/​core/​src/​lib.rs Exports the TLS module.
crates/​core/​src/​config.rs Adds listener settings, port validation, and tests.
crates/​core/​Cargo.toml Adds TLS and test dependencies.
crates/​controller/​src/​management/​auth/​tests.rs Tests management JWKS removal and updates signatures.
crates/​controller/​src/​management.rs Removes JWKS routing and reuses shared TLS setup.
crates/​controller/​src/​gateway_jwt.rs Implements and tests the HTTP/HTTPS JWKS listener.
crates/​controller/​src/​application.rs Runs the optional listener alongside existing APIs.
crates/​controller/​Cargo.toml Removes the direct Rustls dependency.
Cargo.lock Records the Rust dependency changes.

Review effort: Balanced

Comment on lines +26 to +27
{{- printf "%s-jwks" (include "agentdesktop-controller.fullname" .) | trunc 63 | trimSuffix "-" }}
{{- end }}

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants