Skip to content

Add CEL validation rule for RemoteMCPServer with CA Cert and AllowedNamespaces#1988

Merged
EItanya merged 3 commits into
mainfrom
iplay88keys/disable-cross-ns-remotemcpserver
Jun 12, 2026
Merged

Add CEL validation rule for RemoteMCPServer with CA Cert and AllowedNamespaces#1988
EItanya merged 3 commits into
mainfrom
iplay88keys/disable-cross-ns-remotemcpserver

Conversation

@iplay88keys

@iplay88keys iplay88keys commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a CEL admission rule on RemoteMCPServer that rejects setting both spec.allowedNamespaces and spec.tls.caCertSecretRef. A RemoteMCPServer that pins a CA Secret must be same-namespace-only; one that allows cross-namespace references must not pin a CA Secret.

This closes a silent footgun in the cross-namespace + TLS combination without changing any working behavior: the AllowedNamespaces field stays, the reconciler is unchanged, and non-CA cross-namespace sharing keeps working exactly as before.

Why

When an agent consumes a RemoteMCPServer that pins a CA bundle, the translator mounts that Secret onto the agent's pod by bare name (addTLSConfiguration). A SecretVolumeSource has no namespace field — Kubernetes always resolves it in the pod's own namespace, not the RemoteMCPServer's. So if the RMS is referenced cross-namespace, the CA mount either:

  • dangles — the Secret doesn't exist in the agent's namespace and the pod fails to start; or
  • silently mounts the wrong CA — an unrelated Secret of the same name that happens to exist in the agent's namespace is trusted for the upstream, with no error anywhere.

Meanwhile the controller validates and hashes the Secret in the RemoteMCPServer's namespace, so it reports Accepted=true while the pod mount looks elsewhere. The cert reference itself is structurally same-namespace (caCertSecretRef is a bare name with no namespace field, resolved in the owner's namespace by design); the only thing that broke co-location was opening the RMS object up cross-namespace via allowedNamespaces. Making the two mutually exclusive removes the unsound combination at admission time rather than letting it surface as a runtime mount failure on the consuming agent.

Validation rule

Spec-level XValidation on RemoteMCPServerSpec:

rule:    !(has(self.allowedNamespaces) && has(self.allowedNamespaces.from)
            && (self.allowedNamespaces.from == 'All' || self.allowedNamespaces.from == 'Selector')
            && has(self.tls) && has(self.tls.caCertSecretRef) && size(self.tls.caCertSecretRef) > 0)
message: spec.allowedNamespaces must not permit cross-namespace access (from: All or from: Selector)
         when spec.tls.caCertSecretRef is set: a pinned CA Secret is mounted onto the consuming agent's
         pod and Kubernetes resolves it in the agent's namespace, not this RemoteMCPServer's. Use
         from: Same (the default), or remove the CA Secret reference.

The rule only rejects the genuinely-hazardous case — a CA Secret combined with an allowedNamespaces that actually permits other namespaces (from: All or from: Selector). from: Same (or omitted/empty, which defaults to same-namespace) is always allowed alongside a CA Secret, since it carries no cross-namespace mount hazard.

Regenerated kagent.dev_remotemcpservers.yaml and synced the helm CRD copy under helm/kagent-crds/templates/.

Tests

Added cases to the existing envtest CEL suite (tlsconfig_cel_test.go), exercised against a real kube-apiserver:

  • allowedNamespaces from: All + caCertSecretRef → rejected.
  • allowedNamespaces from: Selector + caCertSecretRef → rejected.
  • allowedNamespaces from: Same + caCertSecretRef → accepted (same-namespace, no hazard).
  • allowedNamespaces from: All, no TLS → accepted (cross-namespace sharing still works).
  • caCertSecretRef, no allowedNamespaces → accepted (same-namespace CA pinning still works).

Compatibility

