Skip to content

Commit 77d497b

Browse files
committed
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 d59c705 commit 77d497b

15 files changed

+369
-61
lines changed
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
@@ -104,7 +104,8 @@ public FaultFilter newInstance(String name) {
104104
}
105105

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

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

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

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
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;
2223
import io.grpc.ServerInterceptor;
24+
import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider;
2325
import java.io.Closeable;
2426
import java.util.Objects;
2527
import java.util.concurrent.ScheduledExecutorService;
@@ -93,13 +95,15 @@ default boolean isServerFilter() {
9395
* Parses the top-level filter config from raw proto message. The message may be either a {@link
9496
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
9597
*/
96-
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
98+
ConfigOrError<? extends FilterConfig> parseFilterConfig(
99+
Message rawProtoMessage, FilterContext context);
97100

98101
/**
99102
* Parses the per-filter override filter config from raw proto message. The message may be
100103
* either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
101104
*/
102-
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
105+
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
106+
Message rawProtoMessage, FilterContext context);
103107
}
104108

105109
/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */
@@ -125,6 +129,25 @@ default ServerInterceptor buildServerInterceptor(
125129
@Override
126130
default void close() {}
127131

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+
128151
/** Filter config with instance name. */
129152
final class NamedFilterConfig {
130153
// 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
@@ -86,7 +86,8 @@ public GcpAuthenticationFilter newInstance(String name) {
8686
}
8787

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

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

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ public RbacFilter newInstance(String name) {
9494
}
9595

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

111112
@Override
112-
public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
113+
public ConfigOrError<RbacConfig> parseFilterConfigOverride(
114+
Message rawProtoMessage, FilterContext context) {
113115
RBACPerRoute rbacPerRoute;
114116
if (!(rawProtoMessage instanceof Any)) {
115117
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
@@ -61,13 +61,14 @@ public RouterFilter newInstance(String name) {
6161
}
6262

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

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

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)