diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java index 6561f3581..e407fa5b1 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpConnectorFactory.java @@ -105,6 +105,14 @@ private static void configureSsl(SslConfiguration sslConfiguration, SslContextBu sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(), sslConfiguration.getKeyConfiguration())); } + + if (sslConfiguration.getEnabledProtocols() != null) { + sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols()); + } + + if (sslConfiguration.getEnabledCipherSuites() != null) { + sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites()); + } } catch (GeneralSecurityException | IOException e) { throw new IllegalStateException(e); @@ -189,6 +197,16 @@ private static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguratio sslContextFactory.setKeyManagerPassword(new String(keyConfiguration.getKeyPassword())); } + if (sslConfiguration.getEnabledProtocols() != null) { + sslContextFactory + .setIncludeProtocols(sslConfiguration.getEnabledProtocols().toArray(new String[0])); + } + + if (sslConfiguration.getEnabledCipherSuites() != null) { + sslContextFactory + .setIncludeCipherSuites(sslConfiguration.getEnabledCipherSuites().toArray(new String[0])); + } + return new org.eclipse.jetty.client.HttpClient(sslContextFactory); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java index 5e7b5a895..2abe6fe6e 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/client/ClientHttpRequestFactoryFactory.java @@ -43,9 +43,6 @@ import javax.net.ssl.X509ExtendedKeyManager; import javax.net.ssl.X509TrustManager; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SslProvider; -import okhttp3.OkHttpClient.Builder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.client.config.RequestConfig; @@ -55,7 +52,6 @@ import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.impl.conn.DefaultSchemePortResolver; import org.apache.http.impl.conn.SystemDefaultRoutePlanner; - import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory; @@ -69,9 +65,13 @@ import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.PemObject; import org.springframework.vault.support.SslConfiguration; +import org.springframework.vault.support.SslConfiguration.KeyConfiguration; import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration; -import static org.springframework.vault.support.SslConfiguration.KeyConfiguration; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import okhttp3.ConnectionSpec; +import okhttp3.OkHttpClient.Builder; /** * Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components, @@ -298,7 +298,21 @@ static ClientHttpRequestFactory usingHttpComponents(ClientOptions options, SslCo if (hasSslConfiguration(sslConfiguration)) { SSLContext sslContext = getSSLContext(sslConfiguration, getTrustManagers(sslConfiguration)); - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext); + + String[] enabledProtocols = null; + + if (sslConfiguration.getEnabledProtocols() != null) { + enabledProtocols = sslConfiguration.getEnabledProtocols().toArray(new String[0]); + } + + String[] enabledCipherSuites = null; + + if (sslConfiguration.getEnabledCipherSuites() != null) { + enabledCipherSuites = sslConfiguration.getEnabledCipherSuites().toArray(new String[0]); + } + + SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, + enabledProtocols, enabledCipherSuites, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); httpClientBuilder.setSSLSocketFactory(sslSocketFactory); httpClientBuilder.setSSLContext(sslContext); } @@ -332,6 +346,8 @@ static ClientHttpRequestFactory usingOkHttp3(ClientOptions options, SslConfigura Builder builder = new Builder(); + ConnectionSpec sslConnectionSpec = ConnectionSpec.MODERN_TLS; + if (hasSslConfiguration(sslConfiguration)) { TrustManager[] trustManagers = getTrustManagers(sslConfiguration); @@ -344,9 +360,24 @@ static ClientHttpRequestFactory usingOkHttp3(ClientOptions options, SslConfigura X509TrustManager trustManager = (X509TrustManager) trustManagers[0]; SSLContext sslContext = getSSLContext(sslConfiguration, trustManagers); + ConnectionSpec.Builder sslConnectionSpecBuilder = new ConnectionSpec.Builder(sslConnectionSpec); + + if (sslConfiguration.getEnabledProtocols() != null) { + sslConnectionSpecBuilder.tlsVersions(sslConfiguration.getEnabledProtocols().toArray(new String[0])); + } + + if (sslConfiguration.getEnabledCipherSuites() != null) { + sslConnectionSpecBuilder + .cipherSuites(sslConfiguration.getEnabledCipherSuites().toArray(new String[0])); + } + + sslConnectionSpec = sslConnectionSpecBuilder.build(); + builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); } + builder.connectionSpecs(Arrays.asList(sslConnectionSpec, ConnectionSpec.CLEARTEXT)); + builder.connectTimeout(options.getConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS) .readTimeout(options.getReadTimeout().toMillis(), TimeUnit.MILLISECONDS); @@ -382,6 +413,14 @@ static ClientHttpRequestFactory usingNetty(ClientOptions options, SslConfigurati sslConfiguration.getKeyConfiguration())); } + if (sslConfiguration.getEnabledProtocols() != null) { + sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols()); + } + + if (sslConfiguration.getEnabledCipherSuites() != null) { + sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites()); + } + requestFactory.setSslContext(sslContextBuilder.sslProvider(SslProvider.JDK).build()); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java index 3b46bf3e7..67eccdecf 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java @@ -16,10 +16,11 @@ package org.springframework.vault.config; import java.net.URI; +import java.util.Arrays; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -231,7 +232,12 @@ public SslConfiguration sslConfiguration() { KeyStoreConfiguration trustStoreConfiguration = getKeyStoreConfiguration("vault.ssl.trust-store", "vault.ssl.trust-store-password", "vault.ssl.trust-store-type"); - return new SslConfiguration(keyStoreConfiguration, trustStoreConfiguration); + List enabledProtocols = getList("vault.ssl.enabled-protocols"); + + List enabledCipherSuites = getList("vault.ssl.enabled-cipher-suites"); + + return new SslConfiguration(keyStoreConfiguration, trustStoreConfiguration, enabledProtocols, + enabledCipherSuites); } private KeyStoreConfiguration getKeyStoreConfiguration(String resourceProperty, String passwordProperty, @@ -421,6 +427,16 @@ protected ClientAuthentication kubeAuthentication() { return new KubernetesAuthentication(builder.build(), restOperations()); } + private List getList(String key) { + String val = getEnvironment().getProperty(key); + + if (val == null) { + return null; + } + + return Arrays.asList(val.split(",")); + } + @Nullable private String getProperty(String key) { return getEnvironment().getProperty(key); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java index b1f740968..84f178af4 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/support/SslConfiguration.java @@ -18,7 +18,10 @@ import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.springframework.core.io.AbstractResource; import org.springframework.core.io.Resource; @@ -60,6 +63,10 @@ public class SslConfiguration { private final KeyConfiguration keyConfiguration; + private final List enabledProtocols; + + private final List enabledCipherSuites; + /** * Create a new {@link SslConfiguration} with the default {@link KeyStore} type. * @param keyStore the key store resource, must not be {@literal null}. @@ -96,14 +103,23 @@ public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration, * Create a new {@link SslConfiguration}. * @param keyStoreConfiguration the key store configuration, must not be * {@literal null}. - * @param keyConfiguration the configuration for a specific key in - * {@code keyStoreConfiguration} to use. * @param trustStoreConfiguration the trust store configuration, must not be * {@literal null}. - * @since 2.2 + * @param enabledProtocols the enabled SSL protocols, elements must match protocol + * version strings used by the enabled Java SSL provider. May be {@literal null} to + * indicate the SSL socket factory should use a default list of enabled protocol + * versions. + * @param enabledCipherSuites the enabled SSL cipher suites, elements must match + * cipher suite strings used by the enabled Java SSL provider. May be {@literal null} + * to indicate the SSL socket factory should use a default list of enabled cipher + * suites. + * @since 2.4 + * @see sun.security.ssl.ProtocolVersion + * @see sun.security.ssl.CipherSuite */ public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration, - KeyStoreConfiguration trustStoreConfiguration) { + KeyStoreConfiguration trustStoreConfiguration, List enabledProtocols, + List enabledCipherSuites) { Assert.notNull(keyStoreConfiguration, "KeyStore configuration must not be null"); Assert.notNull(keyConfiguration, "KeyConfiguration must not be null"); @@ -112,6 +128,50 @@ public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration, KeyConfigur this.keyStoreConfiguration = keyStoreConfiguration; this.keyConfiguration = keyConfiguration; this.trustStoreConfiguration = trustStoreConfiguration; + this.enabledProtocols = enabledProtocols != null + ? Collections.unmodifiableList(new ArrayList<>(enabledProtocols)) : null; + this.enabledCipherSuites = enabledCipherSuites != null + ? Collections.unmodifiableList(new ArrayList<>(enabledCipherSuites)) : null; + } + + /** + * Create a new {@link SslConfiguration}. + * @param keyStoreConfiguration the key store configuration, must not be + * {@literal null}. + * @param keyConfiguration the configuration for a specific key in + * {@code keyStoreConfiguration} to use. + * @param trustStoreConfiguration the trust store configuration, must not be + * {@literal null}. + * @since 2.2 + */ + public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration, + KeyStoreConfiguration trustStoreConfiguration) { + this(keyStoreConfiguration, keyConfiguration, trustStoreConfiguration, null, null); + } + + /** + * Create a new {@link SslConfiguration}. + * @param keyStoreConfiguration the key store configuration, must not be + * {@literal null}. + * @param trustStoreConfiguration the trust store configuration, must not be + * {@literal null}. + * @param enabledProtocols the enabled SSL protocols, elements must match protocol + * version strings used by the enabled Java SSL provider. May be {@literal null} to + * indicate the SSL socket factory should use a default list of enabled protocol + * versions. + * @param enabledCipherSuites the enabled SSL cipher suites, elements must match + * cipher suite strings used by the enabled Java SSL provider. May be {@literal null} + * to indicate the SSL socket factory should use a default list of enabled cipher + * suites. + * @since 2.4 + * @see sun.security.ssl.ProtocolVersion + * @see sun.security.ssl.CipherSuite + */ + public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration, KeyStoreConfiguration trustStoreConfiguration, + List enabledProtocols, List enabledCipherSuites) { + + this(keyStoreConfiguration, KeyConfiguration.unconfigured(), trustStoreConfiguration, enabledProtocols, + enabledCipherSuites); } /** @@ -299,6 +359,54 @@ public static SslConfiguration unconfigured() { return new SslConfiguration(KeyStoreConfiguration.unconfigured(), KeyStoreConfiguration.unconfigured()); } + /** + * The list of SSL protocol versions that must be enabled. A value of {@literal null} + * indicates that the SSL socket factory should use a default list of enabled protocol + * versions. + * @return the list of enabled SSL protocol versions. + * @since 2.4 + */ + public List getEnabledProtocols() { + return this.enabledProtocols; + } + + /** + * Create a new {@link SslConfiguration} with the enabled protocol versions applied + * retaining the other configuration from this instance. + * @param enabledProtocols may be {@literal null}. + * @return a new {@link SslConfiguration} with the enabled protocol versions applied. + * @since 2.4 + * @see sun.security.ssl.ProtocolVersion + */ + public SslConfiguration withEnabledProtocols(List enabledProtocols) { + return new SslConfiguration(this.keyStoreConfiguration, this.keyConfiguration, this.trustStoreConfiguration, + enabledProtocols, this.enabledCipherSuites); + } + + /** + * The list of SSL cipher suites that must be enabled. A value of {@literal null} + * indicates that the SSL socket factory should use a default list of enabled cipher + * suites. + * @return the list of enabled SSL cipher suites. + * @since 2.4 + */ + public List getEnabledCipherSuites() { + return this.enabledCipherSuites; + } + + /** + * Create a new {@link SslConfiguration} with the enabled cipher suites applied + * retaining the other configuration from this instance. + * @param enabledCipherSuites may be {@literal null}. + * @return a new {@link SslConfiguration} with the enabled cipher suites applied. + * @since 2.4 + * @see sun.security.ssl.CipherSuite + */ + public SslConfiguration withEnabledCipherSuites(List enabledCipherSuites) { + return new SslConfiguration(this.keyStoreConfiguration, this.keyConfiguration, this.trustStoreConfiguration, + this.enabledProtocols, enabledCipherSuites); + } + /** * @return the {@link java.security.KeyStore key store} resource or {@literal null} if * not configured. diff --git a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java index 97ac76ab6..c58d149a1 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpConnectorFactoryIntegrationTests.java @@ -27,6 +27,9 @@ import static org.springframework.vault.client.ClientHttpConnectorFactory.JettyClient; import static org.springframework.vault.client.ClientHttpConnectorFactory.ReactorNetty; +import java.util.ArrayList; +import java.util.List; + /** * Integration tests for {@link ClientHttpConnectorFactory}. * @@ -49,6 +52,39 @@ void reactorNettyClientShouldWork() { assertThat(response).isNotNull().contains("initialized"); } + @Test + void reactorNettyClientWithExplicitEnabledCipherSuitesShouldWork() { + + List enabledCipherSuites = new ArrayList(); + enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + + ClientHttpConnector factory = ReactorNetty.usingReactorNetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites)); + + WebClient webClient = WebClient.builder().clientConnector(factory).build(); + + String response = request(webClient); + + assertThat(response).isNotNull().contains("initialized"); + } + + @Test + void reactorNettyClientWithExplicitEnabledProtocolsShouldWork() { + + List enabledProtocols = new ArrayList(); + enabledProtocols.add("TLSv1.2"); + + ClientHttpConnector factory = ReactorNetty.usingReactorNetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols)); + + WebClient webClient = WebClient.builder().clientConnector(factory).build(); + + String response = request(webClient); + + assertThat(response).isNotNull().contains("initialized"); + } + @Test void jettyClientShouldWork() { @@ -61,6 +97,39 @@ void jettyClientShouldWork() { assertThat(response).isNotNull().contains("initialized"); } + @Test + void jettyClientWithExplicitEnabledCipherSuitesShouldWork() { + + List enabledCipherSuites = new ArrayList(); + enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + + ClientHttpConnector factory = JettyClient.usingJetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites)); + + WebClient webClient = WebClient.builder().clientConnector(factory).build(); + + String response = request(webClient); + + assertThat(response).isNotNull().contains("initialized"); + } + + @Test + void jettyClientWithExplicitEnabledProtocolsShouldWork() { + + List enabledProtocols = new ArrayList(); + enabledProtocols.add("TLSv1.2"); + + ClientHttpConnector factory = JettyClient.usingJetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols)); + + WebClient webClient = WebClient.builder().clientConnector(factory).build(); + + String response = request(webClient); + + assertThat(response).isNotNull().contains("initialized"); + } + private String request(WebClient webClient) { // Uninitialized and sealed can cause status 500 diff --git a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java index 4a0fd3e4b..1af062e93 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/client/ClientHttpRequestFactoryFactoryIntegrationTests.java @@ -15,10 +15,13 @@ */ package org.springframework.vault.client; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import java.io.File; +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.Test; - import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.FileSystemResource; @@ -27,17 +30,17 @@ import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.vault.client.ClientHttpRequestFactoryFactory.HttpComponents; import org.springframework.vault.client.ClientHttpRequestFactoryFactory.Netty; +import org.springframework.vault.client.ClientHttpRequestFactoryFactory.OkHttp3; import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; import org.springframework.vault.util.Settings; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; - /** * Integration tests for {@link ClientHttpRequestFactory}. * @@ -80,6 +83,43 @@ void httpComponentsClientUsingPemShouldWork() throws Exception { ((DisposableBean) factory).destroy(); } + @Test + void httpComponentsClientWithExplicitEnabledCipherSuitesShouldWork() throws Exception { + + List enabledCipherSuites = new ArrayList(); + enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + + ClientHttpRequestFactory factory = HttpComponents.usingHttpComponents(new ClientOptions(), + Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites)); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + void httpComponentsClientWithExplicitEnabledProtocolsShouldWork() throws Exception { + + List enabledProtocols = new ArrayList(); + enabledProtocols.add("TLSv1.2"); + + ClientHttpRequestFactory factory = HttpComponents.usingHttpComponents(new ClientOptions(), + Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols)); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(HttpComponentsClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + @Test void nettyClientWithoutSslConfigShouldWork() throws Exception { @@ -103,6 +143,96 @@ void nettyClientShouldWork() throws Exception { ((DisposableBean) factory).destroy(); } + @Test + void nettyClientWithExplicitEnabledCipherSuitesShouldWork() throws Exception { + + List enabledCipherSuites = new ArrayList(); + enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + + ClientHttpRequestFactory factory = Netty.usingNetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites)); + ((InitializingBean) factory).afterPropertiesSet(); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(Netty4ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + void nettyClientWithExplicitEnabledProtocolsShouldWork() throws Exception { + + List enabledProtocols = new ArrayList(); + enabledProtocols.add("TLSv1.2"); + + ClientHttpRequestFactory factory = Netty.usingNetty(new ClientOptions(), + Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols)); + ((InitializingBean) factory).afterPropertiesSet(); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(Netty4ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + void okHttp3ClientShouldWork() throws Exception { + + ClientHttpRequestFactory factory = OkHttp3.usingOkHttp3(new ClientOptions(), Settings.createSslConfiguration()); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + void okHttp3ClientWithExplicitCipherSuitesShouldWork() throws Exception { + + List enabledCipherSuites = new ArrayList(); + enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + + ClientHttpRequestFactory factory = OkHttp3.usingOkHttp3(new ClientOptions(), + Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites)); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + + @Test + void okHttp3ClientWithExplicitProtocolsShouldWork() throws Exception { + + List enabledProtocols = new ArrayList(); + enabledProtocols.add("TLSv1.2"); + + ClientHttpRequestFactory factory = OkHttp3.usingOkHttp3(new ClientOptions(), + Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols)); + RestTemplate template = new RestTemplate(factory); + + String response = request(template); + + assertThat(factory).isInstanceOf(OkHttp3ClientHttpRequestFactory.class); + assertThat(response).isNotNull().contains("initialized"); + + ((DisposableBean) factory).destroy(); + } + private String request(RestTemplate template) { // Uninitialized and sealed can cause status 500 diff --git a/spring-vault-core/src/test/java/org/springframework/vault/config/EnvironmentVaultConfigurationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/config/EnvironmentVaultConfigurationUnitTests.java index 0368d4f13..97468cb76 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/config/EnvironmentVaultConfigurationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/config/EnvironmentVaultConfigurationUnitTests.java @@ -15,12 +15,13 @@ */ package org.springframework.vault.config; +import static org.assertj.core.api.Assertions.assertThat; + import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -34,8 +35,6 @@ import org.springframework.vault.support.SslConfiguration; import org.springframework.vault.support.VaultToken; -import static org.assertj.core.api.Assertions.assertThat; - /** * Unit tests for {@link EnvironmentVaultConfiguration}. * @@ -78,6 +77,9 @@ void shouldConfigureSsl() { Map map = new HashMap(); map.put("vault.ssl.key-store", "classpath:certificate.json"); map.put("vault.ssl.trust-store", "classpath:certificate.json"); + map.put("vault.ssl.enabled-protocols", "TLSv1.2,TLSv1.1"); + map.put("vault.ssl.enabled-cipher-suites", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); MapPropertySource propertySource = new MapPropertySource("shouldConfigureSsl", map); this.configurableEnvironment.getPropertySources().addFirst(propertySource); @@ -90,6 +92,10 @@ void shouldConfigureSsl() { assertThat(sslConfiguration.getTrustStore()).isInstanceOf(ClassPathResource.class); assertThat(sslConfiguration.getTrustStorePassword()).isEqualTo("trust store password"); + assertThat(sslConfiguration.getEnabledProtocols()).containsExactly("TLSv1.2", "TLSv1.1"); + assertThat(sslConfiguration.getEnabledCipherSuites()).containsExactly("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + this.configurableEnvironment.getPropertySources().remove(propertySource.getName()); } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/support/SslConfigurationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/support/SslConfigurationUnitTests.java index c5c59c19c..d7447f984 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/support/SslConfigurationUnitTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/support/SslConfigurationUnitTests.java @@ -15,14 +15,15 @@ */ package org.springframework.vault.support; -import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration; import org.springframework.vault.util.Settings; -import static org.assertj.core.api.Assertions.assertThat; - /** * Unit tests for {@link SslConfiguration}. * @@ -46,6 +47,8 @@ void shouldCreateEmptySslConfiguration() { assertThat(sslConfiguration.getKeyStoreConfiguration().isPresent()).isFalse(); assertThat(sslConfiguration.getTrustStoreConfiguration().isPresent()).isFalse(); + assertThat(sslConfiguration.getEnabledCipherSuites()).isNull(); + assertThat(sslConfiguration.getEnabledProtocols()).isNull(); } @Test @@ -63,6 +66,35 @@ void shouldCreateConfiguration() { assertThat(tsConfig.getKeyStoreConfiguration().isPresent()).isFalse(); } + @Test + void shouldCreateConfigurationWithEnabledCipherSuites() { + + KeyStoreConfiguration keystore = KeyStoreConfiguration.of(new ClassPathResource("certificate.json")); + SslConfiguration tsConfig = SslConfiguration.unconfigured().withTrustStore(keystore) + .withEnabledCipherSuites(Arrays.asList(new String[] { "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" })); + + assertThat(tsConfig.getTrustStoreConfiguration()).isSameAs(keystore); + assertThat(tsConfig.getKeyStoreConfiguration().isPresent()).isFalse(); + assertThat(tsConfig.getEnabledCipherSuites().size()).isEqualTo(2); + assertThat(tsConfig.getEnabledCipherSuites().get(0)).isEqualTo("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"); + assertThat(tsConfig.getEnabledCipherSuites().get(1)).isEqualTo("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + } + + @Test + void shouldCreateConfigurationWithEnabledProtocols() { + + KeyStoreConfiguration keystore = KeyStoreConfiguration.of(new ClassPathResource("certificate.json")); + SslConfiguration tsConfig = SslConfiguration.unconfigured().withTrustStore(keystore) + .withEnabledProtocols(Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1" })); + + assertThat(tsConfig.getTrustStoreConfiguration()).isSameAs(keystore); + assertThat(tsConfig.getKeyStoreConfiguration().isPresent()).isFalse(); + assertThat(tsConfig.getEnabledProtocols().size()).isEqualTo(2); + assertThat(tsConfig.getEnabledProtocols().get(0)).isEqualTo("TLSv1.2"); + assertThat(tsConfig.getEnabledProtocols().get(1)).isEqualTo("TLSv1.1"); + } + @Test void shouldCreatePemConfiguration() {