Add support for Reactor, Jetty, and JDK ClientHttpRequestFactory implementations.
Closes #901
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* Copyright 2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.vault.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.support.PemObject;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
|
||||
/**
|
||||
* Shared utility class to provide client configuration regardless of the used facade
|
||||
* (synchronous or reactive).
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 4.0
|
||||
*/
|
||||
class ClientConfiguration {
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal") // allow setting via reflection.
|
||||
private static Log logger = LogFactory.getLog(ClientHttpRequestFactoryFactory.class);
|
||||
|
||||
static SSLContext getSSLContext(SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
return getSSLContext(sslConfiguration.getKeyStoreConfiguration(), sslConfiguration.getKeyConfiguration(),
|
||||
getTrustManagers(sslConfiguration));
|
||||
}
|
||||
|
||||
static SSLContext getSSLContext(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration,
|
||||
SslConfiguration.KeyConfiguration keyConfiguration, @Nullable TrustManager[] trustManagers)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyManager[] keyManagers = keyStoreConfiguration.isPresent()
|
||||
? createKeyManagerFactory(keyStoreConfiguration, keyConfiguration).getKeyManagers() : null;
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(keyManagers, trustManagers, null);
|
||||
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
static KeyManagerFactory createKeyManagerFactory(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration,
|
||||
SslConfiguration.KeyConfiguration keyConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore keyStore = getKeyStore(keyStoreConfiguration);
|
||||
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
|
||||
char[] keyPasswordToUse = keyConfiguration.getKeyPassword();
|
||||
|
||||
if (keyPasswordToUse == null) {
|
||||
keyPasswordToUse = keyStoreConfiguration.getStorePassword() == null ? new char[0]
|
||||
: keyStoreConfiguration.getStorePassword();
|
||||
}
|
||||
|
||||
keyManagerFactory.init(keyStore, keyPasswordToUse);
|
||||
|
||||
if (StringUtils.hasText(keyConfiguration.getKeyAlias())) {
|
||||
return new KeySelectingKeyManagerFactory(keyManagerFactory, keyConfiguration);
|
||||
}
|
||||
|
||||
return keyManagerFactory;
|
||||
}
|
||||
|
||||
static KeyStore getKeyStore(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(getKeyStoreType(keyStoreConfiguration));
|
||||
|
||||
loadKeyStore(keyStoreConfiguration, keyStore);
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
return sslConfiguration.getTrustStoreConfiguration().isPresent()
|
||||
? createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()).getTrustManagers() : null;
|
||||
}
|
||||
|
||||
private static String getKeyStoreType(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration) {
|
||||
|
||||
if (StringUtils.hasText(keyStoreConfiguration.getStoreType())
|
||||
&& !SslConfiguration.PEM_KEYSTORE_TYPE.equalsIgnoreCase(keyStoreConfiguration.getStoreType())) {
|
||||
return keyStoreConfiguration.getStoreType();
|
||||
}
|
||||
|
||||
return KeyStore.getDefaultType();
|
||||
}
|
||||
|
||||
static TrustManagerFactory createTrustManagerFactory(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore trustStore = getKeyStore(keyStoreConfiguration);
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(trustStore);
|
||||
|
||||
return trustManagerFactory;
|
||||
}
|
||||
|
||||
private static void loadKeyStore(SslConfiguration.KeyStoreConfiguration keyStoreConfiguration, KeyStore keyStore)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading keystore from %s".formatted(keyStoreConfiguration.getResource()));
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = keyStoreConfiguration.getResource().getInputStream();
|
||||
|
||||
if (SslConfiguration.PEM_KEYSTORE_TYPE.equalsIgnoreCase(keyStoreConfiguration.getStoreType())) {
|
||||
|
||||
keyStore.load(null);
|
||||
loadFromPem(keyStore, inputStream);
|
||||
}
|
||||
else {
|
||||
keyStore.load(inputStream, keyStoreConfiguration.getStorePassword());
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Keystore loaded with %d entries".formatted(keyStore.size()));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadFromPem(KeyStore keyStore, InputStream inputStream) throws IOException, KeyStoreException {
|
||||
|
||||
List<PemObject> pemObjects = PemObject.parse(new String(FileCopyUtils.copyToByteArray(inputStream)));
|
||||
|
||||
for (PemObject pemObject : pemObjects) {
|
||||
if (pemObject.isCertificate()) {
|
||||
X509Certificate cert = pemObject.getCertificate();
|
||||
String alias = cert.getSubjectX500Principal().getName();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Adding certificate with alias %s".formatted(alias));
|
||||
}
|
||||
|
||||
keyStore.setCertificateEntry(alias, cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
|
||||
return sslConfiguration.getTrustStoreConfiguration().isPresent()
|
||||
|| sslConfiguration.getKeyStoreConfiguration().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientHttpConnector} for Reactor Netty.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public static class ReactorNetty {
|
||||
|
||||
public static HttpClient createClient(ClientOptions options, SslConfiguration sslConfiguration) {
|
||||
|
||||
HttpClient client = HttpClient.create();
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
client = client.secure(builder -> {
|
||||
|
||||
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
|
||||
configureSsl(sslConfiguration, sslContextBuilder);
|
||||
|
||||
try {
|
||||
builder.sslContext(sslContextBuilder.build());
|
||||
}
|
||||
catch (SSLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
client = client
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
Math.toIntExact(options.getConnectionTimeout().toMillis()))
|
||||
.proxyWithSystemProperties();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static void configureSsl(SslConfiguration sslConfiguration, SslContextBuilder sslContextBuilder) {
|
||||
|
||||
try {
|
||||
|
||||
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
|
||||
sslContextBuilder
|
||||
.trustManager(createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()));
|
||||
}
|
||||
|
||||
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
|
||||
sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(),
|
||||
sslConfiguration.getKeyConfiguration()));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
|
||||
sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols());
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
|
||||
sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites());
|
||||
}
|
||||
}
|
||||
catch (GeneralSecurityException | IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility methods to create {@link ClientHttpRequestFactory} using the Jetty Client.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public static class JettyClient {
|
||||
|
||||
public static org.eclipse.jetty.client.HttpClient configureClient(
|
||||
org.eclipse.jetty.client.HttpClient httpClient, ClientOptions options) {
|
||||
|
||||
httpClient.setConnectTimeout(options.getConnectionTimeout().toMillis());
|
||||
httpClient.setAddressResolutionTimeout(options.getConnectionTimeout().toMillis());
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguration sslConfiguration)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
|
||||
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
|
||||
KeyStore keyStore = getKeyStore(sslConfiguration.getKeyStoreConfiguration());
|
||||
sslContextFactory.setKeyStore(keyStore);
|
||||
}
|
||||
|
||||
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
|
||||
KeyStore keyStore = getKeyStore(sslConfiguration.getTrustStoreConfiguration());
|
||||
sslContextFactory.setTrustStore(keyStore);
|
||||
}
|
||||
|
||||
SslConfiguration.KeyConfiguration keyConfiguration = sslConfiguration.getKeyConfiguration();
|
||||
|
||||
if (keyConfiguration.getKeyAlias() != null) {
|
||||
sslContextFactory.setCertAlias(keyConfiguration.getKeyAlias());
|
||||
}
|
||||
|
||||
if (keyConfiguration.getKeyPassword() != null) {
|
||||
sslContextFactory.setKeyManagerPassword(new String(keyConfiguration.getKeyPassword()));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
|
||||
sslContextFactory
|
||||
.setIncludeProtocols(sslConfiguration.getEnabledProtocols().toArray(new String[0]));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
|
||||
sslContextFactory
|
||||
.setIncludeCipherSuites(sslConfiguration.getEnabledCipherSuites().toArray(new String[0]));
|
||||
}
|
||||
|
||||
ClientConnector connector = new ClientConnector();
|
||||
connector.setSslContextFactory(sslContextFactory);
|
||||
|
||||
return new org.eclipse.jetty.client.HttpClient(new HttpClientTransportOverHTTP(connector));
|
||||
}
|
||||
|
||||
return new org.eclipse.jetty.client.HttpClient();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ClientHttpRequestFactory} using the JDK's HttpClient.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public static class JdkHttpClient {
|
||||
|
||||
public static java.net.http.HttpClient.Builder getBuilder(ClientOptions options,
|
||||
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder();
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SSLContext sslContext = getSSLContext(sslConfiguration);
|
||||
|
||||
String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty()
|
||||
? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null;
|
||||
|
||||
String[] enabledCipherSuites = !sslConfiguration.getEnabledCipherSuites().isEmpty()
|
||||
? sslConfiguration.getEnabledCipherSuites().toArray(new String[0]) : null;
|
||||
|
||||
SSLParameters parameters = new SSLParameters();
|
||||
parameters.setProtocols(enabledProtocols);
|
||||
parameters.setCipherSuites(enabledCipherSuites);
|
||||
|
||||
builder.sslContext(sslContext).sslParameters(parameters);
|
||||
}
|
||||
|
||||
builder.proxy(ProxySelector.getDefault())
|
||||
.followRedirects(java.net.http.HttpClient.Redirect.ALWAYS)
|
||||
.connectTimeout(options.getConnectionTimeout());
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class KeySelectingKeyManagerFactory extends KeyManagerFactory {
|
||||
|
||||
KeySelectingKeyManagerFactory(KeyManagerFactory factory, SslConfiguration.KeyConfiguration keyConfiguration) {
|
||||
super(new KeyManagerFactorySpi() {
|
||||
@Override
|
||||
protected void engineInit(KeyStore keyStore, char[] chars)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
factory.init(keyStore, chars);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
factory.init(managerFactoryParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyManager[] engineGetKeyManagers() {
|
||||
|
||||
KeyManager[] keyManagers = factory.getKeyManagers();
|
||||
|
||||
if (keyManagers.length == 1 && keyManagers[0] instanceof X509ExtendedKeyManager) {
|
||||
|
||||
return new KeyManager[] { new KeySelectingX509KeyManager(
|
||||
(X509ExtendedKeyManager) keyManagers[0], keyConfiguration) };
|
||||
}
|
||||
|
||||
return keyManagers;
|
||||
}
|
||||
}, factory.getProvider(), factory.getAlgorithm());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class KeySelectingX509KeyManager extends X509ExtendedKeyManager {
|
||||
|
||||
private final X509ExtendedKeyManager delegate;
|
||||
|
||||
private final SslConfiguration.KeyConfiguration keyConfiguration;
|
||||
|
||||
KeySelectingX509KeyManager(X509ExtendedKeyManager delegate,
|
||||
SslConfiguration.KeyConfiguration keyConfiguration) {
|
||||
this.delegate = delegate;
|
||||
this.keyConfiguration = keyConfiguration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getClientAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||
return this.keyConfiguration.getKeyAlias();
|
||||
}
|
||||
|
||||
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
|
||||
return this.keyConfiguration.getKeyAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getServerAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
return this.delegate.chooseServerAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return this.delegate.getCertificateChain(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return this.delegate.getPrivateKey(alias);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,13 +18,9 @@ package org.springframework.vault.client;
|
||||
import java.io.IOException;
|
||||
import java.net.ProxySelector;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
|
||||
@@ -33,10 +29,6 @@ import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBu
|
||||
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
|
||||
import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import reactor.netty.http.Http11SslContextSpec;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
@@ -50,8 +42,6 @@ import org.springframework.util.ClassUtils;
|
||||
import org.springframework.vault.support.ClientOptions;
|
||||
import org.springframework.vault.support.SslConfiguration;
|
||||
|
||||
import static org.springframework.vault.client.ClientHttpRequestFactoryFactory.*;
|
||||
|
||||
/**
|
||||
* Factory for {@link ClientHttpConnector} that supports
|
||||
* {@link ReactorClientHttpConnector} and {@link JettyClientHttpConnector}. This factory
|
||||
@@ -85,13 +75,13 @@ public class ClientHttpConnectorFactory {
|
||||
Assert.notNull(sslConfiguration, "SslConfiguration must not be null");
|
||||
|
||||
try {
|
||||
if (reactorNettyPresent) {
|
||||
return ReactorNetty.usingReactorNetty(options, sslConfiguration);
|
||||
}
|
||||
|
||||
if (httpComponentsPresent) {
|
||||
return HttpComponents.usingHttpComponents(options, sslConfiguration);
|
||||
}
|
||||
|
||||
if (reactorNettyPresent) {
|
||||
return ReactorNetty.usingReactorNetty(options, sslConfiguration);
|
||||
}
|
||||
|
||||
if (jettyPresent) {
|
||||
@@ -124,51 +114,7 @@ public class ClientHttpConnectorFactory {
|
||||
}
|
||||
|
||||
public static HttpClient createClient(ClientOptions options, SslConfiguration sslConfiguration) {
|
||||
|
||||
HttpClient client = HttpClient.create();
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
Http11SslContextSpec sslContextSpec = Http11SslContextSpec.forClient()
|
||||
.configure(it -> configureSsl(sslConfiguration, it))
|
||||
.get();
|
||||
|
||||
client = client.secure(builder -> builder.sslContext(sslContextSpec));
|
||||
}
|
||||
|
||||
client = client
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
|
||||
Math.toIntExact(options.getConnectionTimeout().toMillis()))
|
||||
.proxyWithSystemProperties();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static void configureSsl(SslConfiguration sslConfiguration, SslContextBuilder sslContextBuilder) {
|
||||
|
||||
try {
|
||||
|
||||
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
|
||||
sslContextBuilder
|
||||
.trustManager(createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()));
|
||||
}
|
||||
|
||||
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
|
||||
sslContextBuilder.keyManager(createKeyManagerFactory(sslConfiguration.getKeyStoreConfiguration(),
|
||||
sslConfiguration.getKeyConfiguration()));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
|
||||
sslContextBuilder.protocols(sslConfiguration.getEnabledProtocols());
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
|
||||
sslContextBuilder.ciphers(sslConfiguration.getEnabledCipherSuites());
|
||||
}
|
||||
}
|
||||
catch (GeneralSecurityException | IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return ClientConfiguration.ReactorNetty.createClient(options, sslConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -224,9 +170,9 @@ public class ClientHttpConnectorFactory {
|
||||
.create()
|
||||
.setDefaultConnectionConfig(connectionConfig);
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
if (ClientConfiguration.hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SSLContext sslContext = getSSLContext(sslConfiguration);
|
||||
SSLContext sslContext = ClientConfiguration.getSSLContext(sslConfiguration);
|
||||
|
||||
String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty()
|
||||
? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null;
|
||||
@@ -278,59 +224,12 @@ public class ClientHttpConnectorFactory {
|
||||
|
||||
public static org.eclipse.jetty.client.HttpClient configureClient(
|
||||
org.eclipse.jetty.client.HttpClient httpClient, ClientOptions options) {
|
||||
|
||||
httpClient.setConnectTimeout(options.getConnectionTimeout().toMillis());
|
||||
httpClient.setAddressResolutionTimeout(options.getConnectionTimeout().toMillis());
|
||||
|
||||
return httpClient;
|
||||
return ClientConfiguration.JettyClient.configureClient(httpClient, options);
|
||||
}
|
||||
|
||||
public static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguration sslConfiguration)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||
|
||||
if (sslConfiguration.getKeyStoreConfiguration().isPresent()) {
|
||||
KeyStore keyStore = ClientHttpRequestFactoryFactory
|
||||
.getKeyStore(sslConfiguration.getKeyStoreConfiguration());
|
||||
sslContextFactory.setKeyStore(keyStore);
|
||||
}
|
||||
|
||||
if (sslConfiguration.getTrustStoreConfiguration().isPresent()) {
|
||||
KeyStore keyStore = ClientHttpRequestFactoryFactory
|
||||
.getKeyStore(sslConfiguration.getTrustStoreConfiguration());
|
||||
sslContextFactory.setTrustStore(keyStore);
|
||||
}
|
||||
|
||||
SslConfiguration.KeyConfiguration keyConfiguration = sslConfiguration.getKeyConfiguration();
|
||||
|
||||
if (keyConfiguration.getKeyAlias() != null) {
|
||||
sslContextFactory.setCertAlias(keyConfiguration.getKeyAlias());
|
||||
}
|
||||
|
||||
if (keyConfiguration.getKeyPassword() != null) {
|
||||
sslContextFactory.setKeyManagerPassword(new String(keyConfiguration.getKeyPassword()));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledProtocols().isEmpty()) {
|
||||
sslContextFactory
|
||||
.setIncludeProtocols(sslConfiguration.getEnabledProtocols().toArray(new String[0]));
|
||||
}
|
||||
|
||||
if (!sslConfiguration.getEnabledCipherSuites().isEmpty()) {
|
||||
sslContextFactory
|
||||
.setIncludeCipherSuites(sslConfiguration.getEnabledCipherSuites().toArray(new String[0]));
|
||||
}
|
||||
|
||||
ClientConnector connector = new ClientConnector();
|
||||
connector.setSslContextFactory(sslContextFactory);
|
||||
|
||||
return new org.eclipse.jetty.client.HttpClient(new HttpClientTransportOverHTTP(connector));
|
||||
}
|
||||
|
||||
return new org.eclipse.jetty.client.HttpClient();
|
||||
return ClientConfiguration.JettyClient.getHttpClient(sslConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -360,30 +259,7 @@ public class ClientHttpConnectorFactory {
|
||||
|
||||
public static java.net.http.HttpClient.Builder getBuilder(ClientOptions options,
|
||||
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder();
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SSLContext sslContext = getSSLContext(sslConfiguration);
|
||||
|
||||
String[] enabledProtocols = !sslConfiguration.getEnabledProtocols().isEmpty()
|
||||
? sslConfiguration.getEnabledProtocols().toArray(new String[0]) : null;
|
||||
|
||||
String[] enabledCipherSuites = !sslConfiguration.getEnabledCipherSuites().isEmpty()
|
||||
? sslConfiguration.getEnabledCipherSuites().toArray(new String[0]) : null;
|
||||
|
||||
SSLParameters parameters = new SSLParameters();
|
||||
parameters.setProtocols(enabledProtocols);
|
||||
parameters.setCipherSuites(enabledCipherSuites);
|
||||
|
||||
builder.sslContext(sslContext).sslParameters(parameters);
|
||||
}
|
||||
|
||||
builder.proxy(ProxySelector.getDefault())
|
||||
.followRedirects(java.net.http.HttpClient.Redirect.ALWAYS)
|
||||
.connectTimeout(options.getConnectionTimeout());
|
||||
return builder;
|
||||
return ClientConfiguration.JdkHttpClient.getBuilder(options, sslConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,29 +27,11 @@ import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.KeyManagerFactorySpi;
|
||||
import javax.net.ssl.ManagerFactoryParameters;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -67,17 +49,19 @@ import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.http.client.JdkClientHttpRequestFactory;
|
||||
import org.springframework.http.client.JettyClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ReactorClientHttpRequestFactory;
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector;
|
||||
import org.springframework.http.client.reactive.JettyClientHttpConnector;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
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.client.ClientConfiguration.*;
|
||||
|
||||
/**
|
||||
* Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components,
|
||||
@@ -95,10 +79,16 @@ public class ClientHttpRequestFactoryFactory {
|
||||
@SuppressWarnings("FieldMayBeFinal") // allow setting via reflection.
|
||||
private static Log logger = LogFactory.getLog(ClientHttpRequestFactoryFactory.class);
|
||||
|
||||
private static final boolean reactorNettyPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient",
|
||||
ClientHttpConnectorFactory.class.getClassLoader());
|
||||
|
||||
private static final boolean httpComponentsPresent = ClassUtils.isPresent(
|
||||
"org.apache.hc.client5.http.impl.classic.HttpClientBuilder",
|
||||
ClientHttpRequestFactoryFactory.class.getClassLoader());
|
||||
|
||||
private static final boolean jettyPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient",
|
||||
ClientHttpConnectorFactory.class.getClassLoader());
|
||||
|
||||
/**
|
||||
* Create a {@link ClientHttpRequestFactory} for the given {@link ClientOptions} and
|
||||
* {@link SslConfiguration}.
|
||||
@@ -117,151 +107,42 @@ public class ClientHttpRequestFactoryFactory {
|
||||
if (httpComponentsPresent) {
|
||||
return HttpComponents.usingHttpComponents(options, sslConfiguration);
|
||||
}
|
||||
|
||||
if (reactorNettyPresent) {
|
||||
return ReactorNetty.usingReactorNetty(options, sslConfiguration);
|
||||
}
|
||||
|
||||
if (jettyPresent) {
|
||||
return JettyClient.usingJetty(options, sslConfiguration);
|
||||
}
|
||||
|
||||
return JdkHttpClient.usingJdkHttpClient(options, sslConfiguration);
|
||||
}
|
||||
catch (GeneralSecurityException | IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
logger.warn("VaultProperties has SSL configured but the SSL configuration "
|
||||
+ "must be applied outside the Vault Client to use the JDK HTTP client");
|
||||
/**
|
||||
* {@link ClientHttpConnector} for Reactor Netty.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 4.0
|
||||
*/
|
||||
public static class ReactorNetty {
|
||||
|
||||
/**
|
||||
* Create a {@link ReactorClientHttpRequestFactory} using Reactor Netty.
|
||||
* @param options must not be {@literal null}
|
||||
* @param sslConfiguration must not be {@literal null}
|
||||
* @return a new and configured {@link ReactorClientHttpRequestFactory} instance.
|
||||
*/
|
||||
public static ReactorClientHttpRequestFactory usingReactorNetty(ClientOptions options,
|
||||
SslConfiguration sslConfiguration) {
|
||||
return new ReactorClientHttpRequestFactory(
|
||||
ClientConfiguration.ReactorNetty.createClient(options, sslConfiguration));
|
||||
}
|
||||
|
||||
return SimpleClient.usingSimpleClientHttpRequest(options);
|
||||
}
|
||||
|
||||
static SSLContext getSSLContext(SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
return getSSLContext(sslConfiguration.getKeyStoreConfiguration(), sslConfiguration.getKeyConfiguration(),
|
||||
getTrustManagers(sslConfiguration));
|
||||
}
|
||||
|
||||
static SSLContext getSSLContext(KeyStoreConfiguration keyStoreConfiguration, KeyConfiguration keyConfiguration,
|
||||
@Nullable TrustManager[] trustManagers) throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyManager[] keyManagers = keyStoreConfiguration.isPresent()
|
||||
? createKeyManagerFactory(keyStoreConfiguration, keyConfiguration).getKeyManagers() : null;
|
||||
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(keyManagers, trustManagers, null);
|
||||
|
||||
return sslContext;
|
||||
}
|
||||
|
||||
static KeyManagerFactory createKeyManagerFactory(KeyStoreConfiguration keyStoreConfiguration,
|
||||
KeyConfiguration keyConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore keyStore = getKeyStore(keyStoreConfiguration);
|
||||
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
|
||||
char[] keyPasswordToUse = keyConfiguration.getKeyPassword();
|
||||
|
||||
if (keyPasswordToUse == null) {
|
||||
keyPasswordToUse = keyStoreConfiguration.getStorePassword() == null ? new char[0]
|
||||
: keyStoreConfiguration.getStorePassword();
|
||||
}
|
||||
|
||||
keyManagerFactory.init(keyStore, keyPasswordToUse);
|
||||
|
||||
if (StringUtils.hasText(keyConfiguration.getKeyAlias())) {
|
||||
return new KeySelectingKeyManagerFactory(keyManagerFactory, keyConfiguration);
|
||||
}
|
||||
|
||||
return keyManagerFactory;
|
||||
}
|
||||
|
||||
static KeyStore getKeyStore(KeyStoreConfiguration keyStoreConfiguration)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(getKeyStoreType(keyStoreConfiguration));
|
||||
|
||||
loadKeyStore(keyStoreConfiguration, keyStore);
|
||||
return keyStore;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static TrustManager[] getTrustManagers(SslConfiguration sslConfiguration)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
return sslConfiguration.getTrustStoreConfiguration().isPresent()
|
||||
? createTrustManagerFactory(sslConfiguration.getTrustStoreConfiguration()).getTrustManagers() : null;
|
||||
}
|
||||
|
||||
private static String getKeyStoreType(KeyStoreConfiguration keyStoreConfiguration) {
|
||||
|
||||
if (StringUtils.hasText(keyStoreConfiguration.getStoreType())
|
||||
&& !SslConfiguration.PEM_KEYSTORE_TYPE.equalsIgnoreCase(keyStoreConfiguration.getStoreType())) {
|
||||
return keyStoreConfiguration.getStoreType();
|
||||
}
|
||||
|
||||
return KeyStore.getDefaultType();
|
||||
}
|
||||
|
||||
static TrustManagerFactory createTrustManagerFactory(KeyStoreConfiguration keyStoreConfiguration)
|
||||
throws GeneralSecurityException, IOException {
|
||||
|
||||
KeyStore trustStore = getKeyStore(keyStoreConfiguration);
|
||||
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init(trustStore);
|
||||
|
||||
return trustManagerFactory;
|
||||
}
|
||||
|
||||
private static void loadKeyStore(KeyStoreConfiguration keyStoreConfiguration, KeyStore keyStore)
|
||||
throws IOException, GeneralSecurityException {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Loading keystore from %s".formatted(keyStoreConfiguration.getResource()));
|
||||
}
|
||||
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
inputStream = keyStoreConfiguration.getResource().getInputStream();
|
||||
|
||||
if (SslConfiguration.PEM_KEYSTORE_TYPE.equalsIgnoreCase(keyStoreConfiguration.getStoreType())) {
|
||||
|
||||
keyStore.load(null);
|
||||
loadFromPem(keyStore, inputStream);
|
||||
}
|
||||
else {
|
||||
keyStore.load(inputStream, keyStoreConfiguration.getStorePassword());
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Keystore loaded with %d entries".formatted(keyStore.size()));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadFromPem(KeyStore keyStore, InputStream inputStream) throws IOException, KeyStoreException {
|
||||
|
||||
List<PemObject> pemObjects = PemObject.parse(new String(FileCopyUtils.copyToByteArray(inputStream)));
|
||||
|
||||
for (PemObject pemObject : pemObjects) {
|
||||
if (pemObject.isCertificate()) {
|
||||
X509Certificate cert = pemObject.getCertificate();
|
||||
String alias = cert.getSubjectX500Principal().getName();
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Adding certificate with alias %s".formatted(alias));
|
||||
}
|
||||
|
||||
keyStore.setCertificateEntry(alias, cert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean hasSslConfiguration(SslConfiguration sslConfiguration) {
|
||||
return sslConfiguration.getTrustStoreConfiguration().isPresent()
|
||||
|| sslConfiguration.getKeyStoreConfiguration().isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,9 +199,9 @@ public class ClientHttpRequestFactoryFactory {
|
||||
.setSoTimeout(readTimeout)
|
||||
.build());
|
||||
|
||||
if (hasSslConfiguration(sslConfiguration)) {
|
||||
if (ClientConfiguration.hasSslConfiguration(sslConfiguration)) {
|
||||
|
||||
SSLContext sslContext = getSSLContext(sslConfiguration);
|
||||
SSLContext sslContext = ClientConfiguration.getSSLContext(sslConfiguration);
|
||||
|
||||
String[] enabledProtocols = null;
|
||||
|
||||
@@ -347,83 +228,66 @@ public class ClientHttpRequestFactoryFactory {
|
||||
|
||||
}
|
||||
|
||||
static class KeySelectingKeyManagerFactory extends KeyManagerFactory {
|
||||
/**
|
||||
* Utility methods to create {@link ClientHttpRequestFactory} using the Jetty Client.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 4.5
|
||||
*/
|
||||
public static class JettyClient {
|
||||
|
||||
KeySelectingKeyManagerFactory(KeyManagerFactory factory, KeyConfiguration keyConfiguration) {
|
||||
super(new KeyManagerFactorySpi() {
|
||||
@Override
|
||||
protected void engineInit(KeyStore keyStore, char[] chars)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
||||
factory.init(keyStore, chars);
|
||||
}
|
||||
/**
|
||||
* Create a {@link JettyClientHttpRequestFactory} using Jetty.
|
||||
* @param options must not be {@literal null}
|
||||
* @param sslConfiguration must not be {@literal null}
|
||||
* @return a new and configured {@link JettyClientHttpConnector} instance.
|
||||
* @throws GeneralSecurityException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static JettyClientHttpRequestFactory usingJetty(ClientOptions options, SslConfiguration sslConfiguration)
|
||||
throws GeneralSecurityException, IOException {
|
||||
return new JettyClientHttpRequestFactory(configureClient(getHttpClient(sslConfiguration), options));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineInit(ManagerFactoryParameters managerFactoryParameters)
|
||||
throws InvalidAlgorithmParameterException {
|
||||
factory.init(managerFactoryParameters);
|
||||
}
|
||||
public static org.eclipse.jetty.client.HttpClient configureClient(
|
||||
org.eclipse.jetty.client.HttpClient httpClient, ClientOptions options) {
|
||||
return ClientConfiguration.JettyClient.configureClient(httpClient, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected KeyManager[] engineGetKeyManagers() {
|
||||
|
||||
KeyManager[] keyManagers = factory.getKeyManagers();
|
||||
|
||||
if (keyManagers.length == 1 && keyManagers[0] instanceof X509ExtendedKeyManager) {
|
||||
|
||||
return new KeyManager[] { new KeySelectingX509KeyManager(
|
||||
(X509ExtendedKeyManager) keyManagers[0], keyConfiguration) };
|
||||
}
|
||||
|
||||
return keyManagers;
|
||||
}
|
||||
}, factory.getProvider(), factory.getAlgorithm());
|
||||
public static org.eclipse.jetty.client.HttpClient getHttpClient(SslConfiguration sslConfiguration)
|
||||
throws IOException, GeneralSecurityException {
|
||||
return ClientConfiguration.JettyClient.getHttpClient(sslConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class KeySelectingX509KeyManager extends X509ExtendedKeyManager {
|
||||
/**
|
||||
* {@link ClientHttpRequestFactory} using the JDK's HttpClient.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 4.5
|
||||
*/
|
||||
public static class JdkHttpClient {
|
||||
|
||||
private final X509ExtendedKeyManager delegate;
|
||||
/**
|
||||
* Create a {@link JdkClientHttpRequestFactory} using the JDK's HttpClient.
|
||||
* @param options must not be {@literal null}
|
||||
* @param sslConfiguration must not be {@literal null}
|
||||
* @return a new and configured {@link JdkClientHttpRequestFactory} instance.
|
||||
* @throws GeneralSecurityException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static JdkClientHttpRequestFactory usingJdkHttpClient(ClientOptions options,
|
||||
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
|
||||
private final KeyConfiguration keyConfiguration;
|
||||
java.net.http.HttpClient.Builder builder = getBuilder(options, sslConfiguration);
|
||||
|
||||
KeySelectingX509KeyManager(X509ExtendedKeyManager delegate, KeyConfiguration keyConfiguration) {
|
||||
this.delegate = delegate;
|
||||
this.keyConfiguration = keyConfiguration;
|
||||
return new JdkClientHttpRequestFactory(builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getClientAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
|
||||
return this.keyConfiguration.getKeyAlias();
|
||||
}
|
||||
|
||||
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
|
||||
return this.keyConfiguration.getKeyAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String keyType, Principal[] issuers) {
|
||||
return this.delegate.getServerAliases(keyType, issuers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
|
||||
return this.delegate.chooseServerAlias(keyType, issuers, socket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String alias) {
|
||||
return this.delegate.getCertificateChain(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String alias) {
|
||||
return this.delegate.getPrivateKey(alias);
|
||||
public static java.net.http.HttpClient.Builder getBuilder(ClientOptions options,
|
||||
SslConfiguration sslConfiguration) throws GeneralSecurityException, IOException {
|
||||
return ClientConfiguration.JdkHttpClient.getBuilder(options, sslConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,52 @@ class ClientHttpRequestFactoryFactoryIntegrationTests {
|
||||
|
||||
String url = new VaultEndpoint().createUriString("sys/health");
|
||||
|
||||
@Test
|
||||
void reactorNettyClientShouldWork() {
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.ReactorNetty
|
||||
.usingReactorNetty(new ClientOptions(), Settings.createSslConfiguration());
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reactorNettyClientWithExplicitEnabledCipherSuitesShouldWork() {
|
||||
|
||||
List<String> enabledCipherSuites = new ArrayList<String>();
|
||||
enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
|
||||
enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.ReactorNetty.usingReactorNetty(
|
||||
new ClientOptions(), Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites));
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reactorNettyClientWithExplicitEnabledProtocolsShouldWork() {
|
||||
|
||||
List<String> enabledProtocols = new ArrayList<String>();
|
||||
enabledProtocols.add("TLSv1.2");
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.ReactorNetty.usingReactorNetty(
|
||||
new ClientOptions(), Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols));
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpComponentsClientShouldWork() throws Exception {
|
||||
|
||||
@@ -117,6 +163,65 @@ class ClientHttpRequestFactoryFactoryIntegrationTests {
|
||||
((DisposableBean) factory).destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
void jettyClientShouldWork() throws Exception {
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.JettyClient.usingJetty(new ClientOptions(),
|
||||
Settings.createSslConfiguration());
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jettyClientWithExplicitEnabledCipherSuitesShouldWork() throws Exception {
|
||||
|
||||
List<String> enabledCipherSuites = new ArrayList<String>();
|
||||
enabledCipherSuites.add("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
|
||||
enabledCipherSuites.add("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.JettyClient.usingJetty(new ClientOptions(),
|
||||
Settings.createSslConfiguration().withEnabledCipherSuites(enabledCipherSuites));
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jettyClientWithExplicitEnabledProtocolsShouldWork() throws Exception {
|
||||
|
||||
List<String> enabledProtocols = new ArrayList<String>();
|
||||
enabledProtocols.add("TLSv1.2");
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.JettyClient.usingJetty(new ClientOptions(),
|
||||
Settings.createSslConfiguration().withEnabledProtocols(enabledProtocols));
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jdkHttpClientShouldWork() throws Exception {
|
||||
|
||||
ClientHttpRequestFactory factory = ClientHttpRequestFactoryFactory.JdkHttpClient
|
||||
.usingJdkHttpClient(new ClientOptions(), Settings.createSslConfiguration());
|
||||
|
||||
RestTemplate template = new RestTemplate(factory);
|
||||
|
||||
String response = request(template);
|
||||
|
||||
assertThat(response).isNotNull().contains("initialized");
|
||||
}
|
||||
|
||||
private String request(RestTemplate template) {
|
||||
|
||||
// Uninitialized and sealed can cause status 500
|
||||
|
||||
@@ -76,6 +76,8 @@ class ReactiveVaultKeyValueMetadataTemplateIntegrationTests
|
||||
@Test
|
||||
void shouldReadMetadataForANewKVEntry() {
|
||||
|
||||
Version version = prepare().getVersion();
|
||||
|
||||
vaultKeyValueMetadataOperations.get(SECRET_NAME).as(StepVerifier::create).assertNext(metadataResponse -> {
|
||||
assertThat(metadataResponse.getMaxVersions()).isEqualTo(0);
|
||||
assertThat(metadataResponse.getCurrentVersion()).isEqualTo(1);
|
||||
@@ -86,7 +88,7 @@ class ReactiveVaultKeyValueMetadataTemplateIntegrationTests
|
||||
|
||||
var version1 = metadataResponse.getVersions().get(0);
|
||||
|
||||
if (prepare().getVersion().isGreaterThanOrEqualTo(Version.parse("1.2.0"))) {
|
||||
if (version.isGreaterThanOrEqualTo(Version.parse("1.2.0"))) {
|
||||
|
||||
assertThat(metadataResponse.getDeleteVersionAfter()).isEqualTo(Duration.ZERO);
|
||||
|
||||
|
||||
@@ -8,28 +8,25 @@ that is scoped only to Spring Vault's client components.
|
||||
|
||||
Spring Vault supports following HTTP imperative clients:
|
||||
|
||||
* Java's builtin `HttpURLConnection` (default client if no other is available)
|
||||
* Java's builtin `HttpClient` (default client if no other is available)
|
||||
* Apache Http Components
|
||||
* Reactor Netty
|
||||
* Jetty
|
||||
|
||||
Spring Vault's reactive integration supports the following reactive HTTP clients:
|
||||
|
||||
* Java's builtin reactive `HttpClient` (default client if no other is available)
|
||||
* Reactor Netty
|
||||
* Apache Http Components
|
||||
* Reactor Netty
|
||||
* Jetty
|
||||
|
||||
Using a specific client requires the according dependency to be available on the classpath
|
||||
so Spring Vault can use the available client for communicating with Vault.
|
||||
|
||||
== Java's builtin `HttpURLConnection`
|
||||
== Java's builtin `HttpClient`
|
||||
|
||||
Java's builtin `HttpURLConnection` is available out-of-the-box without additional
|
||||
configuration. Using `HttpURLConnection` comes with a limitation regarding SSL configuration.
|
||||
Spring Vault won't apply <<vault.client-ssl,customized SSL configuration>> as it would
|
||||
require a deep reconfiguration of the JVM. This configuration would affect all
|
||||
components relying on the default SSL context. Configuring SSL settings using
|
||||
`HttpURLConnection` requires you providing these settings as System Properties. See
|
||||
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#InstallationAndCustomization[Customizing JSSE] for further details.
|
||||
Java's builtin `HttpClient` is available out-of-the-box since Java 11 without additional
|
||||
dependencies.
|
||||
|
||||
== External Clients
|
||||
You can use external clients to access Vault's API. Simply add one of the following
|
||||
|
||||
Reference in New Issue
Block a user