Propagate MCPOIDCConfig CA bundle to vmcp OIDC config#4923
Propagate MCPOIDCConfig CA bundle to vmcp OIDC config#4923ChrisJBurns wants to merge 2 commits intomainfrom
Conversation
VirtualMCPServer referencing an MCPOIDCConfig with a ConfigMap-backed caBundleRef mounted the CA into the pod but never told the vmcp binary where to find it. OIDC discovery against a self-signed issuer then failed with "x509: certificate signed by unknown authority". The vmcp OIDCConfig had no CA-bundle field, so the operator converter silently dropped the resolver's ThvCABundlePath. This mirrors the earlier MCPServer fix (#3142, #3391). - Add CABundlePath to pkg/vmcp/config.OIDCConfig with absolute-path, no-traversal, no-null-byte validation consistent with StaticBackendConfig.CABundlePath. - Populate CABundlePath from resolved.ThvCABundlePath in the operator's mapResolvedOIDCToVmcpConfigFromRef, covering both inline caBundleRef and the Kubernetes service-account CA default. - Wire CABundlePath into auth.TokenValidatorConfig.CACertPath in newOIDCAuthMiddleware so the incoming-OIDC middleware loads the CA into its HTTP client for discovery, JWKS, and introspection. - Add unit tests at each layer and a VirtualMCPServer envtest scenario asserting the rendered vmcp-config ConfigMap contains caBundlePath and the Deployment mounts the ConfigMap read-only at the expected path. Closes #4918 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
task operator-manifests + task crdref-gen after adding CABundlePath to pkg/vmcp/config.OIDCConfig. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4923 +/- ##
==========================================
+ Coverage 69.36% 69.39% +0.02%
==========================================
Files 538 538
Lines 55462 55469 +7
==========================================
+ Hits 38473 38491 +18
+ Misses 14033 14026 -7
+ Partials 2956 2952 -4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jhrozek
left a comment
There was a problem hiding this comment.
LGTM. Small, focused fix that mirrors the established StaticBackendConfig.CABundlePath validation pattern and the earlier MCPServer CA-bundle propagation fixes (#3142, #3391).
Tests cover every layer:
- Validator: valid/relative/
../null-byte cases - Operator converter: inline, KSA default, and empty
ThvCABundlePathcases - Auth middleware wiring: valid PEM, empty, and nonexistent-path (proves the field is consumed, not silently dropped)
- Envtest: asserts the rendered vmcp-config ConfigMap contains the mounted CA path AND the Deployment mounts the ConfigMap read-only at
/config/certs/<cm-name>
Non-blocking nit: pkg/vmcp/config/validator.go:121-129 and :169-177 are now two near-identical CA-bundle validation blocks. Extracting a validateCABundlePath(path, field string) error helper would prevent drift if a third caller ever appears — but with only two call sites, leaving it inline is defensible.
|
@jerm-dro I think this is one of those changes that reveal the leakage onto CRDs. The |
Summary
VirtualMCPServer referencing an
MCPOIDCConfigwith a ConfigMap-backedcaBundleRefmounted the CA into the pod but never told the vmcp binary where to find it, so OIDC discovery against a self-signed issuer failed withx509: certificate signed by unknown authority. This PR closes the plumbing gap by mirroring the earlier MCPServer fix (#3142, #3391): the vMCPOIDCConfignow carries the mounted CA path, and the incoming-OIDC middleware loads it into its HTTP trust store.Closes #4918
Medium level
CABundlePathtopkg/vmcp/config.OIDCConfig(consistent withStaticBackendConfig.CABundlePath) and validated it in the vMCP config validator (absolute path, no null bytes, no traversal).CABundlePathfromresolved.ThvCABundlePathinsidemapResolvedOIDCToVmcpConfigFromRefso both inlinecaBundleRefand the Kubernetes service-account CA default flow through.oidcCfg.CABundlePathintoauth.TokenValidatorConfig.CACertPathinsidenewOIDCAuthMiddleware; the downstream HTTP client builder already reads the field and appends it to the TLS trust store.caBundleRefand asserts both the rendered vmcp config ConfigMap and the Deployment's CA volume mount.Low level
pkg/vmcp/config/config.goCABundlePath stringfield toOIDCConfigwith doc comment and JSON/YAML tagcaBundlePath.pkg/vmcp/config/validator.goOIDCConfig.CABundlePath— reject null bytes,.., and non-absolute paths.pkg/vmcp/config/validator_test.gocmd/thv-operator/pkg/vmcpconfig/converter.goresolved.ThvCABundlePaththrough tovmcpconfig.OIDCConfig.CABundlePathinmapResolvedOIDCToVmcpConfigFromRef.cmd/thv-operator/pkg/vmcpconfig/converter_test.gopkg/vmcp/auth/factory/incoming.gooidcCfg.CABundlePathintoauth.TokenValidatorConfig.CACertPath.pkg/vmcp/auth/factory/incoming_cabundle_test.gocmd/thv-operator/test-integration/mcp-oidc-config/mcpoidcconfig_virtualmcpserver_integration_test.gocaBundlePath: /config/certs/<cm>/<key>and Deployment mounts the ConfigMap read-only.deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_virtualmcpservers.yamlcaBundlePathunderspec.config.incomingAuth.oidc.deploy/charts/operator-crds/templates/toolhive.stacklok.dev_virtualmcpservers.yamldocs/operator/crd-api.mdType of change
Test plan
task lint-fixpasses (0 issues)task testpassestask buildpassesSpecial notes for reviewers
CABundlePath(matchingStaticBackendConfig.CABundlePathin the same package) overCACertPath(downstreamauth.TokenValidatorConfignaming) orThvCABundlePath(operator-resolver internal name) for consistency withinpkg/vmcp/config.ThvCABundlePath = defaultK8sCABundlePathinresolveFromK8sServiceAccountConfigwhenuseClusterAuthis true, so that path now works automatically. Covered by a dedicated converter test case.pkg/audit/zz_generated.deepcopy.gois stale relative to theDetectApplicationErrorsfield onpkg/audit.Config. Surfaced when runningtask operator-generate, but it's out of scope for this PR — would recommend a separate follow-up to regenerate.Generated with Claude Code