Add SSL bundle support to Elasticsearch auto-configuration
Update Elasticsearch RestClient auto-configuration so that an SSL can be configured via an SSL bundle. Closes gh-35155
This commit is contained in:
parent
e9a85b91a8
commit
423c60acfa
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -133,10 +133,16 @@ public class ElasticsearchProperties {
|
|||
|
||||
private final Sniffer sniffer = new Sniffer();
|
||||
|
||||
private final Ssl ssl = new Ssl();
|
||||
|
||||
public Sniffer getSniffer() {
|
||||
return this.sniffer;
|
||||
}
|
||||
|
||||
public Ssl getSsl() {
|
||||
return this.ssl;
|
||||
}
|
||||
|
||||
public static class Sniffer {
|
||||
|
||||
/**
|
||||
|
@ -167,6 +173,23 @@ public class ElasticsearchProperties {
|
|||
|
||||
}
|
||||
|
||||
public static class Ssl {
|
||||
|
||||
/**
|
||||
* SSL bundle name.
|
||||
*/
|
||||
private String bundle;
|
||||
|
||||
public String getBundle() {
|
||||
return this.bundle;
|
||||
}
|
||||
|
||||
public void setBundle(String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2023 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.
|
||||
|
@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientBuilderConfiguration;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientConfiguration;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.RestClientSnifferConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
|
@ -34,7 +35,7 @@ import org.springframework.context.annotation.Import;
|
|||
* @author Stephane Nicoll
|
||||
* @since 2.1.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@AutoConfiguration(after = SslAutoConfiguration.class)
|
||||
@ConditionalOnClass(RestClientBuilder.class)
|
||||
@EnableConfigurationProperties(ElasticsearchProperties.class)
|
||||
@Import({ RestClientBuilderConfiguration.class, RestClientConfiguration.class, RestClientSnifferConfiguration.class })
|
||||
|
|
|
@ -19,8 +19,12 @@ package org.springframework.boot.autoconfigure.elasticsearch;
|
|||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.Credentials;
|
||||
|
@ -29,6 +33,7 @@ import org.apache.http.client.config.RequestConfig;
|
|||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.impl.nio.reactor.IOReactorConfig;
|
||||
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
import org.elasticsearch.client.RestClientBuilder;
|
||||
import org.elasticsearch.client.sniff.Sniffer;
|
||||
|
@ -41,6 +46,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandi
|
|||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundle;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
import org.springframework.boot.ssl.SslOptions;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -81,13 +89,17 @@ class ElasticsearchRestClientConfigurations {
|
|||
|
||||
@Bean
|
||||
RestClientBuilder elasticsearchRestClientBuilder(ElasticsearchConnectionDetails connectionDetails,
|
||||
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers) {
|
||||
ObjectProvider<RestClientBuilderCustomizer> builderCustomizers, ObjectProvider<SslBundles> sslBundles) {
|
||||
RestClientBuilder builder = RestClient.builder(connectionDetails.getNodes()
|
||||
.stream()
|
||||
.map((node) -> new HttpHost(node.hostname(), node.port(), node.protocol().getScheme()))
|
||||
.toArray(HttpHost[]::new));
|
||||
builder.setHttpClientConfigCallback((httpClientBuilder) -> {
|
||||
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(httpClientBuilder));
|
||||
String sslBundleName = this.properties.getRestclient().getSsl().getBundle();
|
||||
if (StringUtils.hasText(sslBundleName)) {
|
||||
configureSsl(httpClientBuilder, sslBundles.getObject().getBundle(sslBundleName));
|
||||
}
|
||||
return httpClientBuilder;
|
||||
});
|
||||
builder.setRequestConfigCallback((requestConfigBuilder) -> {
|
||||
|
@ -102,6 +114,19 @@ class ElasticsearchRestClientConfigurations {
|
|||
return builder;
|
||||
}
|
||||
|
||||
private void configureSsl(HttpAsyncClientBuilder httpClientBuilder, SslBundle sslBundle) {
|
||||
SSLContext sslcontext = sslBundle.createSslContext();
|
||||
SslOptions sslOptions = sslBundle.getOptions();
|
||||
String[] enabledProtocols = toArray(sslOptions.getEnabledProtocols());
|
||||
String[] ciphers = toArray(sslOptions.getCiphers());
|
||||
httpClientBuilder.setSSLStrategy(
|
||||
new SSLIOSessionStrategy(sslcontext, enabledProtocols, ciphers, (HostnameVerifier) null));
|
||||
}
|
||||
|
||||
private static String[] toArray(Set<String> set) {
|
||||
return (set != null) ? set.toArray(String[]::new) : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.autoconfigure.elasticsearch;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
|
@ -24,7 +25,9 @@ import org.apache.http.auth.AuthScope;
|
|||
import org.apache.http.auth.Credentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.config.Registry;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
|
||||
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.elasticsearch.client.Node;
|
||||
import org.elasticsearch.client.RestClient;
|
||||
|
@ -35,10 +38,12 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol;
|
||||
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientConfigurations.PropertiesElasticsearchConnectionDetails;
|
||||
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
|
@ -57,8 +62,8 @@ import static org.mockito.Mockito.mock;
|
|||
*/
|
||||
class ElasticsearchRestClientAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class));
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||
AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, SslAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void configureShouldCreateRestClientBuilderAndRestClient() {
|
||||
|
@ -282,6 +287,32 @@ class ElasticsearchRestClientAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void configureWithSslBundle() {
|
||||
List<String> properties = new ArrayList<>();
|
||||
properties.add("spring.elasticsearch.restclient.ssl.bundle=mybundle");
|
||||
properties.add("spring.ssl.bundle.jks.mybundle.truststore.location=classpath:test.jks");
|
||||
properties.add("spring.ssl.bundle.jks.mybundle.options.ciphers=DESede");
|
||||
properties.add("spring.ssl.bundle.jks.mybundle.options.enabled-protocols=TLSv1.3");
|
||||
this.contextRunner.withPropertyValues(properties.toArray(String[]::new)).run((context) -> {
|
||||
assertThat(context).hasSingleBean(RestClient.class);
|
||||
RestClient restClient = context.getBean(RestClient.class);
|
||||
Object client = ReflectionTestUtils.getField(restClient, "client");
|
||||
Object connmgr = ReflectionTestUtils.getField(client, "connmgr");
|
||||
Registry<SchemeIOSessionStrategy> registry = (Registry<SchemeIOSessionStrategy>) ReflectionTestUtils
|
||||
.getField(connmgr, "ioSessionFactoryRegistry");
|
||||
SchemeIOSessionStrategy strategy = registry.lookup("https");
|
||||
assertThat(strategy).extracting("sslContext").isNotNull();
|
||||
assertThat(strategy).extracting("supportedCipherSuites")
|
||||
.asInstanceOf(InstanceOfAssertFactories.ARRAY)
|
||||
.containsExactly("DESede");
|
||||
assertThat(strategy).extracting("supportedProtocols")
|
||||
.asInstanceOf(InstanceOfAssertFactories.ARRAY)
|
||||
.containsExactly("TLSv1.3");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ConnectionDetailsConfiguration {
|
||||
|
||||
|
|
Loading…
Reference in New Issue