Skip to content

Add upstreamCredentialScope opt-in and enforce its issuer trust - #6458

Open
lorenzozanee wants to merge 1 commit into
stacklok:mainfrom
lorenzozanee:fix/upstream-credential-scope
Open

Add upstreamCredentialScope opt-in and enforce its issuer trust#6458
lorenzozanee wants to merge 1 commit into
stacklok:mainfrom
lorenzozanee:fix/upstream-credential-scope

Conversation

@lorenzozanee

Copy link
Copy Markdown
Contributor

Summary

Planned work on non-interactive agent credentials will read a stored upstream OAuth credential by a durable platform-user identity claim. That claim is only trustworthy when the token carrying it was validated as issued by the embedded auth server that owns the credential namespace, but today there is no way to opt a deployment into that trust model and no check that enforces it.

This PR prepares that ground without changing any existing behavior:

  • Add upstreamCredentialScope (session | platformUser) to EmbeddedAuthServerConfig, with session as the permanent kubebuilder default, and propagate it through the shared authserver.RunConfig consumed by the proxy runner and vMCP. Only a genuinely absent value maps to session; unknown non-empty values are rejected rather than reinterpreted as the default.
  • Introduce a UserTokenReader interface and a WithUserTokenReader validator option as a separate seam from the existing session-based TokenReader (which stays wired exactly as it is, keyed on tsid).
  • Carry the embedded auth server issuer on TokenValidatorConfig (AuthServerIssuer) and enforce, only when a UserTokenReader is supplied, that the configured issuer is non-empty and exactly equals it — no trimming, no trailing-slash tolerance, no fallback to the session reader.
  • Gate the matching issuer-presence/equality checks in the operator (validateOIDCConfigForEmbeddedAuthServer) and in vMCP's ValidateAuthServerIntegration on the platformUser scope, and call the latter from thv vmcp serve after side-loading the auth server config.
  • Reject runtime activation of platformUser with an explicit unsupported-capability error until platform-user storage lands; it never silently starts in session mode and never installs a no-op reader.

Fixes #6403

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

New unit tests cover: omitted-scope ≡ explicit session; unknown values rejected; CRD-to-RunConfig propagation on both MCPServer and VirtualMCPServer paths; the generated CRD schema (enum + default: session); the constructor trust precondition (matching/empty/mismatched/trailing-slash issuers, and legacy behavior without a UserTokenReader); and dormant platformUser activation failing with the unsupported-capability error at reconcile (with delegate clients) and at pod start (without).

API Compatibility

  • This PR does not break the v1beta1 API, OR the api-break-allowed label is applied and the migration guidance is described above.

The operator API change is purely additive: a new optional upstreamCredentialScope field with a permanent session default. Existing objects — including ones created before this field existed — behave identically on upgrade, and existing session-mode validation results are unchanged.

Changes

File Change
cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go New upstreamCredentialScope field + enum/default markers; EffectiveUpstreamCredentialScope helper
pkg/authserver/config.go Scope type/constants, RunConfig.UpstreamCredentialScope, effective-default resolution, validation
cmd/thv-operator/pkg/controllerutil/authserver.go Scope propagation; scope-gated issuer checks in embedded-authserver OIDC validation
pkg/auth/token.go TokenValidatorConfig.AuthServerIssuer, WithUserTokenReader, construction-time issuer precondition
pkg/auth/upstreamtoken/types.go UserTokenReader interface (+ generated mock)
pkg/vmcp/config/validator.go Scope-gated platformUser trust checks in ValidateAuthServerIntegration
pkg/vmcp/cli/serve.go Call ValidateAuthServerIntegration on the side-loaded auth server config
deploy/charts/operator-crds/..., docs/... Regenerated CRD manifests, CRD API docs, OpenAPI docs

Does this introduce a user-facing change?

No for existing deployments: the default session scope keeps the current session-based token wiring, and none of the new checks run for it. platformUser is a new explicit opt-in that currently validates its trust configuration and then fails with an explicit unsupported-capability error until platform-user storage is implemented.

Special notes for reviewers

  • The runtime rejection of platformUser lives in authserver.RunConfig.Validate() rather than pkg/runner/runner.go: both the proxy runner and thv vmcp serve construct the embedded auth server through NewEmbeddedAuthServer, which validates the RunConfig first, so one check covers both entry points.
  • thv vmcp serve now calls ValidateAuthServerIntegration, which the operator already applies at reconcile time. Hand-written vMCP configs that violate pre-existing rules (e.g. issuer consistency between the auth server and incoming auth) will now fail at startup; operator-managed configs already passed these checks. The newly introduced issuer requirements are gated on platformUser only.
  • Known limitation, flagged for the follow-up storage issue: an older runtime deserializes a newer config with plain yaml.Unmarshal/json.Unmarshal and would silently drop the field. Today this is unreachable because platformUser cannot activate anywhere; when storage support wires a real UserTokenReader, that issue needs an explicit required-capability/version check so an explicitly configured platformUser never silently degrades to session mode.

Planned platform-user credential work needs deployments to opt in to
trusting a durable platform-user identity claim, and needs the token
carrying that claim validated as issued by the embedded auth server
that owns the credential namespace. Neither the configuration seam nor
the enforcement exists today.

Add upstreamCredentialScope (session | platformUser) to
EmbeddedAuthServerConfig with session as the permanent default, and
propagate it through the shared authserver RunConfig consumed by the
proxy runner and vMCP. Only a genuinely absent value maps to session;
unknown non-empty values are rejected.

Introduce a separate UserTokenReader seam and enforce, only when one
is supplied, that the configured issuer is non-empty and exactly
equals the embedded auth server issuer carried on
TokenValidatorConfig.AuthServerIssuer. Gate the matching operator and
vMCP checks on platformUser, call ValidateAuthServerIntegration from
thv vmcp serve after side-loading the auth server config, and reject
runtime activation of platformUser as unsupported until platform-user
storage lands. Existing deployments keep identical session behavior:
the session TokenReader wiring, the tsid claim, and every
session-mode validation result are unchanged.

Fixes stacklok#6403

Signed-off-by: lorenzozanee <wyz0707@proton.me>
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.

Add upstreamCredentialScope opt-in and enforce its issuer trust

1 participant