Repository navigation
Conversation
Shared test fixtures live under test/testkit. Move the redistls helper added for Redis session storage TLS there and update its importers; the fixture itself is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
Session storage now supports TLS, but omitting it keeps the previous plaintext connection so existing deployments are unaffected. Make that choice visible: when a Redis password is configured, the proxy runner and vMCP session stores log one startup WARN if TLS is not set or certificate verification is disabled. The connection is still allowed and the password is never logged. The message also notes that the operator-wide default Redis (TOOLHIVE_DEFAULT_REDIS_ADDR) cannot enable TLS yet. The rate limiter always shares these settings, so it does not warn again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
#6731 lets the proxy runner and vMCP connect to Redis session storage over TLS, but only through hand-written RunConfig or vMCP YAML. Operator-managed workloads had no way to request it. Add spec.sessionStorage.tls to MCPServer, MCPRemoteProxy and VirtualMCPServer. It reuses the existing RedisTLSConfig type from the embedded auth server's Redis storage: - An empty object verifies the server certificate against the system roots. - caCertSecretRef mounts a private CA from a Secret at /etc/toolhive/session-redis-tls/ca.crt, read-only and via subPath, as for the auth server's Redis CA. - insecureSkipVerify disables verification, for testing only. The operator writes these settings into the RunConfig (scaling_config.session_redis.tls) and the vMCP config (sessionStorage.tls). On VirtualMCPServer the operator value replaces any spec.config.sessionStorage.tls, like the other session storage fields. Switching the referenced CA Secret rolls the Deployment. CEL rules on SessionStorageConfig reject tls with a non-redis provider, and insecureSkipVerify combined with caCertSecretRef. They sit on SessionStorageConfig rather than on the shared type so existing auth server resources are unaffected. The shared insecureSkipVerify description now recommends caCertSecretRef instead. Refs #6730 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
Add coverage for the Redis session storage TLS paths that were not exercised end to end: - Runner.Run test showing the session store client takes its TLS settings from the RunConfig. Without TLS it does not handshake. With TLS, the server certificate and the hostname from the configured address are verified. A trusted CA connects. - The rate limiter and the vMCP limiter factory now also cover the omitted-TLS case against a TLS-only server, and match the certificate-verification error precisely. - An e2e spec that runs standalone vMCP against a TLS-only, password-protected Redis container. It opens a session and checks over TLS that the session was stored. The redistls fixture gains on-disk certificates with an IP-only server certificate. A real Redis server uses them, and they make a hostname mismatch observable. Refs #6730 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6767 +/- ##
==========================================
+ Coverage 79.40% 79.46% +0.05%
==========================================
Files 808 810 +2
Lines 82022 82147 +125
==========================================
+ Hits 65133 65281 +148
+ Misses 16884 16861 -23
Partials 5 5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The TLS-only Redis e2e spec bind-mounts a generated CA, certificate and key into the Redis container. Only the key was made world-readable, so on Linux hosts, where the container's redis user does not own the mounted files, Redis could not read the 0600 certificate and CA, failed to configure TLS and exited before answering PING. Docker Desktop on macOS does not enforce file ownership on bind mounts, which hid this locally. Make all three files readable inside the private test directory. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
The session storage CA drift check compared the live Deployment with the raw desired volume. On an MCPRemoteProxy whose podTemplateSpec patches that volume, every reconcile saw drift and issued a no-op update. The check now compares live volumes with the desired ones, and the remote proxy uses the rebuilt, patched Deployment when a podTemplateSpec is set. Reject configurations that would silently misbehave: - spec.config.sessionStorage.tls on a VirtualMCPServer was accepted but discarded by the converter, leaving the connection plaintext. - A caCertSecretRef with an empty name or key passed admission but produced a volume the Deployment API rejects on every reconcile. The insecure-transport warning moves into the Redis session store constructors so every caller gets it, and it now also fires without a password because session data still crosses the network in plaintext. Its text no longer names configuration keys that only apply to some callers. Also share one Secret-file volume helper between the session store and the embedded auth server's Redis CAs, build the redistls fixture on a single certificate generator, and drop a runner test case that only exercised crypto/tls and go-redis. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
fosite's GetAudienceStrategy and GetJWKSFetcherStrategy assign their default to the shared Config on first use. The embedded auth server left both unset, so concurrent token requests raced on that write. The race detector flagged it in the pkg/authserver integration tests, which fail on main as well. Set both to fosite's defaults when building the Config, as is already done for the scope strategy and secrets hasher. Behavior is unchanged; the getters simply no longer write. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
Sanskarzz
left a comment
There was a problem hiding this comment.
Thanks for completing the operator integration for #6730 after #6731. I reviewed head ca46e64685d85e43ef143b0b078cd856b7624583, tracing the API fields through configuration conversion, Secret projection, and deployment updates for MCPServer, MCPRemoteProxy, and VirtualMCPServer.
I found no blocking issues in the changes reviewed:
- The shared
SessionStorageConfig.TLSfield is wired into all three runtime configuration paths. An omitted field preserves plaintext behavior, whiletls: {}enables certificate verification using system roots. A custom CA is passed as a mounted file, and TLS configuration or handshake failures do not fall back to plaintext. - The CA Secret projection and deployment update handling account for changes to the Secret name or key, including the remote proxy PodTemplate path. The documented restart requirement for changes to the contents of the same Secret matches the
subPathmount and startup-time CA loading. - The session storage CEL rules reject TLS with the memory provider, incomplete CA references, and the combination of a custom CA with
insecureSkipVerify. Rejecting VirtualMCPServer's nestedspec.config.sessionStorage.tlsalso prevents a setting from being accepted and then discarded by the converter. - The startup warnings preserve existing connection behavior without logging the Redis password. The shared certificate fixture, runtime wiring tests, controller tests, and real Redis TLS e2e test provide useful coverage. The e2e test checks that a legacy session is actually persisted in Redis.
Optional, non-blocking test suggestion: the new TLS admission cases exercise VirtualMCPServer. Could we extend the table to MCPServer and MCPRemoteProxy, covering both served API versions? The generated schemas currently contain the same rules across all three resources, so I do not see a current correctness issue; this would protect that consistency against future schema changes.
Operator-wide defaultRedis TLS remains a separate follow-up, as agreed in #6731. The eager initialization of the two Fosite strategies also preserves their existing defaults while avoiding lazy writes to shared configuration.
Verification: source and generated-schema inspection, a clean git diff --check, and review of the reported CI results. I did not rerun the test suites locally. With the current passing checks, I consider this ready to merge; the additional admission coverage can be a follow-up.
The previous commit set fosite's JWKS fetcher to its default to avoid a lazy write to the shared Config. That fetcher is never used: DCR rejects jwks_uri for private_key_jwt and storage drops it, so client keys are always inline. Creating it eagerly only started a ristretto cache with goroutines that are never closed, for every auth server instance. Use a fetcher that rejects the client instead. It still keeps fosite's getter from writing, starts nothing, and fails closed rather than fetching an arbitrary URL with an unhardened client if a client with a jwks_uri ever reached client authentication. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
The sessionStorage.tls CEL rules live on the shared SessionStorageConfig type, but admission tests only covered VirtualMCPServer at v1beta1. A schema change could drop a rule from one kind or one served version without any test noticing. Run one shared table of TLS admission cases against MCPServer, MCPRemoteProxy and VirtualMCPServer at both v1beta1 and v1alpha1. Also document that workloads using the operator-wide defaultRedis fallback always log the plaintext warning, and that vMCP's no-authentication warning is separate from it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Giuseppe Scuglia <peppescg@gmail.com>
Summary
Follow-up to #6731 (merged), which added Redis TLS for session storage at the runtime level: proxy runner, vMCP and both rate limiters. Operator-managed workloads still had no way to request TLS, and turning it off went unnoticed. This PR completes the feature requested in #6730.
MCPServer,MCPRemoteProxyandVirtualMCPServercan now setspec.sessionStorage.tls. Without it, theredisconfig.TLSConfigadded in Add TLS to Redis session storage #6731 was only reachable through hand-written RunConfig or vMCP YAML. The field reuses the existingRedisTLSConfigtype from the embedded auth server's Redis storage:caCertSecretRefmounts a private CA from a Secret (read-only,subPath, same pattern as the auth server's Redis CA).insecureSkipVerifyis an opt-out, for testing only.scaling_config.session_redis.tls) and the vMCP config (sessionStorage.tls).tlswith a non-redisprovider,insecureSkipVerifytogether withcaCertSecretRef, acaCertSecretRefwith an empty name or key, andspec.config.sessionStorage.tlson aVirtualMCPServer(the converter discards it, so accepting it would silently leave the connection plaintext).podTemplateSpecpatch, so a patch that overrides the volume is not reported as drift on every reconcile.Runner.Run-level wiring test.redistlsfixture moved totest/testkit, where shared test fixtures live, and now uses a single certificate generator.Closes #6730
Type of change
Test plan
task test): full repository with-race, exit 0. The first run hit an unrelated flake inpkg/ignore(TestGetOverlayPaths, which passes in isolation); the rerun was cleanTOOLHIVE_SKIP_DESKTOP_CHECK=1 LABEL_FILTER=infra task test-e2e): 3/3 passed. The new spec "stores sessions over verified TLS with password authentication":redis:7-alpinewith only a TLS port andrequirepass;thv vmcp servewithsessionStorage.tls.caCertFileandTHV_SESSION_REDIS_PASSWORD;redis-cli --tlsthat the session key exists.task lint-fix,task lint): 0 issuesManual testing and other checks:
task license-check: pass.operator-generate,operator-manifests,crdref-gen) andtask docs: rerunning them produces no diff.operator-test-integration): all 12 suites passed. One unrelated timeout in theMCPToolConfigdeletion spec passed on rerun. This includes the new CEL specs:tlswith providermemoryis rejected;insecureSkipVerifywithcaCertSecretRefis rejected;tlsis accepted;task lint-fix0 issues;task testexit 0;operator-test-integrationpassed except one spec (MCPRemoteProxy AuthServerRef … externalAuthConfigRef only … without Failed phase), a race where the phase is checked before the MCPOIDCConfig is validated, which passed 8/8 when rerun alone. The two new CEL specs (emptycaCertSecretRefname/key,spec.config.sessionStorage.tls) pass. The newpodTemplateSpecdrift test fails against the previous drift check and passes with the fix.sessionStorage.tlsadmission table (7 cases) passes onMCPServer,MCPRemoteProxyandVirtualMCPServerat bothv1beta1andv1alpha1(envtest);task testexit 0;go test -race -count=20 -run TestIntegration ./pkg/authserver/clean. TheMCPOIDCConfigsuite has a flaky deletion-protection spec that also fails 2/3 on cleanmain.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.spec.sessionStorage.tlsis a new optional field. Three of the CEL rules only constrain this new field. The fourth rejectsspec.config.sessionStorage.tlsonVirtualMCPServer, a field added in #6731 that is not in any release yet. No existing object becomes invalid. On the sharedRedisTLSConfigtype, only theinsecureSkipVerifyandcaCertSecretRefdescriptions change.Changes
cmd/thv-operator/api/v1beta1/mcpserver_types.goSessionStorageConfig.TLS *RedisTLSConfig(optional) and three CEL rulescmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.goinsecureSkipVerifyis for testing (prefercaCertSecretRef); the doc notes that a missing CA Secret keeps the pod inContainerCreatingcmd/thv-operator/api/v1beta1/virtualmcpserver_types.gospec.config.sessionStorage.tlscmd/thv-operator/pkg/controllerutil/session_redis_tls.goredisconfig.TLSConfig; builds the CA Secret volume and mount; detects volume drift against the desired volumescmd/thv-operator/pkg/controllerutil/authserver.gocmd/thv-operator/controllers/mcpserver_*,mcpremoteproxy_*,virtualmcpserver_deployment.gopodTemplateSpec-patched Deployment)cmd/thv-operator/pkg/vmcpconfig/converter.gosessionStorage.tlsfromspec.sessionStorage.tlspkg/transport/session/redis_config.go,storage_redis.go,session_data_storage_redis.gopkg/runner/runner.go,pkg/vmcp/server/server.gostoreattribute on the runner's Redis INFO log; doc updatespkg/vmcp/config/config.gospec.sessionStorage.tlstest/testkit/redistls/test/helpers/redistls; on-disk CA and IP-only server certificates from a single generatorpkg/runner(Run-level),pkg/ratelimit,pkg/vmcp/ratelimit/factory,pkg/vmcp/server(warning), e2etest/e2e/vmcp_infra_features_test.gopkg/authserver/server/provider.gocmd/thv-operator/test-integration/testutil/session_storage_tls.gosessionStorage.tlsadmission casesdocs/operator/crd-api.md,cmd/vmcp/README.md,docs/arch/13-vmcp-scalability.mdDoes this introduce a user-facing change?
Yes. Operator-managed workloads can enable TLS for Redis session storage, and the same settings apply to Redis-backed rate limiting:
If Redis session storage runs without TLS, or with
insecureSkipVerify, the proxy runner and vMCP log a startup warning. On aVirtualMCPServer,spec.config.sessionStorage.tlsis now rejected; usespec.sessionStorage.tls.Special notes for reviewers
Behavior changes for existing deployments:
tlskeeps the current behavior. The only visible change is the new startup WARN when TLS is not verified, with or without a password.tls, or switching the referenced CA Secret, rolls the Deployment.ContainerCreating, the same as for the embedded auth server's Redis CA.subPath, so rotating the Secret's content needs a pod restart.Where the CEL rules live: on
SessionStorageConfig, not on the sharedRedisTLSConfig. That type is also used byMCPExternalAuthConfigRedis storage, and a type-level rule could start rejecting existing auth-server resources.spec.config.sessionStorage.tlsonVirtualMCPServer: the converter overwritesspec.config.sessionStoragefromspec.sessionStorage, so a value there was silently discarded. A CEL rule now rejects it. The field was added in Add TLS to Redis session storage #6731, which is not in a release yet, so no stored objects are affected.Rate limiter: it always shares the session storage settings, so only the session stores emit the warning.
Unrelated CI fix included on request:
pkg/authservertests failed under-race(also onmain). fosite'sGetAudienceStrategyandGetJWKSFetcherStrategyassign their defaults to the sharedConfigon first use, and the embedded auth server left both unset, so concurrent token requests raced on that write. The fix sets the audience strategy to fosite's default, like the scope strategy and secrets hasher already are. The JWKS fetcher is never used, because DCR rejectsjwks_uriand storage drops it, so instead of fosite's default (a ristretto cache whose goroutines are never closed) it is a fetcher that rejects the client withinvalid_client. This fails closed if a client with ajwks_uriever reached client authentication. Behavior for supported clients is unchanged. Reproduced locally withgo test -race -count=10 -run TestIntegration ./pkg/authserver/; with the fix, 20 and 40 repetitions were clean.Not addressed here:
tls. Making decoding strict would break operator/runner version skew for every field, not just this one.Runner.Rundoes not close the Redis session store when a later startup step fails. This predates the PR (Add TLS to Redis session storage #6731) and belongs in a separate fix.Follow-ups:
defaultRedisfallback (TOOLHIVE_DEFAULT_REDIS_*). It needs Helm values and a CA mount.redisconn.TLSConfigsupports them, but neither CRD type exposes them yet.Size: about 300 non-generated production lines across 19 files, which is over the 10-file guideline. It is split into commits:
podTemplateSpec, two CEL rules, warning moved into the store constructors, shared volume helper);sessionStorage.tlsadmission cases on every kind and served version (review suggestion).Reviewing commit by commit should be easier.
🤖 Generated with Claude Code