Validate MCPOIDCConfig inline issuer and JWKS URLs - #5936
Conversation
The MCPOIDCConfig reconciler only checked type/config consistency, so inline configs with malformed or plain-HTTP issuer and JWKS URLs were accepted and failed later in the OIDC verification path. Wire the existing validation.ValidateOIDCIssuerURL and validation.ValidateJWKSURL helpers into the controller's validation step. The checks run at the controller layer because pkg/validation imports the v1beta1 package, so the types package cannot call it without an import cycle. Add insecureAllowHTTP: true to deploy/keycloak/mcpserver-with-auth.yaml, whose plain-HTTP issuer would otherwise fail the new check. Signed-off-by: Roshan <rosh.s568@gmail.com>
There was a problem hiding this comment.
Review summary
Thanks for the PR @lopster568 !
Small, focused operator-validation fix — wires the existing ValidateOIDCIssuerURL / ValidateJWKSURL into the MCPOIDCConfig reconciler via a new validateOIDCConfigSpec() helper. The approach is sound and idiomatic: it correctly routes bad URLs through the terminal-error path (Valid=False, no requeue), preserves idempotency (the helper is a pure read-only spec check that adds no writes), and its controller-package placement over v1beta1.Validate() is properly justified — pkg/validation imports the v1beta1 package, so calling it from the types package would create an import cycle. Test coverage of the accept/reject matrix is thorough.
The one item that should gate merge is that the companion manifest change is incomplete: the shipped MCPOIDCConfig example is now rejected by the very validation this PR adds (its plain-HTTP jwksUrl has no insecureAllowHTTP escape hatch). You already flagged this in the PR description — it's an open design decision to resolve, not a hidden defect. The remaining inline comment is a low-severity test-robustness nit.
| Severity | Finding | File |
|---|---|---|
| HIGH | Shipped example manifest is rejected by the PR's own new JWKS validation | deploy/keycloak/mcpserver-with-auth.yaml |
| MEDIUM | JWKS is HTTPS-only with no insecureAllowHTTP opt-in while the issuer has one (root cause of the above) |
cmd/thv-operator/controllers/mcpoidcconfig_controller.go |
| LOW | Reconcile test asserts on the brittle substring "HTTP scheme" |
cmd/thv-operator/controllers/mcpoidcconfig_controller_test.go |
Recommendation: COMMENT — the reconciler change is correct and well-tested; resolve the manifest question (and document the JWKS asymmetry intent) before merge.
Reviewed with a multi-agent pass (operator-correctness, security/validation, tests/quality). Codex cross-review skipped (CLI not installed).
ValidateJWKSURL rejected any non-HTTPS URL with no escape hatch while ValidateOIDCIssuerURL accepts HTTP behind insecureAllowHTTP, so the same dev environment that justifies an HTTP issuer could not use an explicit HTTP JWKS URL, and the keycloak example manifest failed the validation this PR adds. Thread allowInsecure through ValidateJWKSURL, driven by the same spec.inline.insecureAllowHTTP field as the issuer check. Also assert the reconcile test on the offending URL echoed into the condition message instead of the validator's error wording. Regenerated: operator CRDs and docs/operator/crd-api.md for the updated field doc. Signed-off-by: Roshan <rosh.s568@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5936 +/- ##
==========================================
+ Coverage 72.40% 72.44% +0.03%
==========================================
Files 733 733
Lines 75845 75858 +13
==========================================
+ Hits 54918 54954 +36
+ Misses 17034 17016 -18
+ Partials 3893 3888 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
CI failure:
|
| File | Helper |
|---|---|
cmd/thv-operator/test-integration/mcp-remote-proxy/mcpremoteproxy_authserverref_integration_test.go |
newMCPOIDCConfig (issuer ~L291) |
cmd/thv-operator/test-integration/mcp-remote-proxy/mcpremoteproxy_cabundle_integration_test.go |
newMCPOIDCConfigWithCABundle (issuer ~L28) |
Adding InsecureAllowHTTP: true to those two InlineOIDCSharedConfig literals clears all 5 failures.
(The http://localhost:9090 at mcpremoteproxy_authserverref_integration_test.go:254 is a MCPExternalAuthConfig.EmbeddedAuthServer issuer — a different type/path not covered by this PR's validation — so it does not need changing.)
The deploy/keycloak manifest was updated for the new validation, but these operator integration fixtures exercising the same reconcile path were missed.
The mcp-remote-proxy integration fixtures build inline MCPOIDCConfigs with an HTTP issuer and no insecureAllowHTTP, so the new URL validation flips them to Valid=False and the referencing MCPRemoteProxy specs time out. Add the opt-in to the two fixture helpers. Signed-off-by: Roshan <rosh.s568@gmail.com>
Resolve mcpoidcconfig_controller_test.go: keep the URL validation tests added here, take main's removal of the referenceCount test from stacklok#5631. Signed-off-by: Roshan <rosh.s568@gmail.com>
|
Fixed in b1c6dfd: both fixture helpers now set Also merged main in and resolved a conflict in mcpoidcconfig_controller_test.go from #5631, keeping the URL validation tests here and taking the referenceCount removal. Branch is mergeable again. |
Summary
MCPOIDCConfig resources with a malformed or plain-HTTP
issuerorjwksUrlare accepted today. The reconciler's only spec gate,MCPOIDCConfig.Validate(), checks type/config consistency but never inspects the URLs, so a bad issuer surfaces later as an opaque OIDC verification failure. The equivalent checks ran for inline OIDC configs on MCPRemoteProxy until #4820 removed that field.validation.ValidateOIDCIssuerURL()andvalidation.ValidateJWKSURL()into the MCPOIDCConfig controller through a newvalidateOIDCConfigSpec()helper.Validate()becausepkg/validationimports thev1beta1package, so the types package cannot call it without an import cycle. This mirrors MCPRemoteProxy'svalidateSpec.spec.inline.insecureAllowHTTPmaps to the issuer validator'sallowInsecure, so HTTP issuers that opt in keep working.insecureAllowHTTP: truetodeploy/keycloak/mcpserver-with-auth.yaml, whose plain-HTTP issuer would otherwise fail the new check.Fixes #4823
Type of change
Test plan
task test)task lint-fix)TestValidateOIDCConfigSpeccovers issuer and JWKS accept/reject cases including theinsecureAllowHTTPopt-in and the empty-JWKS discovery case.TestMCPOIDCConfigReconciler_URLValidationFailureSetsConditionasserts a bad URL setsValid=False. Existing MCPOIDCConfig controller tests pass unmodified.Two
pkg/apitests (TestNewServer_ReadTimeoutConfigured,TestSecurityHeaders) fail on my machine for want of a container runtime, andtask lint-fixreports one gosec finding incmd/thv/app/upgrade.go. Both reproduce unchanged onmainand neither touches this PR's files.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.No CRD schema change; this is controller-level validation only.
Does this introduce a user-facing change?
Yes. Inline configs with a malformed issuer, an HTTP issuer without
insecureAllowHTTP: true, or a non-HTTPS JWKS URL now getValid=Falseat reconcile time instead of failing later during OIDC verification. Stored objects previously accepted with such values flip to invalid on their next reconcile.Special notes for reviewers
Scope is limited to inline configs.
kubernetesServiceAccountconfigs default their issuer to the in-cluster URL and have noinsecureAllowHTTPescape hatch, so validating them here could break clusters using custom internal issuers. Happy to follow up separately.deploy/keycloak/mcpserver-with-auth.yaml'sjwksUrlis plain HTTP andValidateJWKSURLrequires HTTPS with no escape hatch, so that manifest still fails validation after this PR. Dropping the explicitjwksUrlwould fall back to discovery, but its comment says the explicit URL exists to avoid discovery issues. Which would you prefer?