• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20232

02 Apr 2026 11:39AM UTC coverage: 88.784% (+0.03%) from 88.755%
#20232

push

github

web-flow
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
https://github.com/grpc/proposal/pull/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: https://github.com/grpc/proposal/pull/510
- ExtAuthz:
https://github.com/grpc/proposal/pull/481/files#diff-6bb76a24ad2fd8849f164244e68cd54eaR106-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.

35968 of 40512 relevant lines covered (88.78%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.7
/../xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java
1
/*
2
 * Copyright 2021 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.client;
18

19
import com.google.common.annotations.VisibleForTesting;
20
import com.google.common.collect.ImmutableList;
21
import com.google.common.collect.ImmutableMap;
22
import io.grpc.Internal;
23
import io.grpc.InternalLogId;
24
import io.grpc.internal.GrpcUtil;
25
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
26
import io.grpc.internal.JsonParser;
27
import io.grpc.internal.JsonUtil;
28
import io.grpc.xds.client.EnvoyProtoData.Node;
29
import io.grpc.xds.client.XdsLogger.XdsLogLevel;
30
import java.io.IOException;
31
import java.nio.charset.StandardCharsets;
32
import java.nio.file.Files;
33
import java.nio.file.Paths;
34
import java.util.HashMap;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Optional;
38
import javax.annotation.Nullable;
39

40
/**
41
 * A {@link Bootstrapper} implementation that reads xDS configurations from local file system.
42
 */
43
@Internal
44
public abstract class BootstrapperImpl extends Bootstrapper {
45

46
  public static final String GRPC_EXPERIMENTAL_XDS_FALLBACK =
47
      "GRPC_EXPERIMENTAL_XDS_FALLBACK";
48
  public static final String GRPC_EXPERIMENTAL_XDS_DATA_ERROR_HANDLING =
49
      "GRPC_EXPERIMENTAL_XDS_DATA_ERROR_HANDLING";
50

51
  // Client features.
52
  @VisibleForTesting
53
  public static final String CLIENT_FEATURE_DISABLE_OVERPROVISIONING =
54
      "envoy.lb.does_not_support_overprovisioning";
55
  @VisibleForTesting
56
  public static final String CLIENT_FEATURE_RESOURCE_IN_SOTW = "xds.config.resource-in-sotw";
57

58
  // Server features.
59
  private static final String SERVER_FEATURE_IGNORE_RESOURCE_DELETION = "ignore_resource_deletion";
60
  private static final String SERVER_FEATURE_TRUSTED_XDS_SERVER = "trusted_xds_server";
61
  private static final String
62
      SERVER_FEATURE_RESOURCE_TIMER_IS_TRANSIENT_ERROR = "resource_timer_is_transient_error";
63
  private static final String SERVER_FEATURE_FAIL_ON_DATA_ERRORS = "fail_on_data_errors";
64

65
  @VisibleForTesting
66
  static boolean enableXdsFallback = GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_FALLBACK, true);
1✔
67

68
  @VisibleForTesting
69
  public static boolean xdsDataErrorHandlingEnabled
1✔
70
      = GrpcUtil.getFlag(GRPC_EXPERIMENTAL_XDS_DATA_ERROR_HANDLING, false);
1✔
71

72
  protected final XdsLogger logger;
73

74
  protected FileReader reader = LocalFileReader.INSTANCE;
1✔
75

76
  protected BootstrapperImpl() {
1✔
77
    logger = XdsLogger.withLogId(InternalLogId.allocate("bootstrapper", null));
1✔
78
  }
1✔
79

80
  protected abstract String getJsonContent() throws IOException, XdsInitializationException;
81

82
  protected abstract Object getImplSpecificConfig(Map<String, ?> serverConfig, String serverUri)
83
      throws XdsInitializationException;
84

85

86
  /**
87
   * Reads and parses bootstrap config. The config is expected to be in JSON format.
88
   */
89
  @SuppressWarnings("unchecked")
90
  @Override
91
  public BootstrapInfo bootstrap() throws XdsInitializationException {
92
    String jsonContent;
93
    try {
94
      jsonContent = getJsonContent();
1✔
95
    } catch (IOException e) {
×
96
      throw new XdsInitializationException("Fail to read bootstrap file", e);
×
97
    }
1✔
98

99
    Map<String, ?> rawBootstrap;
100
    try {
101
      rawBootstrap = (Map<String, ?>) JsonParser.parse(jsonContent);
1✔
102
    } catch (IOException e) {
×
103
      throw new XdsInitializationException("Failed to parse JSON", e);
×
104
    }
1✔
105

106
    logger.log(XdsLogLevel.DEBUG, "Bootstrap configuration:\n{0}", rawBootstrap);
1✔
107
    return bootstrap(rawBootstrap);
1✔
108
  }
109

110
  @Override
111
  public BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
112
    return bootstrapBuilder(rawData).build();
1✔
113
  }
