add ability to use sslBundles

Fixes gh-2981
This commit is contained in:
niemannd
2024-10-05 09:32:01 +02:00
committed by spencergibb
parent fd3c6f5d3d
commit 0600b4d268
6 changed files with 87 additions and 16 deletions

View File

@@ -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<T, S> {
@@ -50,8 +53,11 @@ public abstract class AbstractSslConfigurer<T, S> {
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<T, S> {
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 {

View File

@@ -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) {
};
}

View File

@@ -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<NettyChannelBuilder, ManagedChannel> {
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<NettyChannelBuilder
final HttpClientProperties.Ssl ssl = getSslProperties();
boolean useInsecureTrustManager = ssl.isUseInsecureTrustManager();
SslBundle bundle = getBundle();
if (useInsecureTrustManager) {
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()[0]);
}
@@ -52,8 +57,17 @@ public class GrpcSslConfigurer extends AbstractSslConfigurer<NettyChannelBuilder
if (!useInsecureTrustManager && ssl.getTrustedX509Certificates().size() > 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();
}
}

View File

@@ -419,6 +419,9 @@ public class HttpClientProperties {
/** Trusted certificates for verifying the remote endpoint's certificate. */
private List<String> 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)

View File

@@ -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<HttpClient, HttpClient> {
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<HttpClient, H
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
clientSslContext.configure(sslContextBuilder -> {
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);

View File

@@ -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<SslBundleRegistrar> 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