From 0600b4d268435ee4eb8629c5a8bc50b4f7227586 Mon Sep 17 00:00:00 2001 From: niemannd <41839756+niemannd@users.noreply.github.com> Date: Sat, 5 Oct 2024 09:32:01 +0200 Subject: [PATCH] add ability to use sslBundles Fixes gh-2981 --- .../gateway/config/AbstractSslConfigurer.java | 18 +++++++++++++- .../config/GatewayAutoConfiguration.java | 11 +++++---- .../gateway/config/GrpcSslConfigurer.java | 20 +++++++++++++--- .../gateway/config/HttpClientProperties.java | 11 +++++++++ .../config/HttpClientSslConfigurer.java | 19 +++++++++++---- .../config/GatewayAutoConfigurationTests.java | 24 +++++++++++++++---- 6 files changed, 87 insertions(+), 16 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java index e31ee43a..66aa3da7 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java @@ -36,6 +36,8 @@ import io.netty.handler.ssl.SslContextBuilder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; import org.springframework.util.ResourceUtils; /** @@ -43,6 +45,7 @@ import org.springframework.util.ResourceUtils; * configuration (can be the same as T). * * @author Abel Salgado Romero + * @author Dominic Niemann */ public abstract class AbstractSslConfigurer { @@ -50,8 +53,11 @@ public abstract class AbstractSslConfigurer { private final HttpClientProperties.Ssl ssl; - protected AbstractSslConfigurer(HttpClientProperties.Ssl sslProperties) { + private final SslBundles bundles; + + protected AbstractSslConfigurer(HttpClientProperties.Ssl sslProperties, SslBundles bundles) { this.ssl = sslProperties; + this.bundles = bundles; } abstract public S configureSsl(T client) throws SSLException; @@ -60,6 +66,16 @@ public abstract class AbstractSslConfigurer { return ssl; } + protected SslBundle getBundle() { + if(ssl.getSslBundle() == null || ssl.getSslBundle().length() > 0) { + return null; + } + if(bundles.getBundleNames().contains(ssl.getSslBundle())) { + return bundles.getBundle(ssl.getSslBundle()); + } + return null; + } + protected X509Certificate[] getTrustedX509CertificatesForTrustManager() { try { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 956279d1..54ba6130 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -56,6 +56,7 @@ import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfig import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint; @@ -187,6 +188,7 @@ import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyReques * @author Mete Alpaslan Katırcıoğlu * @author Alberto C. Ríos * @author Olga Maciaszek-Sharma + * @author Dominic Niemann */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) @@ -353,13 +355,13 @@ public class GatewayAutoConfiguration { @ConditionalOnEnabledFilter(JsonToGrpcGatewayFilterFactory.class) @ConditionalOnMissingBean(GrpcSslConfigurer.class) @ConditionalOnClass(name = "io.grpc.Channel") - public GrpcSslConfigurer grpcSslConfigurer(HttpClientProperties properties) + public GrpcSslConfigurer grpcSslConfigurer(HttpClientProperties properties, SslBundles bundles) throws KeyStoreException, NoSuchAlgorithmException { TrustManagerFactory trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(KeyStore.getInstance(KeyStore.getDefaultType())); - return new GrpcSslConfigurer(properties.getSsl()); + return new GrpcSslConfigurer(properties.getSsl(), bundles); } @Bean @@ -755,8 +757,9 @@ public class GatewayAutoConfiguration { @Bean public HttpClientSslConfigurer httpClientSslConfigurer(ServerProperties serverProperties, - HttpClientProperties httpClientProperties) { - return new HttpClientSslConfigurer(httpClientProperties.getSsl(), serverProperties) { + HttpClientProperties httpClientProperties, + SslBundles bundles) { + return new HttpClientSslConfigurer(httpClientProperties.getSsl(), serverProperties, bundles) { }; } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java index f3b6907e..23299c40 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java @@ -25,13 +25,17 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; + /** * @author Alberto C. Ríos + * @author Dominic Niemann */ public class GrpcSslConfigurer extends AbstractSslConfigurer { - public GrpcSslConfigurer(HttpClientProperties.Ssl sslProperties) { - super(sslProperties); + public GrpcSslConfigurer(HttpClientProperties.Ssl sslProperties, SslBundles bundles) { + super(sslProperties, bundles); } @Override @@ -45,6 +49,7 @@ public class GrpcSslConfigurer extends AbstractSslConfigurer 0) { sslContextBuilder.trustManager(getTrustedX509CertificatesForTrustManager()); } + else if (bundle != null) { + sslContextBuilder.trustManager(bundle.getManagers().getTrustManagerFactory()); + } - return sslContextBuilder.keyManager(getKeyManagerFactory()).build(); + if (bundle != null) { + sslContextBuilder.keyManager(bundle.getManagers().getKeyManagerFactory()); + } + else { + sslContextBuilder.keyManager(getKeyManagerFactory()); + } + return sslContextBuilder.build(); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java index c03b0473..9c15d514 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java @@ -419,6 +419,9 @@ public class HttpClientProperties { /** Trusted certificates for verifying the remote endpoint's certificate. */ private List trustedX509Certificates = new ArrayList<>(); + /** The name of the SSL bundle to use. */ + private String sslBundle; + // use netty default SSL timeouts /** SSL handshake timeout. Default to 10000 ms */ private Duration handshakeTimeout = Duration.ofMillis(10000); @@ -524,6 +527,14 @@ public class HttpClientProperties { this.closeNotifyReadTimeout = closeNotifyReadTimeout; } + public String getSslBundle() { + return sslBundle; + } + + public void setSslBundle(String sslBundle) { + this.sslBundle = sslBundle; + } + @Override public String toString() { return new ToStringCreator(this).append("useInsecureTrustManager", useInsecureTrustManager) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java index f1048217..6d335358 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java @@ -25,20 +25,22 @@ import reactor.netty.http.client.HttpClient; import reactor.netty.tcp.SslProvider; import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.ssl.SslBundle; +import org.springframework.boot.ssl.SslBundles; public class HttpClientSslConfigurer extends AbstractSslConfigurer { private final ServerProperties serverProperties; - public HttpClientSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) { - super(sslProperties); + public HttpClientSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties, SslBundles bundles) { + super(sslProperties, bundles); this.serverProperties = serverProperties; } public HttpClient configureSsl(HttpClient client) { final HttpClientProperties.Ssl ssl = getSslProperties(); - if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) + if (getBundle() != null || (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) || getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) { client = client.secure(sslContextSpec -> { // configure ssl @@ -53,15 +55,24 @@ public class HttpClientSslConfigurer extends AbstractSslConfigurer { X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager(); + SslBundle bundle = getBundle(); if (trustedX509Certificates.length > 0) { setTrustManager(sslContextBuilder, trustedX509Certificates); } else if (ssl.isUseInsecureTrustManager()) { setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE); } + else if (bundle != null) { + setTrustManager(sslContextBuilder, bundle.getManagers().getTrustManagerFactory()); + } try { - sslContextBuilder.keyManager(getKeyManagerFactory()); + if(bundle != null) { + sslContextBuilder.keyManager(bundle.getManagers().getKeyManagerFactory()); + } + else { + sslContextBuilder.keyManager(getKeyManagerFactory()); + } } catch (Exception e) { logger.error(e); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java index 570911f6..8ff998ae 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java @@ -36,6 +36,7 @@ import reactor.netty.resources.ConnectionProvider; import reactor.netty.tcp.SslProvider; import reactor.netty.transport.ProxyProvider; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; @@ -45,9 +46,12 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.ssl.SslBundleRegistrar; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.ssl.DefaultSslBundleRegistry; +import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint; @@ -115,6 +119,7 @@ public class GatewayAutoConfigurationTests { SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) .withPropertyValues("spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true", + "spring.cloud.gateway.httpclient.ssl.ssl-bundle=mybundle", "spring.cloud.gateway.httpclient.connect-timeout=10", "spring.cloud.gateway.httpclient.response-timeout=10s", "spring.cloud.gateway.httpclient.pool.eviction-interval=10s", @@ -136,6 +141,7 @@ public class GatewayAutoConfigurationTests { assertThat(properties.getPool().getEvictionInterval()).hasSeconds(10); assertThat(properties.getPool().isMetrics()).isEqualTo(true); assertThat(properties.getPool().getLeasingStrategy()).isEqualTo(LeasingStrategy.LIFO); + assertThat(properties.getSsl().getSslBundle()).isEqualTo("mybundle"); assertThat(httpClient.configuration().isAcceptGzip()).isTrue(); assertThat(httpClient.configuration().loggingHandler()).isNotNull(); @@ -345,10 +351,20 @@ public class GatewayAutoConfigurationTests { @Bean @Primary CustomSslConfigurer customSslContextFactory(ServerProperties serverProperties, - HttpClientProperties httpClientProperties) { - return new CustomSslConfigurer(httpClientProperties.getSsl(), serverProperties); + HttpClientProperties httpClientProperties, + SslBundles bundles) { + return new CustomSslConfigurer(httpClientProperties.getSsl(), serverProperties, bundles); } + @Bean + @Primary + SslBundles sslBundleRegistry(ObjectProvider sslBundleRegistrars) { + DefaultSslBundleRegistry registry = new DefaultSslBundleRegistry(); + sslBundleRegistrars.orderedStream().forEach((registrar) -> { + registrar.registerBundles(registry); + }); + return registry; + } } protected static class CustomHttpClientFactory extends HttpClientFactory { @@ -392,8 +408,8 @@ public class GatewayAutoConfigurationTests { boolean insecureTrustManagerSet; - protected CustomSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) { - super(sslProperties, serverProperties); + protected CustomSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties, SslBundles bundles) { + super(sslProperties, serverProperties, bundles); } @Override