Not a breaking API change — no field is removed and no working configuration changes behavior. The only new effect is at admission: a manifest that combines a CA Secret with a cross-namespace-permitting allowedNamespaces (from: All / from: Selector) is now rejected on create/update. That combination was already non-functional (dangling or wrong-CA mount), so this turns a silent runtime failure into an explicit, actionable admission error. Same-namespace configurations (from: Same or omitted) are unaffected, and objects already stored are not re-validated until their next write.

@iplay88keys iplay88keys marked this pull request as ready for review June 9, 2026 15:28
Copilot AI review requested due to automatic review settings June 9, 2026 15:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes cross-namespace RemoteMCPServer usage by (1) deleting spec.allowedNamespaces from the RemoteMCPServer API/CRD and (2) making the reconciler reject any cross-namespace RemoteMCPServer reference, to prevent incorrect/dangling TLS CA Secret mounts due to Secrets being namespace-scoped at pod volume resolution time.

Changes:

  • Removed RemoteMCPServerSpec.AllowedNamespaces from the Go API types and regenerated generated code/CRDs.
  • Updated agent reconcile-time validation to deny all cross-namespace RemoteMCPServer references with a clear error.
  • Updated/added tests, including a translator-level characterization test documenting the dangling Secret mount behavior in cross-namespace scenarios.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
helm/kagent-crds/templates/kagent.dev_remotemcpservers.yaml Removes spec.allowedNamespaces from the Helm-packaged RemoteMCPServer CRD schema.
go/core/internal/controller/translator/agent/remotemcpserver_tls_test.go Adds a translator characterization test showing why cross-namespace RMS TLS CA mounts are unsafe.
go/core/internal/controller/reconciler/reconciler.go Makes validateMcpServerReference reject cross-namespace RemoteMCPServer references unconditionally.
go/core/internal/controller/reconciler/reconciler_test.go Updates cross-namespace RMS validation tests to expect denial.
go/api/v1alpha2/zz_generated.deepcopy.go Regenerates deepcopy code to reflect removal of AllowedNamespaces from RemoteMCPServerSpec.
go/api/v1alpha2/remotemcpserver_types.go Removes RemoteMCPServerSpec.AllowedNamespaces from the public API type.
go/api/config/crd/bases/kagent.dev_remotemcpservers.yaml Removes spec.allowedNamespaces from the generated RemoteMCPServer CRD schema.
Files not reviewed (1)
  • go/api/v1alpha2/zz_generated.deepcopy.go: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

EItanya
EItanya previously requested changes Jun 9, 2026

@EItanya EItanya left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't do this, it's a breaking API change

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@iplay88keys iplay88keys force-pushed the iplay88keys/disable-cross-ns-remotemcpserver branch from ed3c5f1 to 53213d4 Compare June 9, 2026 21:02
@iplay88keys

Copy link
Copy Markdown
Contributor Author

We can't do this, it's a breaking API change

Makes sense. I have reworked this to be a CEL validation rule instead so you cannot create a RemoteMCPServer with both spec.allowedNamespaces and spec.tls.caCertSecretRef set.

@iplay88keys iplay88keys changed the title Disable cross-namespace remotemcpserver attachment for declarative agents Add CEL validation rule for RemoteMCPServer with CA Cert and AllowedNamespaces Jun 9, 2026
@iplay88keys iplay88keys requested a review from Copilot June 9, 2026 21:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread go/api/v1alpha2/remotemcpserver_types.go Outdated
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
…ross-ns-remotemcpserver

Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
@iplay88keys iplay88keys requested a review from EItanya June 9, 2026 21:54
@iplay88keys iplay88keys dismissed EItanya’s stale review June 11, 2026 15:29

Changed approach to no longer break the API. I am instead adding CEL rules to reject when spec.allowedNamespaces and spec.tls.caCertSecretRef are both set.

@EItanya EItanya merged commit 9afbe4e into main Jun 12, 2026
23 checks passed
@EItanya EItanya deleted the iplay88keys/disable-cross-ns-remotemcpserver branch June 12, 2026 20:31
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.

3 participants