Skip to content

Commit 46e3e71

Browse files
sauravzgkannanjgithub
authored andcommitted
feat(xds): Allow injecting bootstrap info into xDS Filter API for config parsing
Extend the xDS Filter API to support injecting bootstrap information into filters during configuration parsing. This allows filters to access context information (e.g., allowed gRPC services) from the resource loading layer during configuration validation and parsing. - Update `Filter.Provider.parseFilterConfig` and `parseFilterConfigOverride` to accept a `FilterContext` parameter. - Introduce `BootstrapInfoGrpcServiceContextProvider` to encapsulate bootstrap info for context resolution. - Update `XdsListenerResource` and `XdsRouteConfigureResource` to construct and pass `FilterContext` during configuration parsing. - Update sub-filters (`FaultFilter`, `RbacFilter`, `GcpAuthenticationFilter`, `RouterFilter`) to match the updated `FilterContext` signature. Known Gaps & Limitations: 1. **MetricHolder**: Propagation of `MetricHolder` is not supported with this approach currently and is planned for support in a later phase. 2. **NameResolverRegistry**: Propagation is deferred for consistency. While it could be passed from `XdsNameResolver` on the client side, there is no equivalent mechanism on the server side. To ensure consistent behavior, `DefaultRegistry` is used when validating schemes and creating channels.
1 parent cdb40e2 commit 46e3e71

15 files changed

Lines changed: 368 additions & 61 deletions
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2026 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.xds;
18+
19+
import io.grpc.NameResolverRegistry;
20+
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
21+
import io.grpc.xds.client.Bootstrapper.ServerInfo;
22+
import io.grpc.xds.internal.grpcservice.AllowedGrpcService;
23+
import io.grpc.xds.internal.grpcservice.AllowedGrpcServices;
24+
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext;
25+
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider;
26+
import java.net.URI;
27+
import java.net.URISyntaxException;
28+
import java.util.Optional;
29+
30+
/**
31+
* Concrete implementation of {@link GrpcServiceXdsContextProvider} that uses
32+
* {@link BootstrapInfo} data to resolve context.
33+
*/
34+
final class BootstrapInfoGrpcServiceContextProvider
35+
implements GrpcServiceXdsContextProvider {
36+
37+
private final boolean isTrustedControlPlane;
38+
private final AllowedGrpcServices allowedGrpcServices;
39+
private final NameResolverRegistry nameResolverRegistry;
40+
41+
BootstrapInfoGrpcServiceContextProvider(BootstrapInfo bootstrapInfo, ServerInfo serverInfo) {
42+
this.isTrustedControlPlane = serverInfo.isTrustedXdsServer();
43+
this.allowedGrpcServices = bootstrapInfo.allowedGrpcServices()
44+
.filter(AllowedGrpcServices.class::isInstance)
45+
.map(AllowedGrpcServices.class::cast)
46+
.orElse(AllowedGrpcServices.empty());
47+
this.nameResolverRegistry = NameResolverRegistry.getDefaultRegistry();
48+
}
49+
50+
@Override
51+
public GrpcServiceXdsContext getContextForTarget(String targetUri) {
52+
Optional<AllowedGrpcService> validAllowedGrpcService =
53+
Optional.ofNullable(allowedGrpcServices.services().get(targetUri));
54+
55+
boolean isTargetUriSchemeSupported = false;
56+
try {
57+
URI uri = new URI(targetUri);
58+
String scheme = uri.getScheme();
59+
if (scheme != null) {
60+
isTargetUriSchemeSupported =
61+
nameResolverRegistry.getProviderForScheme(scheme) != null;
62+
}
63+
} catch (URISyntaxException e) {
64+
// Fallback or ignore if not a valid URI
65+
}
66+
67+
return GrpcServiceXdsContext.create(
68+
isTrustedControlPlane,
69+
validAllowedGrpcService,
70+
isTargetUriSchemeSupported
71+
);
72+
}
73+
}

xds/src/main/java/io/grpc/xds/FaultFilter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ public FaultFilter newInstance(String name, GrpcServiceXdsContextProvider grpcSe
105105
}
106106