114

115
  protected BootstrapInfo.Builder bootstrapBuilder(Map<String, ?> rawData)
116
      throws XdsInitializationException {
117
    BootstrapInfo.Builder builder = BootstrapInfo.builder();
1✔
118

119
    List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
1✔
120
    if (rawServerConfigs == null) {
1✔
121
      throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
1✔
122
    }
123
    List<ServerInfo> servers = parseServerInfos(rawServerConfigs, logger);
1✔
124
    if (servers.size() > 1 && !enableXdsFallback) {
1✔
125
      servers = ImmutableList.of(servers.get(0));
×
126
    }
127
    builder.servers(servers);
1✔
128

129
    Node.Builder nodeBuilder = Node.newBuilder();
1✔
130
    Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
1✔
131
    if (rawNode != null) {
1✔
132
      String id = JsonUtil.getString(rawNode, "id");
1✔
133
      if (id != null) {
1✔
134
        logger.log(XdsLogLevel.INFO, "Node id: {0}", id);
1✔
135
        nodeBuilder.setId(id);
1✔
136
      }
137
      String cluster = JsonUtil.getString(rawNode, "cluster");
1✔
138
      if (cluster != null) {
1✔
139
        logger.log(XdsLogLevel.INFO, "Node cluster: {0}", cluster);
1✔
140
        nodeBuilder.setCluster(cluster);
1✔
141
      }
142
      Map<String, ?> metadata = JsonUtil.getObject(rawNode, "metadata");
1✔
143
      if (metadata != null) {
1✔
144
        nodeBuilder.setMetadata(metadata);
1✔
145
      }
146
      Map<String, ?> rawLocality = JsonUtil.getObject(rawNode, "locality");
1✔
147
      if (rawLocality != null) {
1✔
148
        String region = "";
1✔
149
        String zone = "";
1✔
150
        String subZone = "";
1✔
151
        if (rawLocality.containsKey("region")) {
1✔
152
          region = JsonUtil.getString(rawLocality, "region");
1✔
153
        }
154
        if (rawLocality.containsKey("zone")) {
1✔
155
          zone = JsonUtil.getString(rawLocality, "zone");
1✔
156
        }
157
        if (rawLocality.containsKey("sub_zone")) {
1✔
158
          subZone = JsonUtil.getString(rawLocality, "sub_zone");
1✔
159
        }
160
        logger.log(XdsLogLevel.INFO, "Locality region: {0}, zone: {1}, subZone: {2}",
1✔
161
            region, zone, subZone);
162
        Locality locality = Locality.create(region, zone, subZone);
1✔
163
        nodeBuilder.setLocality(locality);
1✔
164
      }
165
    }
166
    GrpcBuildVersion buildVersion = GrpcUtil.getGrpcBuildVersion();
1✔
167
    logger.log(XdsLogLevel.INFO, "Build version: {0}", buildVersion);
1✔
168
    nodeBuilder.setBuildVersion(buildVersion.toString());
1✔
169
    nodeBuilder.setUserAgentName(buildVersion.getUserAgent());
1✔
170
    nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion());
1✔
171
    nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING);
1✔
172
    nodeBuilder.addClientFeatures(CLIENT_FEATURE_RESOURCE_IN_SOTW);
