Allow SSL configuration with the Elasticsearch CA fingerprint.

Original Pull Request #2540
Closes #2539
This commit is contained in:
Peter-Josef Meisch 2023-04-29 18:43:50 +02:00 committed by GitHub
parent 17ecce4362
commit 406961c13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 29 deletions

View File

@ -240,7 +240,7 @@ ClientConfiguration clientConfiguration = ClientConfiguration.builder()
<.> Define default headers, if they need to be customized
<.> Use the builder to provide cluster addresses, set default `HttpHeaders` or enable SSL.
<.> Optionally enable SSL.
<.> Optionally enable SSL. There exist overloads of this function that can take a `SSLContext` or as an alternative the fingerprint of the certificate as it is output by Elasticsearch 8 on startup.
<.> Optionally set a proxy.
<.> Optionally set a path prefix, mostly used when different clusters a behind some reverse proxy.
<.> Set the connection timeout.

View File

@ -5,6 +5,7 @@
== New in Spring Data Elasticsearch 5.1
* Upgrade to Elasticsearch 8.7.0
* Allow specification of the TLS certificate when connecting to an Elasticsearch 8 cluster
[[new-features.5-0-0]]
== New in Spring Data Elasticsearch 5.0

View File

@ -20,7 +20,6 @@ import java.net.SocketAddress;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
@ -28,7 +27,6 @@ import javax.net.ssl.SSLContext;
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Configuration interface exposing common client configuration properties for Elasticsearch clients.
@ -122,6 +120,12 @@ public interface ClientConfiguration {
*/
Optional<SSLContext> getSslContext();
/**
* @return the optional SHA-256 fingerprint of the self-signed http_ca.crt certificate output by Elasticsearch at
* startup time.
*/
Optional<String> getCaFingerprint();
/**
* Returns the {@link HostnameVerifier} to use. Can be {@link Optional#empty()} if not configured.
*
@ -250,6 +254,15 @@ public interface ClientConfiguration {
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder usingSsl(SSLContext sslContext, HostnameVerifier hostnameVerifier);
/**
* Connect via https using a SSLContext that is build from the given certificate fingerprint.
*
* @param caFingerprint the SHA-256 fingerprint of the self-signed http_ca.crt certificate output by Elasticsearch
* at startup time.
* @return the {@link TerminalClientConfigurationBuilder}.
*/
TerminalClientConfigurationBuilder usingSsl(String caFingerprint);
}
/**

View File

@ -20,7 +20,6 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.net.ssl.HostnameVerifier;
@ -33,7 +32,6 @@ import org.springframework.data.elasticsearch.client.ClientConfiguration.Termina
import org.springframework.data.elasticsearch.support.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Default builder implementation for {@link ClientConfiguration}.
@ -51,14 +49,15 @@ class ClientConfigurationBuilder
private final List<InetSocketAddress> hosts = new ArrayList<>();
private HttpHeaders headers = new HttpHeaders();
private boolean useSsl;
private @Nullable SSLContext sslContext;
private @Nullable HostnameVerifier hostnameVerifier;
@Nullable private SSLContext sslContext;
@Nullable private String caFingerprint;
@Nullable private HostnameVerifier hostnameVerifier;
private Duration connectTimeout = Duration.ofSeconds(10);
private Duration soTimeout = Duration.ofSeconds(5);
private @Nullable String username;
private @Nullable String password;
private @Nullable String pathPrefix;
private @Nullable String proxy;
@Nullable private String username;
@Nullable private String password;
@Nullable private String pathPrefix;
@Nullable private String proxy;
private Supplier<HttpHeaders> headersSupplier = HttpHeaders::new;
@Deprecated private final HttpClientConfigCallback httpClientConfigurer = httpClientBuilder -> httpClientBuilder;
private final List<ClientConfiguration.ClientConfigurationCallback<?>> clientConfigurers = new ArrayList<>();
@ -138,10 +137,20 @@ class ClientConfigurationBuilder
return this;
}
@Override
public TerminalClientConfigurationBuilder usingSsl(String caFingerprint) {
Assert.notNull(caFingerprint, "caFingerprint must not be null");
this.useSsl = true;
this.caFingerprint = caFingerprint;
return this;
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withDefaultHeaders(org.springframework.http.HttpHeaders)
*/
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.client.ClientConfiguration.TerminalClientConfigurationBuilder#withDefaultHeaders(org.springframework.http.HttpHeaders)
*/
@Override
public TerminalClientConfigurationBuilder withDefaultHeaders(HttpHeaders defaultHeaders) {
@ -228,8 +237,12 @@ class ClientConfigurationBuilder
headers.setBasicAuth(username, password);
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, soTimeout, connectTimeout, pathPrefix,
hostnameVerifier, proxy, httpClientConfigurer, clientConfigurers, headersSupplier);
if (sslContext != null && caFingerprint != null) {
throw new IllegalArgumentException("Either SSLContext or caFingerprint must be set, but not both");
}
return new DefaultClientConfiguration(hosts, headers, useSsl, sslContext, caFingerprint, soTimeout, connectTimeout,
pathPrefix, hostnameVerifier, proxy, httpClientConfigurer, clientConfigurers, headersSupplier);
}
private static InetSocketAddress parse(String hostAndPort) {

View File

@ -42,19 +42,20 @@ class DefaultClientConfiguration implements ClientConfiguration {
private final List<InetSocketAddress> hosts;
private final HttpHeaders headers;
private final boolean useSsl;
private final @Nullable SSLContext sslContext;
@Nullable private final SSLContext sslContext;
@Nullable private final String caFingerprint;
private final Duration soTimeout;
private final Duration connectTimeout;
private final @Nullable String pathPrefix;
private final @Nullable HostnameVerifier hostnameVerifier;
private final @Nullable String proxy;
@Nullable private final String pathPrefix;
@Nullable private final HostnameVerifier hostnameVerifier;
@Nullable private final String proxy;
private final HttpClientConfigCallback httpClientConfigurer;
private final Supplier<HttpHeaders> headersSupplier;
private final List<ClientConfigurationCallback<?>> clientConfigurers;
DefaultClientConfiguration(List<InetSocketAddress> hosts, HttpHeaders headers, boolean useSsl,
@Nullable SSLContext sslContext, Duration soTimeout, Duration connectTimeout, @Nullable String pathPrefix,
@Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
@Nullable SSLContext sslContext, @Nullable String caFingerprint, Duration soTimeout, Duration connectTimeout,
@Nullable String pathPrefix, @Nullable HostnameVerifier hostnameVerifier, @Nullable String proxy,
HttpClientConfigCallback httpClientConfigurer, List<ClientConfigurationCallback<?>> clientConfigurers,
Supplier<HttpHeaders> headersSupplier) {
@ -62,6 +63,7 @@ class DefaultClientConfiguration implements ClientConfiguration {
this.headers = headers;
this.useSsl = useSsl;
this.sslContext = sslContext;
this.caFingerprint = caFingerprint;
this.soTimeout = soTimeout;
this.connectTimeout = connectTimeout;
this.pathPrefix = pathPrefix;
@ -92,6 +94,11 @@ class DefaultClientConfiguration implements ClientConfiguration {
return Optional.ofNullable(this.sslContext);
}
@Override
public Optional<String> getCaFingerprint() {
return Optional.ofNullable(this.caFingerprint);
}
@Override
public Optional<HostnameVerifier> getHostNameVerifier() {
return Optional.ofNullable(this.hostnameVerifier);

View File

@ -19,6 +19,7 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportOptions;
import co.elastic.clients.transport.TransportUtils;
import co.elastic.clients.transport.Version;
import co.elastic.clients.transport.rest_client.RestClientOptions;
import co.elastic.clients.transport.rest_client.RestClientTransport;
@ -34,13 +35,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
@ -197,6 +192,10 @@ public final class ElasticsearchClients {
}
builder.setHttpClientConfigCallback(clientBuilder -> {
if (clientConfiguration.getCaFingerprint().isPresent()) {
clientBuilder
.setSSLContext(TransportUtils.sslContextFromCaFingerprint(clientConfiguration.getCaFingerprint().get()));
}
clientConfiguration.getSslContext().ifPresent(clientBuilder::setSSLContext);
clientConfiguration.getHostNameVerifier().ifPresent(clientBuilder::setSSLHostnameVerifier);
clientBuilder.addInterceptorLast(new CustomHeaderInjector(clientConfiguration.getHeadersSupplier()));