Skip to content

Commit b18fc68

Browse files
fix(mcp): use sectionName to scope SecurityPolicy to MCP proxy rule only
SecurityPolicy and BackendTrafficPolicy were targeting the entire main HTTPRoute, causing JWT validation to run on .well-known OAuth discovery endpoints that must be publicly accessible per RFC 9728. This adds a name to the MCP proxy rule and uses sectionName on both policies to scope them to only that rule. Fixes #1992 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 98200a4 commit b18fc68

File tree

6 files changed

+13
-21
lines changed

6 files changed

+13
-21
lines changed

internal/controller/mcp_route.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ func (c *MCPRouteController) newMainHTTPRoute(dst *gwapiv1.HTTPRoute, mcpRoute *
202202
// This routes incoming MCP client requests to the MCP proxy in the ext proc.
203203
servingPath := ptr.Deref(mcpRoute.Spec.Path, defaultMCPPath)
204204
rules := []gwapiv1.HTTPRouteRule{{
205+
Name: ptr.To(gwapiv1.SectionName(internalapi.MCPProxyRuleName)),
205206
Matches: []gwapiv1.HTTPRouteMatch{
206207
{
207208
Path: &gwapiv1.HTTPPathMatch{

internal/controller/mcp_route_security_policy.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,16 @@ func (c *MCPRouteController) ensureSecurityPolicy(ctx context.Context, mcpRoute
166166
securityPolicySpec.ExtAuth = extAuth.DeepCopy()
167167
}
168168

169-
// The SecurityPolicy should only apply to the HTTPRoute MCP proxy rule.
170-
// However, since HTTPRouteRule name is experimental in Gateway API, and some vendors (e.g. GKE Gateway) do not
171-
// support it yet, we currently do not set the sectionName to avoid compatibility issues.
172-
// The jwt and API key auth filter will be removed from backend routes in the extension server.
173-
// TODO: use sectionName to target the MCP proxy rule only when the HTTPRouteRule name is in stable channel.
169+
// Target only the MCP proxy rule via sectionName so that auth filters are not applied
170+
// to the .well-known OAuth discovery endpoints which must be publicly accessible (RFC 9728).
174171
securityPolicySpec.TargetRefs = []gwapiv1.LocalPolicyTargetReferenceWithSectionName{
175172
{
176173
LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
177174
Group: "gateway.networking.k8s.io",
178175
Kind: "HTTPRoute",
179176
Name: gwapiv1.ObjectName(httpRouteName),
180177
},
178+
SectionName: ptr.To(gwapiv1.SectionName(internalapi.MCPProxyRuleName)),
181179
},
182180
}
183181

@@ -254,16 +252,15 @@ func (c *MCPRouteController) ensureOAuthProtectedResourceMetadataBTP(ctx context
254252

255253
ensureCORSHeaders(backendTrafficPolicy.Spec.ResponseOverride[0].Response.Header)
256254

257-
// Target the HTTPRoute MCP proxy rule only.
255+
// Target only the MCP proxy rule so the WWW-Authenticate response override applies to /mcp, not .well-known endpoints.
258256
backendTrafficPolicy.Spec.TargetRefs = []gwapiv1.LocalPolicyTargetReferenceWithSectionName{
259257
{
260258
LocalPolicyTargetReference: gwapiv1.LocalPolicyTargetReference{
261259
Group: "gateway.networking.k8s.io",
262260
Kind: "HTTPRoute",
263261
Name: gwapiv1.ObjectName(httpRouteName),
264262
},
265-
// TODO: this filter should be applied to the MCP proxy rule only, enable sectionName when supported in Envoy Gateway.
266-
// SectionName: ptr.To(gwapiv1a2.SectionName(mcpProxyRuleName)).
263+
SectionName: ptr.To(gwapiv1.SectionName(internalapi.MCPProxyRuleName)),
267264
},
268265
}
269266

internal/controller/mcp_route_security_policy_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -382,12 +382,7 @@ func TestMCPRouteController_syncMCPRouteSecurityPolicy(t *testing.T) {
382382
require.Nil(t, securityPolicy.Spec.ExtAuth)
383383
}
384384

385-
// The SecurityPolicy should only apply to the HTTPRoute MCP proxy rule.
386-
// However, since HTTPRouteRule name is experimental in Gateway API, and some vendors (e.g. GKE Gateway) do not
387-
// support it yet, we currently do not set the sectionName to avoid compatibility issues.
388-
// The authn filters will be removed from backend routes in the extension server.
389-
// TODO: use sectionName to target the MCP proxy rule only when the HTTPRouteRule name is in stable channel.
390-
require.Nil(t, securityPolicy.Spec.TargetRefs[0].SectionName)
385+
require.Equal(t, ptr.To(gwapiv1.SectionName(internalapi.MCPProxyRuleName)), securityPolicy.Spec.TargetRefs[0].SectionName)
391386

392387
} else {
393388
require.Error(t, secPolErr, "SecurityPolicy should not exist")

internal/controller/mcp_route_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,7 @@ func TestMCPRouteController_Reconcile(t *testing.T) {
102102
require.Equal(t, route.Spec.Headers, mainHTTPRoute.Spec.Rules[0].Matches[0].Headers)
103103
require.Len(t, mainHTTPRoute.Spec.Rules[0].BackendRefs, 1)
104104
require.Equal(t, gwapiv1.ObjectName("default-myroute-mcp-proxy"), mainHTTPRoute.Spec.Rules[0].BackendRefs[0].Name)
105-
// Since HTTPRouteRule name is experimental in Gateway API, and some vendors (e.g. GKE Gateway) do not
106-
// support it yet, we currently do not set the sectionName to avoid compatibility issues.
107-
// The jwt filter will be removed from backend routes in the extension server.
108-
// TODO: set the rule name and target the SecurityPolicy with jwt authn to the mcp-proxy rule only when the
109-
// HTTPRouteRule name is in stable channel.
110-
require.Nil(t, mainHTTPRoute.Spec.Rules[0].Name)
105+
require.Equal(t, ptr.To(gwapiv1.SectionName(internalapi.MCPProxyRuleName)), mainHTTPRoute.Spec.Rules[0].Name)
111106

112107
// Labels/annotations propagated.
113108
require.Equal(t, "v1", mainHTTPRoute.Labels["l1"])

internal/extensionserver/mcproute.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ func (s *Server) maybeUpdateMCPRoutes(routes []*routev3.RouteConfiguration) {
173173
continue
174174
}
175175
// Remove the authn filters from the well-known and backend routes.
176-
// TODO: remove this step once the SecurityPolicy can target the MCP proxy route rule only.
176+
// NOTE: with sectionName set on SecurityPolicy/BackendTrafficPolicy, EG should no longer
177+
// add auth filters to well-known routes. This remains as a safety net for edge cases.
177178
for _, filterName := range []string{filterNameJWTAuthn, filterNameAPIKeyAuth, filterNameExtAuth} {
178179
if _, ok := route.TypedPerFilterConfig[filterName]; ok {
179180
s.log.Info("removing authn filter from well-known and backend routes", "route", route.Name, "filter", filterName)

internal/internalapi/internalapi.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ const (
4343
MCPPerBackendRefHTTPRoutePrefix = MCPGeneratedResourceCommonPrefix + "br-"
4444
// MCPPerBackendHTTPRouteFilterPrefix is the prefix for the HTTP route filter names for per-backend resources.
4545
MCPPerBackendHTTPRouteFilterPrefix = MCPGeneratedResourceCommonPrefix + "brf-"
46+
// MCPProxyRuleName is the name of the MCP proxy HTTPRouteRule (rule/0).
47+
// Used as sectionName to scope SecurityPolicy and BackendTrafficPolicy to only the /mcp endpoint.
48+
MCPProxyRuleName = "mcp-proxy"
4649

4750
// MCPMetadataHeaderPrefix is the prefix for special headers used to pass metadata in the filter metadata.
4851
// These headers are added internally to the requests to the upstream servers so they can be populated in the filter

0 commit comments

Comments
 (0)