107107
@Override
108-
public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
108+
public ConfigOrError<FaultConfig> parseFilterConfig(
109+
Message rawProtoMessage, FilterContext context) {
109110
HTTPFault httpFaultProto;
110111
if (!(rawProtoMessage instanceof Any)) {
111112
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -120,8 +121,9 @@ public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
120121
}
121122

122123
@Override
123-
public ConfigOrError<FaultConfig> parseFilterConfigOverride(Message rawProtoMessage) {
124-
return parseFilterConfig(rawProtoMessage);
124+
public ConfigOrError<FaultConfig> parseFilterConfigOverride(
125+
Message rawProtoMessage, FilterContext context) {
126+
return parseFilterConfig(rawProtoMessage, context);
125127
}
126128

127129
private static ConfigOrError<FaultConfig> parseHttpFault(HTTPFault httpFault) {

xds/src/main/java/io/grpc/xds/Filter.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package io.grpc.xds;
1818

19+
1920
import com.google.common.base.MoreObjects;
2021
import com.google.protobuf.Message;
2122
import io.grpc.ClientInterceptor;
@@ -94,13 +95,15 @@ default boolean isServerFilter() {
9495
* Parses the top-level filter config from raw proto message. The message may be either a {@link
9596
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
9697
*/
97-
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
98+
ConfigOrError<? extends FilterConfig> parseFilterConfig(
99+
Message rawProtoMessage, FilterContext context);
98100

99101
/**
100102
* Parses the per-filter override filter config from raw proto message. The message may be
101103
* either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
102104
*/
103-
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
105+
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
106+
Message rawProtoMessage, FilterContext context);
104107
}
105108

106109
/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */
@@ -126,6 +129,25 @@ default ServerInterceptor buildServerInterceptor(
126129
@Override
127130
default void close() {}
128131

132+
/** Context carrying dynamic metadata for a filter. */
133+
@com.google.auto.value.AutoValue
134+
abstract class FilterContext {
135+
public abstract GrpcServiceXdsContextProvider grpcServiceContextProvider();
136+
137+
public static Builder builder() {
138+
return new AutoValue_Filter_FilterContext.Builder();
139+
}
140+
141+
142+
@com.google.auto.value.AutoValue.Builder
143+
public abstract static class Builder {
144+
public abstract Builder grpcServiceContextProvider(
145+
GrpcServiceXdsContextProvider provider);
146+
147+
public abstract FilterContext build();
148+
}
149+
}
150+
129151
/** Filter config with instance name. */
130152
final class NamedFilterConfig {
131153
// filter instance name

xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public GcpAuthenticationFilter newInstance(String name, GrpcServiceXdsContextPro
8787
}
8888

8989
@Override
90-
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProtoMessage) {
90+
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(
91+
Message rawProtoMessage, FilterContext context) {
9192
GcpAuthnFilterConfig gcpAuthnProto;
9293
if (!(rawProtoMessage instanceof Any)) {
9394
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -122,8 +123,8 @@ public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProto
122123

123124
@Override
124125
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfigOverride(
125-
Message rawProtoMessage) {
126-
return parseFilterConfig(rawProtoMessage);
126+
Message rawProtoMessage, FilterContext context) {
127+
return parseFilterConfig(rawProtoMessage, context);
127128
}
128129
}
129130

xds/src/main/java/io/grpc/xds/RbacFilter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public RbacFilter newInstance(String name, GrpcServiceXdsContextProvider grpcSer
9595
}
9696

9797
@Override
98-
public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
98+
public ConfigOrError<RbacConfig> parseFilterConfig(
99+
Message rawProtoMessage, FilterContext context) {
99100
RBAC rbacProto;
100101
if (!(rawProtoMessage instanceof Any)) {
101102
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -110,7 +111,8 @@ public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
110111
}
111112

112113
@Override
113-
public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
114+
public ConfigOrError<RbacConfig> parseFilterConfigOverride(
115+
Message rawProtoMessage, FilterContext context) {
114116
RBACPerRoute rbacPerRoute;
115117
if (!(rawProtoMessage instanceof Any)) {
116118
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());

xds/src/main/java/io/grpc/xds/RouterFilter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,14 @@ public RouterFilter newInstance(String name, GrpcServiceXdsContextProvider grpcS
6262
}
6363

6464
@Override
65-
public ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage) {
65+
public ConfigOrError<? extends FilterConfig> parseFilterConfig(
66+
Message rawProtoMessage, FilterContext context) {
6667
return ConfigOrError.fromConfig(ROUTER_CONFIG);
6768
}
6869

6970
@Override
7071
public ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
71-
Message rawProtoMessage) {
72+
Message rawProtoMessage, FilterContext context) {
7273
return ConfigOrError.fromError("Router Filter should not have override config");
7374
}
7475
}

xds/src/main/java/io/grpc/xds/XdsListenerResource.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
527527
"HttpConnectionManager contains duplicate HttpFilter: " + filterName);
528528
}
529529
StructOrError<Filter.FilterConfig> filterConfig =
530-
parseHttpFilter(httpFilter, filterRegistry, isForClient);
530+
parseHttpFilter(httpFilter, filterRegistry, isForClient, args);
531531
if ((i == proto.getHttpFiltersCount() - 1)
532532
&& (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) {
533533
throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: "
@@ -581,7 +581,8 @@ private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) {
581581
@Nullable // Returns null if the filter is optional but not supported.
582582
static StructOrError<Filter.FilterConfig> parseHttpFilter(
583583
io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
584-
httpFilter, FilterRegistry filterRegistry, boolean isForClient) {
584+
httpFilter, FilterRegistry filterRegistry, boolean isForClient,
585+
XdsResourceType.Args args) {
585586
String filterName = httpFilter.getName();
586587
boolean isOptional = httpFilter.getIsOptional();
587588
if (!httpFilter.hasTypedConfig()) {
@@ -616,7 +617,14 @@ static StructOrError<Filter.FilterConfig> parseHttpFilter(
616617
"HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + (
617618
isForClient ? "client" : "server"));
618619
}
619-
ConfigOrError<? extends FilterConfig> filterConfig = provider.parseFilterConfig(rawConfig);
620+
621+
BootstrapInfoGrpcServiceContextProvider contextProvider =
622+
new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo());
623+
Filter.FilterContext filterContext = Filter.FilterContext.builder()
624+
.grpcServiceContextProvider(contextProvider)
625+
.build();
626+
ConfigOrError<? extends FilterConfig> filterConfig =
627+
provider.parseFilterConfig(rawConfig, filterContext);
620628
if (filterConfig.errorDetail != null) {
621629
return StructOrError.fromError(
622630
"Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);

xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private static StructOrError<VirtualHost> parseVirtualHost(
198198
routes.add(route.getStruct());
199199
}
200200
StructOrError<Map<String, Filter.FilterConfig>> overrideConfigs =
201-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
201+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
202202
if (overrideConfigs.getErrorDetail() != null) {
203203
return StructOrError.fromError(
204204
"VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: "
@@ -210,7 +210,13 @@ private static StructOrError<VirtualHost> parseVirtualHost(
210210

211211
@VisibleForTesting
212212
static StructOrError<Map<String, FilterConfig>> parseOverrideFilterConfigs(
213-
Map<String, Any> rawFilterConfigMap, FilterRegistry filterRegistry) {
213+
Map<String, Any> rawFilterConfigMap, FilterRegistry filterRegistry,
214+
XdsResourceType.Args args) {
215+
BootstrapInfoGrpcServiceContextProvider grpcServiceContextProvider =
216+
new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo());
217+
Filter.FilterContext context = Filter.FilterContext.builder()
218+
.grpcServiceContextProvider(grpcServiceContextProvider)
219+
.build();
214220
Map<String, FilterConfig> overrideConfigs = new HashMap<>();
215221
for (String name : rawFilterConfigMap.keySet()) {
216222
Any anyConfig = rawFilterConfigMap.get(name);
@@ -254,7 +260,7 @@ static StructOrError<Map<String, FilterConfig>> parseOverrideFilterConfigs(
254260
"HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported");
255261
}
256262
ConfigOrError<? extends Filter.FilterConfig> filterConfig =
257-
provider.parseFilterConfigOverride(rawConfig);
263+
provider.parseFilterConfigOverride(rawConfig, context);
258264
if (filterConfig.errorDetail != null) {
259265
return StructOrError.fromError(
260266
"Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail);
@@ -281,7 +287,7 @@ static StructOrError<Route> parseRoute(
281287
}
282288

283289
StructOrError<Map<String, FilterConfig>> overrideConfigsOrError =
284-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
290+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
285291
if (overrideConfigsOrError.getErrorDetail() != null) {
286292
return StructOrError.fromError(
287293
"Route [" + proto.getName() + "] contains invalid HttpFilter config: "
@@ -490,7 +496,7 @@ static StructOrError<RouteAction> parseRouteAction(
490496
for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight
491497
: clusterWeights) {
492498
StructOrError<ClusterWeight> clusterWeightOrError =
493-
parseClusterWeight(clusterWeight, filterRegistry);
499+
parseClusterWeight(clusterWeight, filterRegistry, args);
494500
if (clusterWeightOrError.getErrorDetail() != null) {
495501
return StructOrError.fromError("RouteAction contains invalid ClusterWeight: "
496502
+ clusterWeightOrError.getErrorDetail());
@@ -599,9 +605,9 @@ private static StructOrError<VirtualHost.Route.RouteAction.RetryPolicy> parseRet
599605
@VisibleForTesting
600606
static StructOrError<VirtualHost.Route.RouteAction.ClusterWeight> parseClusterWeight(
601607
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto,
602-
FilterRegistry filterRegistry) {
608+
FilterRegistry filterRegistry, XdsResourceType.Args args) {
603609
StructOrError<Map<String, Filter.FilterConfig>> overrideConfigs =
604-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
610+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
605611
if (overrideConfigs.getErrorDetail() != null) {
606612
return StructOrError.fromError(
607613
"ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: "

0 commit comments

Comments
 (0)