1✔
173
    builder.node(nodeBuilder.build());
1✔
174

175
    Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
1✔
176
    if (certProvidersBlob != null) {
1✔
177
      logger.log(XdsLogLevel.INFO, "Configured with {0} cert providers", certProvidersBlob.size());
1✔
178
      Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
1✔
179
      for (String name : certProvidersBlob.keySet()) {
1✔
180
        Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
1✔
181
        String pluginName =
1✔
182
            checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
1✔
183
        logger.log(XdsLogLevel.INFO, "cert provider: {0}, plugin name: {1}", name, pluginName);
1✔
184
        Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
1✔
185
        CertificateProviderInfo certificateProviderInfo =
1✔
186
            CertificateProviderInfo.create(pluginName, config);
1✔
187
        certProviders.put(name, certificateProviderInfo);
1✔
188
      }
1✔
189
      builder.certProviders(certProviders);
1✔
190
    }
191

192
    String serverResourceId =
1✔
193
        JsonUtil.getString(rawData, "server_listener_resource_name_template");
1✔
194
    logger.log(
1✔
195
        XdsLogLevel.INFO, "server_listener_resource_name_template: {0}", serverResourceId);
196
    builder.serverListenerResourceNameTemplate(serverResourceId);
1✔
197

198
    String clientDefaultListener =
1✔
199
        JsonUtil.getString(rawData, "client_default_listener_resource_name_template");
1✔
200
    logger.log(
1✔
201
        XdsLogLevel.INFO, "client_default_listener_resource_name_template: {0}",
202
        clientDefaultListener);
203
    if (clientDefaultListener != null) {
1✔
204
      builder.clientDefaultListenerResourceNameTemplate(clientDefaultListener);
1✔
205
    }
206

207
    Map<String, ?> rawAuthoritiesMap =
1✔
208
        JsonUtil.getObject(rawData, "authorities");
1✔
209
    ImmutableMap.Builder<String, AuthorityInfo> authorityInfoMapBuilder = ImmutableMap.builder();
1✔
210
    if (rawAuthoritiesMap != null) {
1✔
211
      logger.log(
1✔
212
          XdsLogLevel.INFO, "Configured with {0} xDS server authorities", rawAuthoritiesMap.size());
1✔
213
      for (String authorityName : rawAuthoritiesMap.keySet()) {
1✔
214
        logger.log(XdsLogLevel.INFO, "xDS server authority: {0}", authorityName);
1✔
215
        Map<String, ?> rawAuthority = JsonUtil.getObject(rawAuthoritiesMap, authorityName);
1✔
216
        String clientListnerTemplate =
1✔
217
            JsonUtil.getString(rawAuthority, "client_listener_resource_name_template");
1✔
218
        logger.log(
1✔
219
            XdsLogLevel.INFO, "client_listener_resource_name_template: {0}", clientListnerTemplate);
220
        String prefix = XDSTP_SCHEME + "//" + authorityName + "/";
1✔
221
        if (clientListnerTemplate == null) {
1✔
222
          clientListnerTemplate = prefix + "envoy.config.listener.v3.Listener/%s";
1✔
223
        } else if (!clientListnerTemplate.startsWith(prefix)) {
1✔
224
          throw new XdsInitializationException(
1✔
225
              "client_listener_resource_name_template: '" + clientListnerTemplate
226
                  + "' does not start with " + prefix);
227
        }
228
        List<?> rawAuthorityServers = JsonUtil.getList(rawAuthority, "xds_servers");
1✔
229
        List<ServerInfo> authorityServers;
230
        if (rawAuthorityServers == null || rawAuthorityServers.isEmpty()) {
1✔
231
          authorityServers = servers;
1✔
232
        } else {
233
          if (rawAuthorityServers.size() > 1 && !enableXdsFallback) {
1✔
234
            rawAuthorityServers = ImmutableList.of(rawAuthorityServers.get(0));
×
235
          }
236
          authorityServers = parseServerInfos(rawAuthorityServers, logger);
1✔
237
        }
238
        authorityInfoMapBuilder.put(
1✔
239
            authorityName, AuthorityInfo.create(clientListnerTemplate, authorityServers));
1✔
240
      }
1✔
241
      builder.authorities(authorityInfoMapBuilder.buildOrThrow());
1✔
242
    }
