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 com.google.auto.value.AutoValue; 20 import com.google.common.annotations.VisibleForTesting; 21 import com.google.common.collect.ImmutableList; 22 import com.google.protobuf.util.Durations; 23 import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; 24 import io.grpc.Internal; 25 import io.grpc.xds.internal.security.SslContextProviderSupplier; 26 import java.net.InetAddress; 27 import java.net.UnknownHostException; 28 import java.util.Objects; 29 import javax.annotation.Nullable; 30 31 /** 32 * Defines gRPC data types for Envoy protobuf messages used in xDS protocol on the server side, 33 * similar to how {@link EnvoyProtoData} defines it for the client side. 34 */ 35 @Internal 36 public final class EnvoyServerProtoData { 37 38 // Prevent instantiation. EnvoyServerProtoData()39 private EnvoyServerProtoData() { 40 } 41 42 public abstract static class BaseTlsContext { 43 @Nullable protected final CommonTlsContext commonTlsContext; 44 BaseTlsContext(@ullable CommonTlsContext commonTlsContext)45 protected BaseTlsContext(@Nullable CommonTlsContext commonTlsContext) { 46 this.commonTlsContext = commonTlsContext; 47 } 48 getCommonTlsContext()49 @Nullable public CommonTlsContext getCommonTlsContext() { 50 return commonTlsContext; 51 } 52 53 @Override equals(Object o)54 public boolean equals(Object o) { 55 if (this == o) { 56 return true; 57 } 58 if (!(o instanceof BaseTlsContext)) { 59 return false; 60 } 61 BaseTlsContext that = (BaseTlsContext) o; 62 return Objects.equals(commonTlsContext, that.commonTlsContext); 63 } 64 65 @Override hashCode()66 public int hashCode() { 67 return Objects.hashCode(commonTlsContext); 68 } 69 } 70 71 public static final class UpstreamTlsContext extends BaseTlsContext { 72 73 @VisibleForTesting UpstreamTlsContext(CommonTlsContext commonTlsContext)74 public UpstreamTlsContext(CommonTlsContext commonTlsContext) { 75 super(commonTlsContext); 76 } 77 fromEnvoyProtoUpstreamTlsContext( io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext)78 public static UpstreamTlsContext fromEnvoyProtoUpstreamTlsContext( 79 io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext 80 upstreamTlsContext) { 81 return new UpstreamTlsContext(upstreamTlsContext.getCommonTlsContext()); 82 } 83 84 @Override toString()85 public String toString() { 86 return "UpstreamTlsContext{" + "commonTlsContext=" + commonTlsContext + '}'; 87 } 88 } 89 90 public static final class DownstreamTlsContext extends BaseTlsContext { 91 92 private final boolean requireClientCertificate; 93 94 @VisibleForTesting DownstreamTlsContext( CommonTlsContext commonTlsContext, boolean requireClientCertificate)95 public DownstreamTlsContext( 96 CommonTlsContext commonTlsContext, boolean requireClientCertificate) { 97 super(commonTlsContext); 98 this.requireClientCertificate = requireClientCertificate; 99 } 100 fromEnvoyProtoDownstreamTlsContext( io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext downstreamTlsContext)101 public static DownstreamTlsContext fromEnvoyProtoDownstreamTlsContext( 102 io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext 103 downstreamTlsContext) { 104 return new DownstreamTlsContext(downstreamTlsContext.getCommonTlsContext(), 105 downstreamTlsContext.hasRequireClientCertificate()); 106 } 107 isRequireClientCertificate()108 public boolean isRequireClientCertificate() { 109 return requireClientCertificate; 110 } 111 112 @Override toString()113 public String toString() { 114 return "DownstreamTlsContext{" 115 + "commonTlsContext=" 116 + commonTlsContext 117 + ", requireClientCertificate=" 118 + requireClientCertificate 119 + '}'; 120 } 121 122 @Override equals(Object o)123 public boolean equals(Object o) { 124 if (this == o) { 125 return true; 126 } 127 if (o == null || getClass() != o.getClass()) { 128 return false; 129 } 130 if (!super.equals(o)) { 131 return false; 132 } 133 DownstreamTlsContext that = (DownstreamTlsContext) o; 134 return requireClientCertificate == that.requireClientCertificate; 135 } 136 137 @Override hashCode()138 public int hashCode() { 139 return Objects.hash(super.hashCode(), requireClientCertificate); 140 } 141 } 142 143 @AutoValue 144 abstract static class CidrRange { 145 addressPrefix()146 abstract InetAddress addressPrefix(); 147 prefixLen()148 abstract int prefixLen(); 149 create(String addressPrefix, int prefixLen)150 static CidrRange create(String addressPrefix, int prefixLen) throws UnknownHostException { 151 return new AutoValue_EnvoyServerProtoData_CidrRange( 152 InetAddress.getByName(addressPrefix), prefixLen); 153 } 154 } 155 156 enum ConnectionSourceType { 157 // Any connection source matches. 158 ANY, 159 160 // Match a connection originating from the same host. 161 SAME_IP_OR_LOOPBACK, 162 163 // Match a connection originating from a different host. 164 EXTERNAL 165 } 166 167 /** 168 * Corresponds to Envoy proto message 169 * {@link io.envoyproxy.envoy.config.listener.v3.FilterChainMatch}. 170 */ 171 @AutoValue 172 abstract static class FilterChainMatch { 173 destinationPort()174 abstract int destinationPort(); 175 prefixRanges()176 abstract ImmutableList<CidrRange> prefixRanges(); 177 applicationProtocols()178 abstract ImmutableList<String> applicationProtocols(); 179 sourcePrefixRanges()180 abstract ImmutableList<CidrRange> sourcePrefixRanges(); 181 connectionSourceType()182 abstract ConnectionSourceType connectionSourceType(); 183 sourcePorts()184 abstract ImmutableList<Integer> sourcePorts(); 185 serverNames()186 abstract ImmutableList<String> serverNames(); 187 transportProtocol()188 abstract String transportProtocol(); 189 create(int destinationPort, ImmutableList<CidrRange> prefixRanges, ImmutableList<String> applicationProtocols, ImmutableList<CidrRange> sourcePrefixRanges, ConnectionSourceType connectionSourceType, ImmutableList<Integer> sourcePorts, ImmutableList<String> serverNames, String transportProtocol)190 public static FilterChainMatch create(int destinationPort, 191 ImmutableList<CidrRange> prefixRanges, 192 ImmutableList<String> applicationProtocols, ImmutableList<CidrRange> sourcePrefixRanges, 193 ConnectionSourceType connectionSourceType, ImmutableList<Integer> sourcePorts, 194 ImmutableList<String> serverNames, String transportProtocol) { 195 return new AutoValue_EnvoyServerProtoData_FilterChainMatch( 196 destinationPort, prefixRanges, applicationProtocols, sourcePrefixRanges, 197 connectionSourceType, sourcePorts, serverNames, transportProtocol); 198 } 199 } 200 201 /** 202 * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.FilterChain}. 203 */ 204 @AutoValue 205 abstract static class FilterChain { 206 207 // possibly empty name()208 abstract String name(); 209 210 // TODO(sanjaypujare): flatten structure by moving FilterChainMatch class members here. filterChainMatch()211 abstract FilterChainMatch filterChainMatch(); 212 httpConnectionManager()213 abstract HttpConnectionManager httpConnectionManager(); 214 215 @Nullable sslContextProviderSupplier()216 abstract SslContextProviderSupplier sslContextProviderSupplier(); 217 create( String name, FilterChainMatch filterChainMatch, HttpConnectionManager httpConnectionManager, @Nullable DownstreamTlsContext downstreamTlsContext, TlsContextManager tlsContextManager)218 static FilterChain create( 219 String name, 220 FilterChainMatch filterChainMatch, 221 HttpConnectionManager httpConnectionManager, 222 @Nullable DownstreamTlsContext downstreamTlsContext, 223 TlsContextManager tlsContextManager) { 224 SslContextProviderSupplier sslContextProviderSupplier = 225 downstreamTlsContext == null 226 ? null : new SslContextProviderSupplier(downstreamTlsContext, tlsContextManager); 227 return new AutoValue_EnvoyServerProtoData_FilterChain( 228 name, filterChainMatch, httpConnectionManager, sslContextProviderSupplier); 229 } 230 } 231 232 /** 233 * Corresponds to Envoy proto message {@link io.envoyproxy.envoy.config.listener.v3.Listener} and 234 * related classes. 235 */ 236 @AutoValue 237 abstract static class Listener { 238 name()239 abstract String name(); 240 241 @Nullable address()242 abstract String address(); 243 filterChains()244 abstract ImmutableList<FilterChain> filterChains(); 245 246 @Nullable defaultFilterChain()247 abstract FilterChain defaultFilterChain(); 248 create( String name, @Nullable String address, ImmutableList<FilterChain> filterChains, @Nullable FilterChain defaultFilterChain)249 static Listener create( 250 String name, 251 @Nullable String address, 252 ImmutableList<FilterChain> filterChains, 253 @Nullable FilterChain defaultFilterChain) { 254 return new AutoValue_EnvoyServerProtoData_Listener(name, address, filterChains, 255 defaultFilterChain); 256 } 257 } 258 259 /** 260 * Corresponds to Envoy proto message {@link 261 * io.envoyproxy.envoy.config.cluster.v3.OutlierDetection}. Only the fields supported by gRPC are 262 * included. 263 * 264 * <p>Protobuf Duration fields are represented in their string format (e.g. "10s"). 265 */ 266 @AutoValue 267 abstract static class OutlierDetection { 268 269 @Nullable intervalNanos()270 abstract Long intervalNanos(); 271 272 @Nullable baseEjectionTimeNanos()273 abstract Long baseEjectionTimeNanos(); 274 275 @Nullable maxEjectionTimeNanos()276 abstract Long maxEjectionTimeNanos(); 277 278 @Nullable maxEjectionPercent()279 abstract Integer maxEjectionPercent(); 280 281 @Nullable successRateEjection()282 abstract SuccessRateEjection successRateEjection(); 283 284 @Nullable failurePercentageEjection()285 abstract FailurePercentageEjection failurePercentageEjection(); 286 create( @ullable Long intervalNanos, @Nullable Long baseEjectionTimeNanos, @Nullable Long maxEjectionTimeNanos, @Nullable Integer maxEjectionPercentage, @Nullable SuccessRateEjection successRateEjection, @Nullable FailurePercentageEjection failurePercentageEjection)287 static OutlierDetection create( 288 @Nullable Long intervalNanos, 289 @Nullable Long baseEjectionTimeNanos, 290 @Nullable Long maxEjectionTimeNanos, 291 @Nullable Integer maxEjectionPercentage, 292 @Nullable SuccessRateEjection successRateEjection, 293 @Nullable FailurePercentageEjection failurePercentageEjection) { 294 return new AutoValue_EnvoyServerProtoData_OutlierDetection(intervalNanos, 295 baseEjectionTimeNanos, maxEjectionTimeNanos, maxEjectionPercentage, successRateEjection, 296 failurePercentageEjection); 297 } 298 fromEnvoyOutlierDetection( io.envoyproxy.envoy.config.cluster.v3.OutlierDetection envoyOutlierDetection)299 static OutlierDetection fromEnvoyOutlierDetection( 300 io.envoyproxy.envoy.config.cluster.v3.OutlierDetection envoyOutlierDetection) { 301 302 Long intervalNanos = envoyOutlierDetection.hasInterval() 303 ? Durations.toNanos(envoyOutlierDetection.getInterval()) : null; 304 Long baseEjectionTimeNanos = envoyOutlierDetection.hasBaseEjectionTime() 305 ? Durations.toNanos(envoyOutlierDetection.getBaseEjectionTime()) : null; 306 Long maxEjectionTimeNanos = envoyOutlierDetection.hasMaxEjectionTime() 307 ? Durations.toNanos(envoyOutlierDetection.getMaxEjectionTime()) : null; 308 Integer maxEjectionPercentage = envoyOutlierDetection.hasMaxEjectionPercent() 309 ? envoyOutlierDetection.getMaxEjectionPercent().getValue() : null; 310 311 SuccessRateEjection successRateEjection; 312 // If success rate enforcement has been turned completely off, don't configure this ejection. 313 if (envoyOutlierDetection.hasEnforcingSuccessRate() 314 && envoyOutlierDetection.getEnforcingSuccessRate().getValue() == 0) { 315 successRateEjection = null; 316 } else { 317 Integer stdevFactor = envoyOutlierDetection.hasSuccessRateStdevFactor() 318 ? envoyOutlierDetection.getSuccessRateStdevFactor().getValue() : null; 319 Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingSuccessRate() 320 ? envoyOutlierDetection.getEnforcingSuccessRate().getValue() : null; 321 Integer minimumHosts = envoyOutlierDetection.hasSuccessRateMinimumHosts() 322 ? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null; 323 Integer requestVolume = envoyOutlierDetection.hasSuccessRateRequestVolume() 324 ? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null; 325 326 successRateEjection = SuccessRateEjection.create(stdevFactor, enforcementPercentage, 327 minimumHosts, requestVolume); 328 } 329 330 FailurePercentageEjection failurePercentageEjection; 331 if (envoyOutlierDetection.hasEnforcingFailurePercentage() 332 && envoyOutlierDetection.getEnforcingFailurePercentage().getValue() == 0) { 333 failurePercentageEjection = null; 334 } else { 335 Integer threshold = envoyOutlierDetection.hasFailurePercentageThreshold() 336 ? envoyOutlierDetection.getFailurePercentageThreshold().getValue() : null; 337 Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingFailurePercentage() 338 ? envoyOutlierDetection.getEnforcingFailurePercentage().getValue() : null; 339 Integer minimumHosts = envoyOutlierDetection.hasFailurePercentageMinimumHosts() 340 ? envoyOutlierDetection.getFailurePercentageMinimumHosts().getValue() : null; 341 Integer requestVolume = envoyOutlierDetection.hasFailurePercentageRequestVolume() 342 ? envoyOutlierDetection.getFailurePercentageRequestVolume().getValue() : null; 343 344 failurePercentageEjection = FailurePercentageEjection.create(threshold, 345 enforcementPercentage, minimumHosts, requestVolume); 346 } 347 348 return create(intervalNanos, baseEjectionTimeNanos, maxEjectionTimeNanos, 349 maxEjectionPercentage, successRateEjection, failurePercentageEjection); 350 } 351 } 352 353 @AutoValue 354 abstract static class SuccessRateEjection { 355 356 @Nullable stdevFactor()357 abstract Integer stdevFactor(); 358 359 @Nullable enforcementPercentage()360 abstract Integer enforcementPercentage(); 361 362 @Nullable minimumHosts()363 abstract Integer minimumHosts(); 364 365 @Nullable requestVolume()366 abstract Integer requestVolume(); 367 create( @ullable Integer stdevFactor, @Nullable Integer enforcementPercentage, @Nullable Integer minimumHosts, @Nullable Integer requestVolume)368 static SuccessRateEjection create( 369 @Nullable Integer stdevFactor, 370 @Nullable Integer enforcementPercentage, 371 @Nullable Integer minimumHosts, 372 @Nullable Integer requestVolume) { 373 return new AutoValue_EnvoyServerProtoData_SuccessRateEjection(stdevFactor, 374 enforcementPercentage, minimumHosts, requestVolume); 375 } 376 } 377 378 @AutoValue 379 abstract static class FailurePercentageEjection { 380 381 @Nullable threshold()382 abstract Integer threshold(); 383 384 @Nullable enforcementPercentage()385 abstract Integer enforcementPercentage(); 386 387 @Nullable minimumHosts()388 abstract Integer minimumHosts(); 389 390 @Nullable requestVolume()391 abstract Integer requestVolume(); 392 create( @ullable Integer threshold, @Nullable Integer enforcementPercentage, @Nullable Integer minimumHosts, @Nullable Integer requestVolume)393 static FailurePercentageEjection create( 394 @Nullable Integer threshold, 395 @Nullable Integer enforcementPercentage, 396 @Nullable Integer minimumHosts, 397 @Nullable Integer requestVolume) { 398 return new AutoValue_EnvoyServerProtoData_FailurePercentageEjection(threshold, 399 enforcementPercentage, minimumHosts, requestVolume); 400 } 401 } 402 } 403