Skip to content

Commit 842636f

Browse files
authored
xds: Add configuration objects for ExtAuthz, GrpcService and Bootstrap changes for GrpcService (#12492)
This commit introduces configuration objects for the external authorization (ExtAuthz) filter and the gRPC service and corresponding translations from XDS proto and Bootstrap. These classes provide a structured, immutable representation of the subset of the configuration defined in the xDS protobuf messages. This PR should mostly now (hopefully ) be compliant with grpc/proposal#510 but without - CallCredentials (since I don't see A97) being implemented yet and would prefer to do it in a followup , we return empty optional) - TlsCredentials( since it's non trivial to construct a TLS credentials object, we throw an exception) - LocalCredentials(Java does't support these, we throw an exception) The main new classes are: - `ExtAuthzConfig`: Represents the configuration for the `ExtAuthz` filter, including settings for the gRPC service, header mutation rules, and other filter behaviors. - `GrpcServiceConfig`: Represents the configuration for a gRPC service, including the target URI, credentials, and other settings. - `HeaderMutationRulesConfig`: Represents the configuration for header mutation rules. - `ChannelCredsConfig` and friends: To allow comparison between credential configuration , to allow caching based on creds which'll be needed in followup PRs for authz and proc. The relevant sections of the spec are - GrpcService: grpc/proposal#510 - ExtAuthz: https://github.com/grpc/proposal/pull/481/files#diff-6bb76a24aa142cc33db9218509688f01b30c8885d2fd8849f164244e68cd54eaR106-R190 This commit also includes parsers to create these configuration objects from the corresponding protobuf messages, as well as unit tests for the new classes.
1 parent 15788b7 commit 842636f

26 files changed

+2814
-10
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2025 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 com.google.common.collect.ImmutableList;
20+
import io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz;
21+
import io.grpc.internal.GrpcUtil;
22+
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
23+
import io.grpc.xds.client.Bootstrapper.ServerInfo;
24+
import io.grpc.xds.internal.MatcherParser;
25+
import io.grpc.xds.internal.extauthz.ExtAuthzConfig;
26+
import io.grpc.xds.internal.extauthz.ExtAuthzParseException;
27+
import io.grpc.xds.internal.grpcservice.GrpcServiceConfig;
28+
import io.grpc.xds.internal.grpcservice.GrpcServiceParseException;
29+
import io.grpc.xds.internal.headermutations.HeaderMutationRulesParseException;
30+
import io.grpc.xds.internal.headermutations.HeaderMutationRulesParser;
31+
32+
33+
/**
34+
* Parser for {@link io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz}.
35+
*/
36+
final class ExtAuthzConfigParser {
37+
38+
private ExtAuthzConfigParser() {}
39+
40+
/**
41+
* Parses the {@link io.envoyproxy.envoy.extensions.filters.http.ext_authz.v3.ExtAuthz} proto to
42+
* create an {@link ExtAuthzConfig} instance.
43+
*
44+
* @param extAuthzProto The ext_authz proto to parse.
45+
* @return An {@link ExtAuthzConfig} instance.
46+
* @throws ExtAuthzParseException if the proto is invalid or contains unsupported features.
47+
*/
48+
public static ExtAuthzConfig parse(
49+
ExtAuthz extAuthzProto, BootstrapInfo bootstrapInfo, ServerInfo serverInfo)
50+
throws ExtAuthzParseException {
51+
if (!extAuthzProto.hasGrpcService()) {
52+
throw new ExtAuthzParseException(
53+
"unsupported ExtAuthz service type: only grpc_service is supported");
54+
}
55+
GrpcServiceConfig grpcServiceConfig;
56+
try {
57+
grpcServiceConfig =
58+
GrpcServiceConfigParser.parse(extAuthzProto.getGrpcService(), bootstrapInfo, serverInfo);
59+
} catch (GrpcServiceParseException e) {
60+
throw new ExtAuthzParseException("Failed to parse GrpcService config: " + e.getMessage(), e);
61+
}
62+
ExtAuthzConfig.Builder builder = ExtAuthzConfig.builder().grpcService(grpcServiceConfig)
63+
.failureModeAllow(extAuthzProto.getFailureModeAllow())
64+
.failureModeAllowHeaderAdd(extAuthzProto.getFailureModeAllowHeaderAdd())
65+
.includePeerCertificate(extAuthzProto.getIncludePeerCertificate())
66+
.denyAtDisable(extAuthzProto.getDenyAtDisable().getDefaultValue().getValue());
67+
68+
if (extAuthzProto.hasFilterEnabled()) {
69+
try {
70+
builder.filterEnabled(
71+
MatcherParser.parseFractionMatcher(extAuthzProto.getFilterEnabled().getDefaultValue()));
72+
} catch (IllegalArgumentException e) {
73+
throw new ExtAuthzParseException(e.getMessage());
74+
}
75+
}
76+
77+
if (extAuthzProto.hasStatusOnError()) {
78+
builder.statusOnError(
79+
GrpcUtil.httpStatusToGrpcStatus(extAuthzProto.getStatusOnError().getCodeValue()));
80+
}
81+
82+
if (extAuthzProto.hasAllowedHeaders()) {
83+
builder.allowedHeaders(extAuthzProto.getAllowedHeaders().getPatternsList().stream()
84+
.map(MatcherParser::parseStringMatcher).collect(ImmutableList.toImmutableList()));
85+
}
86+
87+
if (extAuthzProto.hasDisallowedHeaders()) {
88+
builder.disallowedHeaders(extAuthzProto.getDisallowedHeaders().getPatternsList().stream()
89+
.map(MatcherParser::parseStringMatcher).collect(ImmutableList.toImmutableList()));
90+
}
91+
92+
if (extAuthzProto.hasDecoderHeaderMutationRules()) {
93+
try {
94+
builder.decoderHeaderMutationRules(
95+
HeaderMutationRulesParser.parse(extAuthzProto.getDecoderHeaderMutationRules()));
96+
} catch (HeaderMutationRulesParseException e) {
97+
throw new ExtAuthzParseException(e.getMessage(), e);
98+
}
99+
}
100+
101+
return builder.build();
102+
}
103+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025 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 com.google.auto.value.AutoValue;
20+
import io.grpc.Internal;
21+
import io.grpc.xds.client.AllowedGrpcServices;
22+
23+
/**
24+
* Custom configuration for gRPC xDS bootstrap implementation.
25+
*/
26+
@Internal
27+
@AutoValue
28+
public abstract class GrpcBootstrapImplConfig {
29+
public abstract AllowedGrpcServices allowedGrpcServices();
30+
31+
public static GrpcBootstrapImplConfig create(AllowedGrpcServices services) {
32+
return new AutoValue_GrpcBootstrapImplConfig(services);
33+
}
34+
}

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

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@
1919
import com.google.common.annotations.VisibleForTesting;
2020
import com.google.common.collect.ImmutableMap;
2121
import com.google.errorprone.annotations.concurrent.GuardedBy;
22+
import io.grpc.CallCredentials;
2223
import io.grpc.ChannelCredentials;
2324
import io.grpc.internal.JsonUtil;
25+
import io.grpc.xds.client.AllowedGrpcServices;
26+
import io.grpc.xds.client.AllowedGrpcServices.AllowedGrpcService;
2427
import io.grpc.xds.client.BootstrapperImpl;
28+
import io.grpc.xds.client.ConfiguredChannelCredentials;
29+
import io.grpc.xds.client.ConfiguredChannelCredentials.ChannelCredsConfig;
2530
import io.grpc.xds.client.XdsInitializationException;
2631
import io.grpc.xds.client.XdsLogger;
2732
import java.io.IOException;
2833
import java.util.List;
2934
import java.util.Map;
35+
import java.util.Optional;
3036
import javax.annotation.Nullable;
3137

3238
class GrpcBootstrapperImpl extends BootstrapperImpl {
@@ -97,7 +103,8 @@ protected String getJsonContent() throws XdsInitializationException, IOException
97103
@Override
98104
protected Object getImplSpecificConfig(Map<String, ?> serverConfig, String serverUri)
99105
throws XdsInitializationException {
100-
return getChannelCredentials(serverConfig, serverUri);
106+
ConfiguredChannelCredentials configuredChannel = getChannelCredentials(serverConfig, serverUri);
107+
return configuredChannel != null ? configuredChannel.channelCredentials() : null;
101108
}
102109

103110
@GuardedBy("GrpcBootstrapperImpl.class")
@@ -120,26 +127,26 @@ static synchronized BootstrapInfo defaultBootstrap() throws XdsInitializationExc
120127
return defaultBootstrap;
121128
}
122129

123-
private static ChannelCredentials getChannelCredentials(Map<String, ?> serverConfig,
124-
String serverUri)
130+
private static ConfiguredChannelCredentials getChannelCredentials(Map<String, ?> serverConfig,
131+
String serverUri)
125132
throws XdsInitializationException {
126133
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
127134
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
128135
throw new XdsInitializationException(
129136
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
130137
}
131-
ChannelCredentials channelCredentials =
138+
ConfiguredChannelCredentials credentials =
132139
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
133-
if (channelCredentials == null) {
140+
if (credentials == null) {
134141
throw new XdsInitializationException(
135142
"Server " + serverUri + ": no supported channel credentials found");
136143
}
137-
return channelCredentials;
144+
return credentials;
138145
}
139146

140147
@Nullable
141-
private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList,
142-
String serverUri)
148+
private static ConfiguredChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList,
149+
String serverUri)
143150
throws XdsInitializationException {
144151
for (Map<String, ?> channelCreds : jsonList) {
145152
String type = JsonUtil.getString(channelCreds, "type");
@@ -155,9 +162,95 @@ private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> j
155162
config = ImmutableMap.of();
156163
}
157164

158-
return provider.newChannelCredentials(config);
165+
ChannelCredentials creds = provider.newChannelCredentials(config);
166+
if (creds == null) {
167+
return null;
168+
}
169+
return ConfiguredChannelCredentials.create(creds, new JsonChannelCredsConfig(type, config));
159170
}
160171
}
161172
return null;
162173
}
174+
175+
@Override
176+
protected Optional<Object> parseImplSpecificObject(
177+
@Nullable Map<String, ?> rawAllowedGrpcServices)
178+
throws XdsInitializationException {
179+
if (rawAllowedGrpcServices == null || rawAllowedGrpcServices.isEmpty()) {
180+
return Optional.of(GrpcBootstrapImplConfig.create(AllowedGrpcServices.empty()));
181+
}
182+
183+
ImmutableMap.Builder<String, AllowedGrpcService> builder =
184+
ImmutableMap.builder();
185+
for (String targetUri : rawAllowedGrpcServices.keySet()) {
186+
Map<String, ?> serviceConfig = JsonUtil.getObject(rawAllowedGrpcServices, targetUri);
187+
if (serviceConfig == null) {
188+
throw new XdsInitializationException(
189+
"Invalid allowed_grpc_services config for " + targetUri);
190+
}
191+
ConfiguredChannelCredentials configuredChannel =
192+
getChannelCredentials(serviceConfig, targetUri);
193+
194+
Optional<CallCredentials> callCredentials = Optional.empty();
195+
List<?> rawCallCredsList = JsonUtil.getList(serviceConfig, "call_creds");
196+
if (rawCallCredsList != null && !rawCallCredsList.isEmpty()) {
197+
callCredentials =
198+
parseCallCredentials(JsonUtil.checkObjectList(rawCallCredsList), targetUri);
199+
}
200+
201+
AllowedGrpcService.Builder b = AllowedGrpcService.builder()
202+
.configuredChannelCredentials(configuredChannel);
203+
callCredentials.ifPresent(b::callCredentials);
204+
builder.put(targetUri, b.build());
205+
}
206+
GrpcBootstrapImplConfig customConfig =
207+
GrpcBootstrapImplConfig.create(AllowedGrpcServices.create(builder.build()));
208+
return Optional.of(customConfig);
209+
}
210+
211+
@SuppressWarnings("unused")
212+
private static Optional<CallCredentials> parseCallCredentials(List<Map<String, ?>> jsonList,
213+
String targetUri)
214+
throws XdsInitializationException {
215+
// TODO(sauravzg): Currently no xDS call credentials providers are implemented (no
216+
// XdsCallCredentialsRegistry).
217+
// As per A102/A97, we should just ignore unsupported call credentials types
218+
// without throwing an exception.
219+
return Optional.empty();
220+
}
221+
222+
private static final class JsonChannelCredsConfig implements ChannelCredsConfig {
223+
private final String type;
224+
private final Map<String, ?> config;
225+
226+
JsonChannelCredsConfig(String type, Map<String, ?> config) {
227+
this.type = type;
228+
this.config = config;
229+
}
230+
231+
@Override
232+
public String type() {
233+
return type;
234+
}
235+
236+
@Override
237+
public boolean equals(Object o) {
238+
if (this == o) {
239+
return true;
240+
}
241+
if (o == null || getClass() != o.getClass()) {
242+
return false;
243+
}
244+
JsonChannelCredsConfig that = (JsonChannelCredsConfig) o;
245+
return java.util.Objects.equals(type, that.type)
246+
&& java.util.Objects.equals(config, that.config);
247+
}
248+
249+
@Override
250+
public int hashCode() {
251+
return java.util.Objects.hash(type, config);
252+
}
253+
}
254+
163255
}
256+

0 commit comments

Comments
 (0)