243

244
    Map<String, ?> rawAllowedGrpcServices = JsonUtil.getObject(rawData, "allowed_grpc_services");
1✔
245
    builder.implSpecificObject(parseImplSpecificObject(rawAllowedGrpcServices));
1✔
246

247
    return builder;
1✔
248
  }
249

250
  protected Optional<Object> parseImplSpecificObject(
251
      @Nullable Map<String, ?> rawAllowedGrpcServices)
252
      throws XdsInitializationException {
253
    return Optional.empty();
×
254
  }
255

256
  private List<ServerInfo> parseServerInfos(List<?> rawServerConfigs, XdsLogger logger)
257
      throws XdsInitializationException {
258
    logger.log(XdsLogLevel.INFO, "Configured with {0} xDS servers", rawServerConfigs.size());
1✔
259
    ImmutableList.Builder<ServerInfo> servers = ImmutableList.builder();
1✔
260
    List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
1✔
261
    for (Map<String, ?> serverConfig : serverConfigList) {
1✔
262
      String serverUri = JsonUtil.getString(serverConfig, "server_uri");
1✔
263
      if (serverUri == null) {
1✔
264
        throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
1✔
265
      }
266
      logger.log(XdsLogLevel.INFO, "xDS server URI: {0}", serverUri);
1✔
267

268
      Object implSpecificConfig = getImplSpecificConfig(serverConfig, serverUri);
1✔
269

270
      boolean resourceTimerIsTransientError = false;
1✔
271
      boolean ignoreResourceDeletion = false;
1✔
272
      boolean failOnDataErrors = false;
1✔
273
      // "For forward compatibility reasons, the client will ignore any entry in the list that it
274
      // does not understand, regardless of type."
275
      List<?> serverFeatures = JsonUtil.getList(serverConfig, "server_features");
1✔
276
      if (serverFeatures != null) {
1✔
277
        logger.log(XdsLogLevel.INFO, "Server features: {0}", serverFeatures);
1✔
278
        if (serverFeatures.contains(SERVER_FEATURE_IGNORE_RESOURCE_DELETION)) {
1✔
279
          ignoreResourceDeletion = true;
1✔
280
        }
281
        resourceTimerIsTransientError = xdsDataErrorHandlingEnabled
1✔
282
            && serverFeatures.contains(SERVER_FEATURE_RESOURCE_TIMER_IS_TRANSIENT_ERROR);
1✔
283
        failOnDataErrors = xdsDataErrorHandlingEnabled
1✔
284
            && serverFeatures.contains(SERVER_FEATURE_FAIL_ON_DATA_ERRORS);
1✔
285
      }
286
      servers.add(
1✔
287
          ServerInfo.create(serverUri, implSpecificConfig, ignoreResourceDeletion,
1✔
288
              serverFeatures != null
289
                  && serverFeatures.contains(SERVER_FEATURE_TRUSTED_XDS_SERVER),
1✔
290
              resourceTimerIsTransientError, failOnDataErrors));
291
    }
1✔
292
    return servers.build();
1✔
293
  }
294

295
  @VisibleForTesting
296
  public void setFileReader(FileReader reader) {
297
    this.reader = reader;
1✔
298
  }
1✔
299

300
  /**
301
   * Reads the content of the file with the given path in the file system.
302
   */
303
  public interface FileReader {
304
    String readFile(String path) throws IOException;
305
  }
306

307
  protected enum LocalFileReader implements FileReader {
1✔
308
    INSTANCE;
1✔
309

310
    @Override
311
    public String readFile(String path) throws IOException {
312
      return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
×
313
    }
314
  }
315

316
  private static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
317
    if (value == null) {
1✔
318
      throw new XdsInitializationException(
1✔
319
          "Invalid bootstrap: '" + fieldName + "' does not exist.");
320
    }
321
    return value;
1✔
322
  }
323

324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc