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

grpc / grpc-java / #19524

25 Oct 2024 09:06AM UTC coverage: 84.61% (-0.007%) from 84.617%
#19524

push

github

web-flow
Xds: Implement using system root trust CA for TLS server authentication (#11470)

Allow using system root certs for server cert validation rather than CA root certs provided by the control plane when the validation context provided by the control plane specifies so.

33855 of 40013 relevant lines covered (84.61%)

0.85 hits per line

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

88.89
/../xds/src/main/java/io/grpc/xds/XdsClusterResource.java
1
/*
2
 * Copyright 2022 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 static com.google.common.base.Preconditions.checkNotNull;
20
import static io.grpc.xds.client.Bootstrapper.ServerInfo;
21

22
import com.google.auto.value.AutoValue;
23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.MoreObjects;
25
import com.google.common.base.Strings;
26
import com.google.common.collect.ImmutableList;
27
import com.google.common.collect.ImmutableMap;
28
import com.google.protobuf.Duration;
29
import com.google.protobuf.InvalidProtocolBufferException;
30
import com.google.protobuf.Message;
31
import com.google.protobuf.Struct;
32
import com.google.protobuf.util.Durations;
33
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
34
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
35
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
36
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
37
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
38
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
39
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
40
import io.grpc.LoadBalancerRegistry;
41
import io.grpc.NameResolver;
42
import io.grpc.internal.GrpcUtil;
43
import io.grpc.internal.ServiceConfigUtil;
44
import io.grpc.internal.ServiceConfigUtil.LbConfig;
45
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
46
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
47
import io.grpc.xds.XdsClusterResource.CdsUpdate;
48
import io.grpc.xds.client.XdsClient.ResourceUpdate;
49
import io.grpc.xds.client.XdsResourceType;
50
import io.grpc.xds.internal.security.CommonTlsContextUtil;
51
import java.util.List;
52
import java.util.Locale;
53
import java.util.Set;
54
import javax.annotation.Nullable;
55

56
class XdsClusterResource extends XdsResourceType<CdsUpdate> {
1✔
57
  @VisibleForTesting
58
  static boolean enableLeastRequest =
59
      !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
1✔
60
          ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
×
61
          : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest"));
1✔
62
  @VisibleForTesting
63
  public static boolean enableSystemRootCerts =
1✔
64
      GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false);
1✔
65

66
  @VisibleForTesting
67
  static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
68
  static final String ADS_TYPE_URL_CDS =
69
      "type.googleapis.com/envoy.config.cluster.v3.Cluster";
70
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT =
71
      "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext";
72
  private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 =
73
      "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext";
74
  private final LoadBalancerRegistry loadBalancerRegistry
1✔
75
      = LoadBalancerRegistry.getDefaultRegistry();
1✔
76

77
  private static final XdsClusterResource instance = new XdsClusterResource();
1✔
78

79
  public static XdsClusterResource getInstance() {
80
    return instance;
1✔
81
  }
82

83
  @Override
84
  @Nullable
85
  protected String extractResourceName(Message unpackedResource) {
86
    if (!(unpackedResource instanceof Cluster)) {
1✔
87
      return null;
×
88
    }
89
    return ((Cluster) unpackedResource).getName();
1✔
90
  }
91

92
  @Override
93
  public String typeName() {
94
    return "CDS";
1✔
95
  }
96

97
  @Override
98
  public String typeUrl() {
99
    return ADS_TYPE_URL_CDS;
1✔
100
  }
101

102
  @Override
103
  public boolean shouldRetrieveResourceKeysForArgs() {
104
    return true;
1✔
105
  }
106

107
  @Override
108
  protected boolean isFullStateOfTheWorld() {
109
    return true;
1✔
110
  }
111

112
  @Override
113
  @SuppressWarnings("unchecked")
114
  protected Class<Cluster> unpackedClassName() {
115
    return Cluster.class;
1✔
116
  }
117

118
  @Override
119
  protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
120
    if (!(unpackedMessage instanceof Cluster)) {
1✔
121
      throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
×
122
    }
123
    Set<String> certProviderInstances = null;
1✔
124
    if (args.getBootstrapInfo() != null && args.getBootstrapInfo().certProviders() != null) {
1✔
125
      certProviderInstances = args.getBootstrapInfo().certProviders().keySet();
1✔
126
    }
127
    return processCluster((Cluster) unpackedMessage, certProviderInstances,
1✔
128
        args.getServerInfo(), loadBalancerRegistry);
1✔
129
  }
130

131
  @VisibleForTesting
132
  static CdsUpdate processCluster(Cluster cluster,
133
                                  Set<String> certProviderInstances,
134
                                  ServerInfo serverInfo,
135
                                  LoadBalancerRegistry loadBalancerRegistry)
136
      throws ResourceInvalidException {
137
    StructOrError<CdsUpdate.Builder> structOrError;
138
    switch (cluster.getClusterDiscoveryTypeCase()) {
1✔
139
      case TYPE:
140
        structOrError = parseNonAggregateCluster(cluster,
1✔
141
            certProviderInstances, serverInfo);
142
        break;
1✔
143
      case CLUSTER_TYPE:
144
        structOrError = parseAggregateCluster(cluster);
1✔
145
        break;
1✔
146
      case CLUSTERDISCOVERYTYPE_NOT_SET:
147
      default:
148
        throw new ResourceInvalidException(
1✔
149
            "Cluster " + cluster.getName() + ": unspecified cluster discovery type");
1✔
150
    }
151
    if (structOrError.getErrorDetail() != null) {
1✔
152
      throw new ResourceInvalidException(structOrError.getErrorDetail());
1✔
153
    }
154
    CdsUpdate.Builder updateBuilder = structOrError.getStruct();
1✔
155

156
    ImmutableMap<String, ?> lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster,
1✔
157
        enableLeastRequest);
158

159
    // Validate the LB config by trying to parse it with the corresponding LB provider.
160
    LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig);
1✔
161
    NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider(
1✔
162
        lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig(
1✔
163
        lbConfig.getRawConfigValue());
1✔
164
    if (configOrError.getError() != null) {
1✔
165
      throw new ResourceInvalidException(structOrError.getErrorDetail());
×
166
    }
167

168
    updateBuilder.lbPolicyConfig(lbPolicyConfig);
1✔
169
    updateBuilder.filterMetadata(
1✔
170
        ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
1✔
171

172
    return updateBuilder.build();
1✔
173
  }
174

175
  private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
176
    String clusterName = cluster.getName();
1✔
177
    Cluster.CustomClusterType customType = cluster.getClusterType();
1✔
178
    String typeName = customType.getName();
1✔
179
    if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) {
1✔
180
      return StructOrError.fromError(
×
181
          "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName);
182
    }
183
    io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig;
184
    try {
185
      clusterConfig = unpackCompatibleType(customType.getTypedConfig(),
1✔
186
          io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class,
187
          TYPE_URL_CLUSTER_CONFIG, null);
188
    } catch (InvalidProtocolBufferException e) {
×
189
      return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e);
×
190
    }
1✔
191
    return StructOrError.fromStruct(CdsUpdate.forAggregate(
1✔
192
        clusterName, clusterConfig.getClustersList()));
1✔
193
  }
194

195
  private static StructOrError<CdsUpdate.Builder> parseNonAggregateCluster(
196
      Cluster cluster, Set<String> certProviderInstances, ServerInfo serverInfo) {
197
    String clusterName = cluster.getName();
1✔
198
    ServerInfo lrsServerInfo = null;
1✔
199
    Long maxConcurrentRequests = null;
1✔
200
    UpstreamTlsContext upstreamTlsContext = null;
1✔
201
    OutlierDetection outlierDetection = null;
1✔
202
    if (cluster.hasLrsServer()) {
1✔
203
      if (!cluster.getLrsServer().hasSelf()) {
1✔
204
        return StructOrError.fromError(
×
205
            "Cluster " + clusterName + ": only support LRS for the same management server");
206
      }
207
      lrsServerInfo = serverInfo;
1✔
208
    }
209
    if (cluster.hasCircuitBreakers()) {
1✔
210
      List<Thresholds> thresholds = cluster.getCircuitBreakers().getThresholdsList();
1✔
211
      for (Thresholds threshold : thresholds) {
1✔
212
        if (threshold.getPriority() != RoutingPriority.DEFAULT) {
1✔
213
          continue;
1✔
214
        }
215
        if (threshold.hasMaxRequests()) {
1✔
216
          maxConcurrentRequests = (long) threshold.getMaxRequests().getValue();
1✔
217
        }
218
      }
1✔
219
    }
220
    if (cluster.getTransportSocketMatchesCount() > 0) {
1✔
221
      return StructOrError.fromError("Cluster " + clusterName
1✔
222
          + ": transport-socket-matches not supported.");
223
    }
224
    if (cluster.hasTransportSocket()) {
1✔
225
      if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) {
1✔
226
        return StructOrError.fromError("transport-socket with name "
1✔
227
            + cluster.getTransportSocket().getName() + " not supported.");
1✔
228
      }
229
      try {
230
        upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
1✔
231
            validateUpstreamTlsContext(
1✔
232
                unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(),
1✔
233
                io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class,
234
                TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2),
235
                certProviderInstances));
236
      } catch (InvalidProtocolBufferException | ResourceInvalidException e) {
1✔
237
        return StructOrError.fromError(
1✔
238
            "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
239
      }
1✔
240
    }
241

242
    if (cluster.hasOutlierDetection()) {
1✔
243
      try {
244
        outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
1✔
245
            validateOutlierDetection(cluster.getOutlierDetection()));
1✔
246
      } catch (ResourceInvalidException e) {
1✔
247
        return StructOrError.fromError(
1✔
248
            "Cluster " + clusterName + ": malformed outlier_detection: " + e);
249
      }
1✔
250
    }
251

252
    Cluster.DiscoveryType type = cluster.getType();
1✔
253
    if (type == Cluster.DiscoveryType.EDS) {
1✔
254
      String edsServiceName = null;
1✔
255
      io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig =
1✔
256
          cluster.getEdsClusterConfig();
1✔
257
      if (!edsClusterConfig.getEdsConfig().hasAds()
1✔
258
          && ! edsClusterConfig.getEdsConfig().hasSelf()) {
1✔
259
        return StructOrError.fromError(
1✔
260
            "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use"
261
                + " EDS over ADS or self ConfigSource");
262
      }
263
      // If the service_name field is set, that value will be used for the EDS request.
264
      if (!edsClusterConfig.getServiceName().isEmpty()) {
1✔
265
        edsServiceName = edsClusterConfig.getServiceName();
1✔
266
      }
267
      // edsServiceName is required if the CDS resource has an xdstp name.
268
      if ((edsServiceName == null) && clusterName.toLowerCase(Locale.ROOT).startsWith("xdstp:")) {
1✔
269
        return StructOrError.fromError(
1✔
270
            "EDS service_name must be set when Cluster resource has an xdstp name");
271
      }
272
      return StructOrError.fromStruct(CdsUpdate.forEds(
1✔
273
          clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
274
          outlierDetection));
275
    } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) {
1✔
276
      if (!cluster.hasLoadAssignment()) {
1✔
277
        return StructOrError.fromError(
×
278
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host");
279
      }
280
      ClusterLoadAssignment assignment = cluster.getLoadAssignment();
1✔
281
      if (assignment.getEndpointsCount() != 1
1✔
282
          || assignment.getEndpoints(0).getLbEndpointsCount() != 1) {
1✔
283
        return StructOrError.fromError(
×
284
            "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single "
285
                + "locality_lb_endpoint and a single lb_endpoint");
286
      }
287
      io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint =
1✔
288
          assignment.getEndpoints(0).getLbEndpoints(0);
1✔
289
      if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress()
1✔
290
          || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) {
1✔
291
        return StructOrError.fromError(
×
292
            "Cluster " + clusterName
293
                + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address");
294
      }
295
      SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
1✔
296
      if (!socketAddress.getResolverName().isEmpty()) {
1✔
297
        return StructOrError.fromError(
×
298
            "Cluster " + clusterName
299
                + ": LOGICAL DNS clusters must NOT have a custom resolver name set");
300
      }
301
      if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) {
1✔
302
        return StructOrError.fromError(
×
303
            "Cluster " + clusterName
304
                + ": LOGICAL DNS clusters socket_address must have port_value");
305
      }
306
      String dnsHostName = String.format(
1✔
307
          Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue());
1✔
308
      return StructOrError.fromStruct(CdsUpdate.forLogicalDns(
1✔
309
          clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext));
310
    }
311
    return StructOrError.fromError(
×
312
        "Cluster " + clusterName + ": unsupported built-in discovery type: " + type);
313
  }
314

315
  static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
316
      io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
317
      throws ResourceInvalidException {
318
    if (outlierDetection.hasInterval()) {
1✔
319
      if (!Durations.isValid(outlierDetection.getInterval())) {
1✔
320
        throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
1✔
321
      }
322
      if (hasNegativeValues(outlierDetection.getInterval())) {
1✔
323
        throw new ResourceInvalidException("outlier_detection interval has a negative value");
1✔
324
      }
325
    }
326
    if (outlierDetection.hasBaseEjectionTime()) {
1✔
327
      if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
1✔
328
        throw new ResourceInvalidException(
1✔
329
            "outlier_detection base_ejection_time is not a valid Duration");
330
      }
331
      if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
1✔
332
        throw new ResourceInvalidException(
1✔
333
            "outlier_detection base_ejection_time has a negative value");
334
      }
335
    }
336
    if (outlierDetection.hasMaxEjectionTime()) {
1✔
337
      if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
1✔
338
        throw new ResourceInvalidException(
1✔
339
            "outlier_detection max_ejection_time is not a valid Duration");
340
      }
341
      if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
1✔
342
        throw new ResourceInvalidException(
1✔
343
            "outlier_detection max_ejection_time has a negative value");
344
      }
345
    }
346
    if (outlierDetection.hasMaxEjectionPercent()
1✔
347
        && outlierDetection.getMaxEjectionPercent().getValue() > 100) {
1✔
348
      throw new ResourceInvalidException(
1✔
349
          "outlier_detection max_ejection_percent is > 100");
350
    }
351
    if (outlierDetection.hasEnforcingSuccessRate()
1✔
352
        && outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
1✔
353
      throw new ResourceInvalidException(
1✔
354
          "outlier_detection enforcing_success_rate is > 100");
355
    }
356
    if (outlierDetection.hasFailurePercentageThreshold()
1✔
357
        && outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
1✔
358
      throw new ResourceInvalidException(
1✔
359
          "outlier_detection failure_percentage_threshold is > 100");
360
    }
361
    if (outlierDetection.hasEnforcingFailurePercentage()
1✔
362
        && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
1✔
363
      throw new ResourceInvalidException(
1✔
364
          "outlier_detection enforcing_failure_percentage is > 100");
365
    }
366

367
    return outlierDetection;
1✔
368
  }
369

370
  static boolean hasNegativeValues(Duration duration) {
371
    return duration.getSeconds() < 0 || duration.getNanos() < 0;
1✔
372
  }
373

374
  @VisibleForTesting
375
  static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
376
      validateUpstreamTlsContext(
377
      io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext,
378
      Set<String> certProviderInstances)
379
      throws ResourceInvalidException {
380
    if (upstreamTlsContext.hasCommonTlsContext()) {
1✔
381
      validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances,
1✔
382
          false);
383
    } else {
384
      throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context");
1✔
385
    }
386
    return upstreamTlsContext;
1✔
387
  }
388

389
  @VisibleForTesting
390
  static void validateCommonTlsContext(
391
      CommonTlsContext commonTlsContext, Set<String> certProviderInstances, boolean server)
392
      throws ResourceInvalidException {
393
    if (commonTlsContext.hasCustomHandshaker()) {
1✔
394
      throw new ResourceInvalidException(
1✔
395
          "common-tls-context with custom_handshaker is not supported");
396
    }
397
    if (commonTlsContext.hasTlsParams()) {
1✔
398
      throw new ResourceInvalidException("common-tls-context with tls_params is not supported");
1✔
399
    }
400
    if (commonTlsContext.hasValidationContextSdsSecretConfig()) {
1✔
401
      throw new ResourceInvalidException(
1✔
402
          "common-tls-context with validation_context_sds_secret_config is not supported");
403
    }
404
    if (commonTlsContext.hasValidationContextCertificateProvider()) {
1✔
405
      throw new ResourceInvalidException(
1✔
406
          "common-tls-context with validation_context_certificate_provider is not supported");
407
    }
408
    if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
1✔
409
      throw new ResourceInvalidException(
1✔
410
          "common-tls-context with validation_context_certificate_provider_instance is not"
411
              + " supported");
412
    }
413
    String certInstanceName = getIdentityCertInstanceName(commonTlsContext);
1✔
414
    if (certInstanceName == null) {
1✔
415
      if (server) {
1✔
416
        throw new ResourceInvalidException(
1✔
417
            "tls_certificate_provider_instance is required in downstream-tls-context");
418
      }
419
      if (commonTlsContext.getTlsCertificatesCount() > 0) {
1✔
420
        throw new ResourceInvalidException(
1✔
421
            "tls_certificate_provider_instance is unset");
422
      }
423
      if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) {
1✔
424
        throw new ResourceInvalidException(
1✔
425
            "tls_certificate_provider_instance is unset");
426
      }
427
      if (commonTlsContext.hasTlsCertificateCertificateProvider()) {
1✔
428
        throw new ResourceInvalidException(
1✔
429
            "tls_certificate_provider_instance is unset");
430
      }
431
    } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) {
1✔
432
      throw new ResourceInvalidException(
1✔
433
          "CertificateProvider instance name '" + certInstanceName
434
              + "' not defined in the bootstrap file.");
435
    }
436
    String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
1✔
437
    if (rootCaInstanceName == null) {
1✔
438
      if (!server && (!enableSystemRootCerts
1✔
439
          || !CommonTlsContextUtil.isUsingSystemRootCerts(commonTlsContext))) {
1✔
440
        throw new ResourceInvalidException(
1✔
441
            "ca_certificate_provider_instance or system_root_certs is required in "
442
                + "upstream-tls-context");
443
      }
444
    } else {
445
      if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
1✔
446
        throw new ResourceInvalidException(
1✔
447
            "ca_certificate_provider_instance name '" + rootCaInstanceName
448
                + "' not defined in the bootstrap file.");
449
      }
450
      CertificateValidationContext certificateValidationContext = null;
1✔
451
      if (commonTlsContext.hasValidationContext()) {
1✔
452
        certificateValidationContext = commonTlsContext.getValidationContext();
1✔
453
      } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext
1✔
454
          .getCombinedValidationContext().hasDefaultValidationContext()) {
1✔
455
        certificateValidationContext = commonTlsContext.getCombinedValidationContext()
1✔
456
            .getDefaultValidationContext();
1✔
457
      }
458
      if (certificateValidationContext != null) {
1✔
459
        if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) {
1✔
460
          throw new ResourceInvalidException(
1✔
461
              "match_subject_alt_names only allowed in upstream_tls_context");
462
        }
463
        if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) {
1✔
464
          throw new ResourceInvalidException(
1✔
465
              "verify_certificate_spki in default_validation_context is not supported");
466
        }
467
        if (certificateValidationContext.getVerifyCertificateHashCount() > 0) {
1✔
468
          throw new ResourceInvalidException(
1✔
469
              "verify_certificate_hash in default_validation_context is not supported");
470
        }
471
        if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) {
1✔
472
          throw new ResourceInvalidException(
1✔
473
              "require_signed_certificate_timestamp in default_validation_context is not "
474
                  + "supported");
475
        }
476
        if (certificateValidationContext.hasCrl()) {
1✔
477
          throw new ResourceInvalidException("crl in default_validation_context is not supported");
1✔
478
        }
479
        if (certificateValidationContext.hasCustomValidatorConfig()) {
1✔
480
          throw new ResourceInvalidException(
1✔
481
              "custom_validator_config in default_validation_context is not supported");
482
        }
483
      }
484
    }
485
  }
1✔
486

487
  private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
488
    if (commonTlsContext.hasTlsCertificateProviderInstance()) {
1✔
489
      return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
1✔
490
    } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
1✔
491
      return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName();
1✔
492
    }
493
    return null;
1✔
494
  }
495

496
  private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) {
497
    if (commonTlsContext.hasValidationContext()) {
1✔
498
      if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) {
1✔
499
        return commonTlsContext.getValidationContext().getCaCertificateProviderInstance()
1✔
500
            .getInstanceName();
1✔
501
      }
502
    } else if (commonTlsContext.hasCombinedValidationContext()) {
1✔
503
      CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext
1✔
504
          = commonTlsContext.getCombinedValidationContext();
1✔
505
      if (combinedCertificateValidationContext.hasDefaultValidationContext()
1✔
506
          && combinedCertificateValidationContext.getDefaultValidationContext()
1✔
507
          .hasCaCertificateProviderInstance()) {
1✔
508
        return combinedCertificateValidationContext.getDefaultValidationContext()
×
509
            .getCaCertificateProviderInstance().getInstanceName();
×
510
      } else if (combinedCertificateValidationContext
1✔
511
          .hasValidationContextCertificateProviderInstance()) {
1✔
512
        return combinedCertificateValidationContext
1✔
513
            .getValidationContextCertificateProviderInstance().getInstanceName();
1✔
514
      }
515
    }
516
    return null;
1✔
517
  }
518

519
  /** xDS resource update for cluster-level configuration. */
520
  @AutoValue
521
  abstract static class CdsUpdate implements ResourceUpdate {
1✔
522
    abstract String clusterName();
523

524
    abstract ClusterType clusterType();
525

526
    abstract ImmutableMap<String, ?> lbPolicyConfig();
527

528
    // Only valid if lbPolicy is "ring_hash_experimental".
529
    abstract long minRingSize();
530

531
    // Only valid if lbPolicy is "ring_hash_experimental".
532
    abstract long maxRingSize();
533

534
    // Only valid if lbPolicy is "least_request_experimental".
535
    abstract int choiceCount();
536

537
    // Alternative resource name to be used in EDS requests.
538
    /// Only valid for EDS cluster.
539
    @Nullable
540
    abstract String edsServiceName();
541

542
    // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable
543
    // via DNS.
544
    // Only valid for LOGICAL_DNS cluster.
545
    @Nullable
546
    abstract String dnsHostName();
547

548
    // Load report server info for reporting loads via LRS.
549
    // Only valid for EDS or LOGICAL_DNS cluster.
550
    @Nullable
551
    abstract ServerInfo lrsServerInfo();
552

553
    // Max number of concurrent requests can be sent to this cluster.
554
    // Only valid for EDS or LOGICAL_DNS cluster.
555
    @Nullable
556
    abstract Long maxConcurrentRequests();
557

558
    // TLS context used to connect to connect to this cluster.
559
    // Only valid for EDS or LOGICAL_DNS cluster.
560
    @Nullable
561
    abstract UpstreamTlsContext upstreamTlsContext();
562

563
    // List of underlying clusters making of this aggregate cluster.
564
    // Only valid for AGGREGATE cluster.
565
    @Nullable
566
    abstract ImmutableList<String> prioritizedClusterNames();
567

568
    // Outlier detection configuration.
569
    @Nullable
570
    abstract OutlierDetection outlierDetection();
571

572
    abstract ImmutableMap<String, Struct> filterMetadata();
573

574
    private static Builder newBuilder(String clusterName) {
575
      return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
1✔
576
          .clusterName(clusterName)
1✔
577
          .minRingSize(0)
1✔
578
          .maxRingSize(0)
1✔
579
          .choiceCount(0)
1✔
580
          .filterMetadata(ImmutableMap.of());
1✔
581
    }
582

583
    static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
584
      checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
1✔
585
      return newBuilder(clusterName)
1✔
586
          .clusterType(ClusterType.AGGREGATE)
1✔
587
          .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames));
1✔
588
    }
589

590
    static Builder forEds(String clusterName, @Nullable String edsServiceName,
591
                          @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
592
                          @Nullable UpstreamTlsContext upstreamTlsContext,
593
                          @Nullable OutlierDetection outlierDetection) {
594
      return newBuilder(clusterName)
1✔
595
          .clusterType(ClusterType.EDS)
1✔
596
          .edsServiceName(edsServiceName)
1✔
597
          .lrsServerInfo(lrsServerInfo)
1✔
598
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
599
          .upstreamTlsContext(upstreamTlsContext)
1✔
600
          .outlierDetection(outlierDetection);
1✔
601
    }
602

603
    static Builder forLogicalDns(String clusterName, String dnsHostName,
604
                                 @Nullable ServerInfo lrsServerInfo,
605
                                 @Nullable Long maxConcurrentRequests,
606
                                 @Nullable UpstreamTlsContext upstreamTlsContext) {
607
      return newBuilder(clusterName)
1✔
608
          .clusterType(ClusterType.LOGICAL_DNS)
1✔
609
          .dnsHostName(dnsHostName)
1✔
610
          .lrsServerInfo(lrsServerInfo)
1✔
611
          .maxConcurrentRequests(maxConcurrentRequests)
1✔
612
          .upstreamTlsContext(upstreamTlsContext);
1✔
613
    }
614

615
    enum ClusterType {
1✔
616
      EDS, LOGICAL_DNS, AGGREGATE
1✔
617
    }
618

619
    enum LbPolicy {
×
620
      ROUND_ROBIN, RING_HASH, LEAST_REQUEST
×
621
    }
622

623
    // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed.
624
    @Override
625
    public final String toString() {
626
      return MoreObjects.toStringHelper(this)
×
627
          .add("clusterName", clusterName())
×
628
          .add("clusterType", clusterType())
×
629
          .add("lbPolicyConfig", lbPolicyConfig())
×
630
          .add("minRingSize", minRingSize())
×
631
          .add("maxRingSize", maxRingSize())
×
632
          .add("choiceCount", choiceCount())
×
633
          .add("edsServiceName", edsServiceName())
×
634
          .add("dnsHostName", dnsHostName())
×
635
          .add("lrsServerInfo", lrsServerInfo())
×
636
          .add("maxConcurrentRequests", maxConcurrentRequests())
×
637
          // Exclude upstreamTlsContext and outlierDetection as their string representations are
638
          // cumbersome.
639
          .add("prioritizedClusterNames", prioritizedClusterNames())
×
640
          .toString();
×
641
    }
642

643
    @AutoValue.Builder
644
    abstract static class Builder {
1✔
645
      // Private, use one of the static factory methods instead.
646
      protected abstract Builder clusterName(String clusterName);
647

648
      // Private, use one of the static factory methods instead.
649
      protected abstract Builder clusterType(ClusterType clusterType);
650

651
      protected abstract Builder lbPolicyConfig(ImmutableMap<String, ?> lbPolicyConfig);
652

653
      Builder roundRobinLbPolicy() {
654
        return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of()));
