Polish gh-2699 Updates HttpClientFactory for backwards compatibility

If a user has extended HttpClientFactory and doesn't use the new SslConfigurer, the original protected methods need to be there. Those methods are marked as deprecated.
This commit is contained in:
spencergibb
2022-08-31 11:48:16 -04:00
parent 774975a0fe
commit 0b58d77102
2 changed files with 240 additions and 21 deletions

View File

@@ -16,9 +16,18 @@
package org.springframework.cloud.gateway.config;
import java.io.IOException;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchProviderException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
@@ -26,6 +35,9 @@ import javax.net.ssl.TrustManagerFactory;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import reactor.netty.http.Http11SslContextSpec;
import reactor.netty.http.Http2SslContextSpec;
import reactor.netty.http.HttpProtocol;
import reactor.netty.http.client.HttpClient;
import reactor.netty.http.client.HttpResponseDecoderSpec;
@@ -38,6 +50,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED;
@@ -64,7 +77,7 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
List<HttpClientCustomizer> customizers) {
this.properties = properties;
this.serverProperties = serverProperties;
this.sslConfigurer = new HttpClientSslConfigurer(properties.getSsl(), serverProperties);
this.sslConfigurer = null;
this.customizers = customizers;
}
@@ -116,31 +129,45 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
}
protected HttpClient configureSsl(HttpClient httpClient) {
return sslConfigurer.configureSsl(httpClient);
if (sslConfigurer != null) {
return sslConfigurer.configureSsl(httpClient);
}
HttpClientProperties.Ssl ssl = properties.getSsl();
if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
|| getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
httpClient = httpClient.secure(sslContextSpec -> {
// configure ssl
configureSslContext(ssl, sslContextSpec);
});
}
return httpClient;
}
@Deprecated
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
sslConfigurer.configureSslContext(ssl, sslContextSpec);
}
SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled())
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
clientSslContext.configure(sslContextBuilder -> {
X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager();
if (trustedX509Certificates.length > 0) {
setTrustManager(sslContextBuilder, trustedX509Certificates);
}
else if (ssl.isUseInsecureTrustManager()) {
setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE);
}
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
return sslConfigurer.getTrustedX509CertificatesForTrustManager();
}
try {
sslContextBuilder.keyManager(getKeyManagerFactory());
}
catch (Exception e) {
logger.error(e);
}
});
protected KeyManagerFactory getKeyManagerFactory() {
return sslConfigurer.getKeyManagerFactory();
}
protected KeyStore createKeyStore() {
return sslConfigurer.createKeyStore();
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
sslConfigurer.setTrustManager(sslContextBuilder, trustedX509Certificates);
}
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
sslConfigurer.setTrustManager(sslContextBuilder, factory);
sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout())
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
}
private HttpClient applyCustomizers(HttpClient httpClient) {
@@ -165,6 +192,88 @@ public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
return httpClient;
}
@Deprecated
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
ArrayList<Certificate> allCerts = new ArrayList<>();
for (String trustedCert : ssl.getTrustedX509Certificates()) {
try {
URL url = ResourceUtils.getURL(trustedCert);
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(url.openStream());
allCerts.addAll(certs);
}
catch (IOException e) {
throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e);
}
}
return allCerts.toArray(new X509Certificate[allCerts.size()]);
}
catch (CertificateException e1) {
throw new RuntimeException("Could not load CertificateFactory X.509", e1);
}
}
@Deprecated
protected KeyManagerFactory getKeyManagerFactory() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) {
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null;
if (keyPassword == null && ssl.getKeyStorePassword() != null) {
keyPassword = ssl.getKeyStorePassword().toCharArray();
}
keyManagerFactory.init(this.createKeyStore(), keyPassword);
return keyManagerFactory;
}
return null;
}
catch (Exception e) {
throw new IllegalStateException(e);
}
}
@Deprecated
protected KeyStore createKeyStore() {
HttpClientProperties.Ssl ssl = properties.getSsl();
try {
KeyStore store = ssl.getKeyStoreProvider() != null
? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider())
: KeyStore.getInstance(ssl.getKeyStoreType());
try {
URL url = ResourceUtils.getURL(ssl.getKeyStore());
store.load(url.openStream(),
ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null);
}
catch (Exception e) {
throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e);
}
return store;
}
catch (KeyStoreException | NoSuchProviderException e) {
throw new RuntimeException("Could not load KeyStore for given type and provider", e);
}
}
@Deprecated
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
sslContextBuilder.trustManager(trustedX509Certificates);
}
@Deprecated
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
sslContextBuilder.trustManager(factory);
}
protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy,
ProxyProvider.TypeSpec proxySpec) {
ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).host(proxy.getHost());

