Skip to content

VirtualMCPServer.authServerConfig.upstreamProviders has no way to trust a private CA for the upstream token/authorization endpoints #6417

Description

@jhrozek

Bug description

When an authServerConfig.upstreamProviders[] entry points at an upstream IdP served over HTTPS with a private/internal CA (e.g. an in-cluster Dex behind a cert-manager-issued cert), vMCP's embedded auth server fails the authorization-code exchange with:

tls: failed to verify certificate: x509: certificate signed by unknown authority

Neither upstreamProviders[].oauth2Config nor .oidcConfig has a field to supply a trusted CA bundle for the outbound HTTP client vMCP uses to talk to that upstream's authorizationEndpoint/tokenEndpoint (or OIDC-discovered equivalents). Confirmed via kubectl explain on both:

virtualmcpserver.spec.authServerConfig.upstreamProviders.oauth2Config
virtualmcpserver.spec.authServerConfig.upstreamProviders.oidcConfig

— neither lists a caBundle/caBundleRef-shaped field.

This is asymmetric within the same CRD: MCPOIDCConfig (used for incoming token validation on incomingAuth.oidcConfigRef) does support caBundleRef. Only the outbound leg — vMCP acting as an OAuth client against the upstream provider — has no equivalent.

Reproduction

  • ToolHive v0.44.0
  • In-cluster Dex serving HTTPS via a cert-manager-issued certificate, private CA
  • VirtualMCPServer with:
    authServerConfig:
      upstreamProviders:
        - name: dex
          type: oauth2
          oauth2Config:
            authorizationEndpoint: https://dex.<ns>.svc.cluster.local:5556/auth
            tokenEndpoint: https://dex.<ns>.svc.cluster.local:5556/token
            allowPrivateIPs: true
            ...
  • incomingAuth.oidcConfigRef → an MCPOIDCConfig with caBundleRef set to the same CA — this part validates incoming tokens fine.
  • Drive a full PKCE browser-style login (DCR → /oauth/authorize → Dex login → callback). The redirect to Dex and the login itself succeed (TLS validates against the CA fine from any client that's given it explicitly). vMCP's own callback handler then fails when it calls Dex's /token endpoint server-to-server, because that HTTP client has no CA configured and falls back to the system root store only.

Expected behavior

oauth2Config/oidcConfig (or a shared parent struct) should accept a CA bundle reference (mirroring MCPOIDCConfig.caBundleRef) that scopes trust to that upstream provider's outbound calls, without requiring a process-wide SSL_CERT_FILE override on the vMCP pod.

Current workaround

Setting SSL_CERT_FILE in the vMCP pod's env to a mounted CA file makes Go's x509.SystemCertPool() pick it up for all outbound HTTPS calls the process makes — this works, but it's a full override (not additive to the system trust store), scoped to the whole process rather than the specific upstream, and undocumented as the sanctioned path for this. stacklok-enterprise-platform's connector-gateway (built on the same vMCP core) uses this exact same workaround for its own private-CA upstream IdP.

Scope

  • Only affects type: oauth2/type: oidc upstream providers with a private CA; public-CA upstreams (real Okta/Entra/etc.) are unaffected.
  • Does not affect incomingAuth token validation (MCPOIDCConfig.caBundleRef already covers that).

Activity

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions