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

grpc / grpc-java / #20002

29 Sep 2025 04:21PM UTC coverage: 88.592% (+0.02%) from 88.575%
#20002

push

github

web-flow
xds: xDS based SNI setting and SAN validation (#12378)

When using xDS credentials make SNI for the Tls handshake to be
configured via xDS, rather than use the channel authority as the SNI,
and make SAN validation to be able to use the SNI sent when so
instructed via xDS.

Implements A101.

34877 of 39368 relevant lines covered (88.59%)

0.89 hits per line

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

95.79
/../xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java
1
/*
2
 * Copyright 2020 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

21
import com.google.auto.value.AutoValue;
22
import com.google.common.annotations.VisibleForTesting;
23
import com.google.common.collect.ImmutableList;
24
import com.google.protobuf.util.Durations;
25
import io.envoyproxy.envoy.config.core.v3.SocketAddress.Protocol;
26
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
27
import io.grpc.Internal;
28
import io.grpc.xds.client.EnvoyProtoData;
29
import io.grpc.xds.internal.security.SslContextProviderSupplier;
30
import java.net.InetAddress;
31
import java.util.Objects;
32
import javax.annotation.Nullable;
33

34
/**
35
 * Defines gRPC data types for Envoy protobuf messages used in xDS protocol on the server side,
36
 * similar to how {@link EnvoyProtoData} defines it for the client side.
37
 */
38
@Internal
39
public final class EnvoyServerProtoData {
40

41
  // Prevent instantiation.
42
  private EnvoyServerProtoData() {
43
  }
44

45
  public abstract static class BaseTlsContext {
46
    protected final CommonTlsContext commonTlsContext;
47

48
    protected BaseTlsContext(CommonTlsContext commonTlsContext) {
1✔
49
      this.commonTlsContext = checkNotNull(commonTlsContext, "commonTlsContext cannot be null.");
1✔
50
    }
1✔
51

52
    public CommonTlsContext getCommonTlsContext() {
53
      return commonTlsContext;
1✔
54
    }
55

56
    @Override
57
    public boolean equals(Object o) {
58
      if (this == o) {
1✔
59
        return true;
×
60
      }
61
      if (!(o instanceof BaseTlsContext)) {
1✔
62
        return false;
1✔
63
      }
64
      BaseTlsContext that = (BaseTlsContext) o;
1✔
65
      return Objects.equals(commonTlsContext, that.commonTlsContext);
1✔
66
    }
67

68
    @Override
69
    public int hashCode() {
70
      return Objects.hashCode(commonTlsContext);
1✔
71
    }
72
  }
73

74
  public static final class UpstreamTlsContext extends BaseTlsContext {
75

76
    private final String sni;
77
    private final boolean autoHostSni;
78
    private final boolean autoSniSanValidation;
79

80
    @VisibleForTesting
81
    public UpstreamTlsContext(CommonTlsContext commonTlsContext) {
82
      super(commonTlsContext);
1✔
83
      this.sni = null;
1✔
84
      this.autoHostSni = false;
1✔
85
      this.autoSniSanValidation = false;
1✔
86
    }
1✔
87

88
    @VisibleForTesting
89
    public UpstreamTlsContext(
90
        io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
91
            upstreamTlsContext) {
92
      super(upstreamTlsContext.getCommonTlsContext());
1✔
93
      this.sni = upstreamTlsContext.getSni();
1✔
94
      this.autoHostSni = upstreamTlsContext.getAutoHostSni();
1✔
95
      this.autoSniSanValidation = upstreamTlsContext.getAutoSniSanValidation();
1✔
96
    }
1✔
97

98
    public static UpstreamTlsContext fromEnvoyProtoUpstreamTlsContext(
99
        io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
100
            upstreamTlsContext) {
101
      return new UpstreamTlsContext(upstreamTlsContext);
1✔
102
    }
103

104
    public String getSni() {
105
      return sni;
1✔
106
    }
107

108
    public boolean getAutoHostSni() {
109
      return autoHostSni;
1✔
110
    }
111

112
    public boolean getAutoSniSanValidation() {
113
      return autoSniSanValidation;
1✔
114
    }
115

116
    @Override
117
    public String toString() {
118
      return "UpstreamTlsContext{"
1✔
119
          + "commonTlsContext=" + commonTlsContext
120
          + "\nsni=" + sni
121
          + "\nauto_host_sni=" + autoHostSni
122
          + "\nauto_sni_san_validation=" + autoSniSanValidation
123
          + "}";
124
    }
125
  }
126

127
  public static final class DownstreamTlsContext extends BaseTlsContext {
128

129
    private final boolean requireClientCertificate;
130

131
    @VisibleForTesting
132
    public DownstreamTlsContext(
133
        CommonTlsContext commonTlsContext, boolean requireClientCertificate) {
134
      super(commonTlsContext);
1✔
135
      this.requireClientCertificate = requireClientCertificate;
1✔
136
    }
1✔
137

138
    public static DownstreamTlsContext fromEnvoyProtoDownstreamTlsContext(
139
        io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
140
            downstreamTlsContext) {
141
      return new DownstreamTlsContext(downstreamTlsContext.getCommonTlsContext(),
1✔
142
        downstreamTlsContext.hasRequireClientCertificate());
1✔
143
    }
144

145
    public boolean isRequireClientCertificate() {
146
      return requireClientCertificate;
1✔
147
    }
148

149
    @Override
150
    public String toString() {
151
      return "DownstreamTlsContext{"
×
152
          + "commonTlsContext="
153
          + commonTlsContext
154
          + ", requireClientCertificate="
155
          + requireClientCertificate
156
          + '}';
157
    }
158

159
    @Override
160
    public boolean equals(Object o) {
161
      if (this == o) {
1✔
162
        return true;
×
163
      }
164
      if (o == null || getClass() != o.getClass()) {
1✔
165
        return false;
×
166
      }
167
      if (!super.equals(o)) {
1✔
168
        return false;
1✔
169
      }
170
      DownstreamTlsContext that = (DownstreamTlsContext) o;
1✔
171
      return requireClientCertificate == that.requireClientCertificate;
1✔
172
    }
173

174
    @Override
175
    public int hashCode() {
176
      return Objects.hash(super.hashCode(), requireClientCertificate);
1✔
177
    }
178
  }
179

180
  @AutoValue
181
  abstract static class CidrRange {
1✔
182

183
    abstract InetAddress addressPrefix();
184

185
    abstract int prefixLen();
186

187
    static CidrRange create(InetAddress addressPrefix, int prefixLen) {
188
      return new AutoValue_EnvoyServerProtoData_CidrRange(
1✔
189
          addressPrefix, prefixLen);
190
    }
191
  }
192

193
  enum ConnectionSourceType {
1✔
194
    // Any connection source matches.
195
    ANY,
1✔
196

197
    // Match a connection originating from the same host.
198
    SAME_IP_OR_LOOPBACK,
1✔
199

200
    // Match a connection originating from a different host.
201
    EXTERNAL
1✔
202
  }
203

204
  /**
205
   * Corresponds to Envoy proto message
206
   * {@link io.envoyproxy.envoy.config.listener.v3.FilterChainMatch}.
207
   */
208
  @AutoValue
209
  abstract static class FilterChainMatch {
1✔
210

211
    abstract int destinationPort();
212

213
    abstract ImmutableList<CidrRange> prefixRanges();
214

215
    abstract ImmutableList<String> applicationProtocols();
216

217
    abstract ImmutableList<CidrRange> sourcePrefixRanges();
218

219
    abstract ConnectionSourceType connectionSourceType();
220

221
    abstract ImmutableList<Integer> sourcePorts();
222

223
    abstract ImmutableList<String> serverNames();
224

225
    abstract String transportProtocol();
226

227
    public static FilterChainMatch create(int destinationPort,
228
        ImmutableList<CidrRange> prefixRanges,
229
        ImmutableList<String> applicationProtocols, ImmutableList<CidrRange> sourcePrefixRanges,
230
        ConnectionSourceType connectionSourceType, ImmutableList<Integer> sourcePorts,
231
        ImmutableList<String> serverNames, String transportProtocol) {
232
      return new AutoValue_EnvoyServerProtoData_FilterChainMatch(
1✔
233
          destinationPort, prefixRanges, applicationProtocols, sourcePrefixRanges,
234
          connectionSourceType, sourcePorts, serverNames, transportProtocol);
235
    }
236
  }
237

238
  /**
239
   * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.FilterChain}.
240
   */
241
  @AutoValue
242
  abstract static class FilterChain {
1✔
243

244
    // Must be unique per server instance (except the default chain).
245
    abstract String name();
246

247
    // TODO(sanjaypujare): flatten structure by moving FilterChainMatch class members here.
248
    abstract FilterChainMatch filterChainMatch();
249

250
    abstract HttpConnectionManager httpConnectionManager();
251

252
    @Nullable
253
    abstract SslContextProviderSupplier sslContextProviderSupplier();
254

255
    static FilterChain create(
256
        String name,
257
        FilterChainMatch filterChainMatch,
258
        HttpConnectionManager httpConnectionManager,
259
        @Nullable DownstreamTlsContext downstreamTlsContext,
260
        TlsContextManager tlsContextManager) {
261
      SslContextProviderSupplier sslContextProviderSupplier =
262
          downstreamTlsContext == null
1✔
263
              ? null : new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager);
1✔
264
      return new AutoValue_EnvoyServerProtoData_FilterChain(
1✔
265
          name, filterChainMatch, httpConnectionManager, sslContextProviderSupplier);
266
    }
267
  }
268

269
  /**
270
   * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.Listener} and
271
   * related classes.
272
   */
273
  @AutoValue
274
  abstract static class Listener {
1✔
275

276
    abstract String name();
277

278
    @Nullable
279
    abstract String address();
280

281
    abstract ImmutableList<FilterChain> filterChains();
282

283
    @Nullable
284
    abstract FilterChain defaultFilterChain();
285

286
    @Nullable
287
    abstract Protocol protocol();
288

289
    static Listener create(
290
        String name,
291
        @Nullable String address,
292
        ImmutableList<FilterChain> filterChains,
293
        @Nullable FilterChain defaultFilterChain,
294
        @Nullable Protocol protocol) {
295
      return new AutoValue_EnvoyServerProtoData_Listener(name, address, filterChains,
1✔
296
          defaultFilterChain, protocol);
297
    }
298
  }
299

300
  /**
301
   * Corresponds to Envoy proto message {@link
302
   * io.envoyproxy.envoy.config.cluster.v3.OutlierDetection}. Only the fields supported by gRPC are
303
   * included.
304
   *
305
   * <p>Protobuf Duration fields are represented in their string format (e.g. "10s").
306
   */
307
  @AutoValue
308
  abstract static class OutlierDetection {
1✔
309

310
    @Nullable
311
    abstract Long intervalNanos();
312

313
    @Nullable
314
    abstract Long baseEjectionTimeNanos();
315

316
    @Nullable
317
    abstract Long maxEjectionTimeNanos();
318

319
    @Nullable
320
    abstract Integer maxEjectionPercent();
321

322
    @Nullable
323
    abstract SuccessRateEjection successRateEjection();
324

325
    @Nullable
326
    abstract FailurePercentageEjection failurePercentageEjection();
327

328
    static OutlierDetection create(
329
        @Nullable Long intervalNanos,
330
        @Nullable Long baseEjectionTimeNanos,
331
        @Nullable Long maxEjectionTimeNanos,
332
        @Nullable Integer maxEjectionPercentage,
333
        @Nullable SuccessRateEjection successRateEjection,
334
        @Nullable FailurePercentageEjection failurePercentageEjection) {
335
      return new AutoValue_EnvoyServerProtoData_OutlierDetection(intervalNanos,
1✔
336
          baseEjectionTimeNanos, maxEjectionTimeNanos, maxEjectionPercentage, successRateEjection,
337
          failurePercentageEjection);
338
    }
339

340
    static OutlierDetection fromEnvoyOutlierDetection(
341
        io.envoyproxy.envoy.config.cluster.v3.OutlierDetection envoyOutlierDetection) {
342

343
      Long intervalNanos = envoyOutlierDetection.hasInterval()
1✔
344
          ? Durations.toNanos(envoyOutlierDetection.getInterval()) : null;
1✔
345
      Long baseEjectionTimeNanos = envoyOutlierDetection.hasBaseEjectionTime()
1✔
346
          ? Durations.toNanos(envoyOutlierDetection.getBaseEjectionTime()) : null;
1✔
347
      Long maxEjectionTimeNanos = envoyOutlierDetection.hasMaxEjectionTime()
1✔
348
          ? Durations.toNanos(envoyOutlierDetection.getMaxEjectionTime()) : null;
1✔
349
      Integer maxEjectionPercentage = envoyOutlierDetection.hasMaxEjectionPercent()
1✔
350
          ? envoyOutlierDetection.getMaxEjectionPercent().getValue() : null;
1✔
351

352
      SuccessRateEjection successRateEjection;
353
      // If success rate enforcement has been turned completely off, don't configure this ejection.
354
      if (envoyOutlierDetection.hasEnforcingSuccessRate()
1✔
355
          && envoyOutlierDetection.getEnforcingSuccessRate().getValue() == 0) {
1✔
356
        successRateEjection = null;
1✔
357
      } else {
358
        Integer stdevFactor = envoyOutlierDetection.hasSuccessRateStdevFactor()
1✔
359
            ? envoyOutlierDetection.getSuccessRateStdevFactor().getValue() : null;
1✔
360
        Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingSuccessRate()
1✔
361
            ? envoyOutlierDetection.getEnforcingSuccessRate().getValue() : null;
1✔
362
        Integer minimumHosts = envoyOutlierDetection.hasSuccessRateMinimumHosts()
1✔
363
            ? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null;
1✔
364
        Integer requestVolume = envoyOutlierDetection.hasSuccessRateRequestVolume()
1✔
365
            ? envoyOutlierDetection.getSuccessRateRequestVolume().getValue() : null;
1✔
366

367
        successRateEjection = SuccessRateEjection.create(stdevFactor, enforcementPercentage,
1✔
368
            minimumHosts, requestVolume);
369
      }
370

371
      FailurePercentageEjection failurePercentageEjection;
372
      if (envoyOutlierDetection.hasEnforcingFailurePercentage()
1✔
373
          && envoyOutlierDetection.getEnforcingFailurePercentage().getValue() == 0) {
1✔
374
        failurePercentageEjection = null;
1✔
375
      } else {
376
        Integer threshold = envoyOutlierDetection.hasFailurePercentageThreshold()
1✔
377
            ? envoyOutlierDetection.getFailurePercentageThreshold().getValue() : null;
1✔
378
        Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingFailurePercentage()
1✔
379
            ? envoyOutlierDetection.getEnforcingFailurePercentage().getValue() : null;
1✔
380
        Integer minimumHosts = envoyOutlierDetection.hasFailurePercentageMinimumHosts()
1✔
381
            ? envoyOutlierDetection.getFailurePercentageMinimumHosts().getValue() : null;
1✔
382
        Integer requestVolume = envoyOutlierDetection.hasFailurePercentageRequestVolume()
1✔
383
            ? envoyOutlierDetection.getFailurePercentageRequestVolume().getValue() : null;
1✔
384

385
        failurePercentageEjection = FailurePercentageEjection.create(threshold,
1✔
386
            enforcementPercentage, minimumHosts, requestVolume);
387
      }
388

389
      return create(intervalNanos, baseEjectionTimeNanos, maxEjectionTimeNanos,
1✔
390
          maxEjectionPercentage, successRateEjection, failurePercentageEjection);
391
    }
392
  }
393

394
  @AutoValue
395
  abstract static class SuccessRateEjection {
1✔
396

397
    @Nullable
398
    abstract Integer stdevFactor();
399

400
    @Nullable
401
    abstract Integer enforcementPercentage();
402

403
    @Nullable
404
    abstract Integer minimumHosts();
405

406
    @Nullable
407
    abstract Integer requestVolume();
408

409
    static SuccessRateEjection create(
410
        @Nullable Integer stdevFactor,
411
        @Nullable Integer enforcementPercentage,
412
        @Nullable Integer minimumHosts,
413
        @Nullable Integer requestVolume) {
414
      return new AutoValue_EnvoyServerProtoData_SuccessRateEjection(stdevFactor,
1✔
415
          enforcementPercentage, minimumHosts, requestVolume);
416
    }
417
  }
418

419
  @AutoValue
420
  abstract static class FailurePercentageEjection {
1✔
421

422
    @Nullable
423
    abstract Integer threshold();
424

425
    @Nullable
426
    abstract Integer enforcementPercentage();
427

428
    @Nullable
429
    abstract Integer minimumHosts();
430

431
    @Nullable
432
    abstract Integer requestVolume();
433

434
    static FailurePercentageEjection create(
435
        @Nullable Integer threshold,
436
        @Nullable Integer enforcementPercentage,
437
        @Nullable Integer minimumHosts,
438
        @Nullable Integer requestVolume) {
439
      return new AutoValue_EnvoyServerProtoData_FailurePercentageEjection(threshold,
1✔
440
          enforcementPercentage, minimumHosts, requestVolume);
441
    }
442
  }
443
}
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