View File

@@ -18,9 +18,12 @@ package org.springframework.cloud.gateway.config;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import io.netty.channel.ChannelOption;
@@ -164,6 +167,28 @@ public class GatewayAutoConfigurationTests {
});
}
@Test
@Deprecated
public void nettyHttpClientNoSslConfigurerIsBackwardsCompatible() {
new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
NoSslConfigurerCustomHttpClientFactoryConfig.class))
.withPropertyValues("spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true")
.run(context -> {
assertThat(context).hasSingleBean(HttpClient.class);
NoSslConfigurerHttpClientFactory factory = context.getBean(NoSslConfigurerHttpClientFactory.class);
assertThat(factory.configureSslCalled).isTrue();
assertThat(factory.configureSslContextCalled).isTrue();
assertThat(factory.getTrustedX509CertificatesForTrustManagerCalled).isTrue();
assertThat(factory.getKeyManagerFactoryCalled).isTrue();
assertThat(factory.createKeyStoreCalled).isFalse();
assertThat(factory.setTrustManagerCertCalled).isFalse();
assertThat(factory.setTrustManagerFactoryCalled).isTrue();
});
}
@Test
public void verboseActuatorEnabledByDefault() {
try (ConfigurableApplicationContext ctx = SpringApplication.run(Config.class, "--spring.jmx.enabled=false",
@@ -325,6 +350,21 @@ public class GatewayAutoConfigurationTests {
}
@Configuration
@EnableConfigurationProperties(ServerProperties.class)
@AutoConfigureBefore(GatewayAutoConfiguration.class)
@Deprecated
protected static class NoSslConfigurerCustomHttpClientFactoryConfig {
@Bean
@Primary
NoSslConfigurerHttpClientFactory noSslConfigurerHttpClientFactory(HttpClientProperties properties,
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
return new NoSslConfigurerHttpClientFactory(properties, serverProperties, customizers);
}
}
protected static class CustomHttpClientFactory extends HttpClientFactory {
private ConnectionProvider connectionProvider;
@@ -387,6 +427,76 @@ public class GatewayAutoConfigurationTests {
}
/*
* Class to test backwards compatibility if no `SslConfigurer` used.
*/
@Deprecated
protected static class NoSslConfigurerHttpClientFactory extends HttpClientFactory {
boolean configureSslCalled;
boolean configureSslContextCalled;
boolean getTrustedX509CertificatesForTrustManagerCalled;
boolean getKeyManagerFactoryCalled;
boolean createKeyStoreCalled;
boolean setTrustManagerCertCalled;
boolean setTrustManagerFactoryCalled;
public NoSslConfigurerHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
List<HttpClientCustomizer> customizers) {
super(properties, serverProperties, customizers);
}
@Override
protected HttpClient configureSsl(HttpClient httpClient) {
configureSslCalled = true;
return super.configureSsl(httpClient);
}
@Override
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
configureSslContextCalled = true;
super.configureSslContext(ssl, sslContextSpec);
}
@Override
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
getTrustedX509CertificatesForTrustManagerCalled = true;
return super.getTrustedX509CertificatesForTrustManager();
}
@Override
protected KeyManagerFactory getKeyManagerFactory() {
getKeyManagerFactoryCalled = true;
return super.getKeyManagerFactory();
}
@Override
protected KeyStore createKeyStore() {
createKeyStoreCalled = true;
return super.createKeyStore();
}
@Override
protected void setTrustManager(SslContextBuilder sslContextBuilder,
X509Certificate... trustedX509Certificates) {
setTrustManagerCertCalled = true;
super.setTrustManager(sslContextBuilder, trustedX509Certificates);
}
@Override
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
setTrustManagerFactoryCalled = true;
super.setTrustManager(sslContextBuilder, factory);
}
}
@EnableAutoConfiguration
@SpringBootConfiguration
protected static class Config {