1✔
655
      }
656

657
      Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) {
658
        return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental",
1✔
659
            ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize",
1✔
660
                maxRingSize.doubleValue())));
1✔
661
      }
662

663
      Builder leastRequestLbPolicy(Integer choiceCount) {
664
        return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental",
1✔
665
            ImmutableMap.of("choiceCount", choiceCount.doubleValue())));
1✔
666
      }
667

668
      // Private, use leastRequestLbPolicy(int).
669
      protected abstract Builder choiceCount(int choiceCount);
670

671
      // Private, use ringHashLbPolicy(long, long).
672
      protected abstract Builder minRingSize(long minRingSize);
673

674
      // Private, use ringHashLbPolicy(long, long).
675
      protected abstract Builder maxRingSize(long maxRingSize);
676

677
      // Private, use CdsUpdate.forEds() instead.
678
      protected abstract Builder edsServiceName(String edsServiceName);
679

680
      // Private, use CdsUpdate.forLogicalDns() instead.
681
      protected abstract Builder dnsHostName(String dnsHostName);
682

683
      // Private, use one of the static factory methods instead.
684
      protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo);
685

686
      // Private, use one of the static factory methods instead.
687
      protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests);
688

689
      // Private, use one of the static factory methods instead.
690
      protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext);
691

692
      // Private, use CdsUpdate.forAggregate() instead.
693
      protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
694

695
      protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
696

697
      protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
698

699
      abstract CdsUpdate build();
700
    }
701
  }
702
}
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

© 2025 Coveralls, Inc