Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ssl bundle support for autoconfigured opensearch client #397

Merged
merged 2 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
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;
Expand All @@ -16,6 +18,7 @@
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.opensearch.client.RestClient;
import org.opensearch.client.RestClientBuilder;
import org.opensearch.client.sniff.Sniffer;
Expand All @@ -25,6 +28,9 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
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;
Expand All @@ -48,8 +54,8 @@ static class RestClientBuilderConfiguration {
}

@Bean
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer(OpenSearchProperties properties) {
return new DefaultRestClientBuilderCustomizer(properties, this.connectionDetails);
RestClientBuilderCustomizer defaultRestClientBuilderCustomizer(OpenSearchProperties properties, ObjectProvider<SslBundles> sslBundles) {
return new DefaultRestClientBuilderCustomizer(properties, this.connectionDetails, sslBundles);
}

@Bean
Expand Down Expand Up @@ -136,9 +142,12 @@ static class DefaultRestClientBuilderCustomizer implements RestClientBuilderCust

private final OpenSearchConnectionDetails connectionDetails;

DefaultRestClientBuilderCustomizer(OpenSearchProperties properties, OpenSearchConnectionDetails connectionDetails) {
private final ObjectProvider<SslBundles> sslBundles;

DefaultRestClientBuilderCustomizer(OpenSearchProperties properties, OpenSearchConnectionDetails connectionDetails, ObjectProvider<SslBundles> sslBundles) {
this.properties = properties;
this.connectionDetails = connectionDetails;
this.sslBundles = sslBundles;
}

@Override
Expand All @@ -150,6 +159,11 @@ public void customize(HttpAsyncClientBuilder builder) {
map.from(this.properties::isSocketKeepAlive)
.to((keepAlive) -> builder.setDefaultIOReactorConfig(
IOReactorConfig.custom().setSoKeepAlive(keepAlive).build()));

String sslBundleName = properties.getRestclient().getSsl().getBundle();
if (StringUtils.hasText(sslBundleName)) {
this.configureSsl(builder, sslBundles.getObject().getBundle(sslBundleName));
}
}

@Override
Expand All @@ -163,6 +177,13 @@ public void customize(RequestConfig.Builder builder) {
.asInt(Duration::toMillis)
.to(builder::setSocketTimeout);
}

private void configureSsl(HttpAsyncClientBuilder builder, SslBundle sslBundle) {
SSLContext sslcontext = sslBundle.createSslContext();
SslOptions sslOptions = sslBundle.getOptions();

builder.setSSLStrategy(new SSLIOSessionStrategy(sslcontext, sslOptions.getEnabledProtocols(), sslOptions.getCiphers(), (HostnameVerifier) null));
}
}

private static class ConnectionsDetailsCredentialsProvider extends BasicCredentialsProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opensearch.client.RestClient;
import org.opensearch.testcontainers.OpensearchContainer;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
Expand All @@ -34,6 +35,12 @@ class OpenSearchRestClientAutoConfigurationIntegrationTests extends AbstractOpen
.withStartupAttempts(5)
.withStartupTimeout(Duration.ofMinutes(10));

@Container
static final OpensearchContainer<?> secureOpensearch = new OpensearchContainer<>(getDockerImageName())
.withSecurityEnabled()
.withStartupAttempts(5)
.withStartupTimeout(Duration.ofMinutes(10));

private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(OpenSearchRestClientAutoConfiguration.class));

Expand Down Expand Up @@ -76,4 +83,27 @@ void restClientCanQueryOpensearchNode() {
}
});
}

@Test
void restClientWithSslCanConnectToOpensearch() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class))
.withPropertyValues(
"opensearch.uris=" + secureOpensearch.getHttpHostAddress(),
"opensearch.connection-timeout=120s",
"opensearch.socket-timeout=120s",
"opensearch.username=" + secureOpensearch.getUsername(),
"opensearch.password=" + secureOpensearch.getPassword(),
"opensearch.restclient.ssl.bundle=opensearch-demo-ca",
"spring.ssl.bundle.pem.opensearch-demo-ca.truststore.certificate=classpath:opensearch-demo-ca.pem"
)
.run((context) -> {
final RestClient client = context.getBean(RestClient.class);
final Request request = new Request("GET", "/");

final Response response = client.performRequest(request);

assertThat(response.getStatusLine().getStatusCode()).isEqualTo(200);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
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.junit.jupiter.api.Test;
import org.opensearch.client.Node;
Expand All @@ -25,10 +27,12 @@
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
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;

/**
* Tests for {@link OpenSearchRestClientAutoConfiguration}.
Expand Down Expand Up @@ -259,6 +263,34 @@ void configureWhenCustomSnifferShouldBackOff() {
});
}

@Test
void configureWithSslBundle() {
this.contextRunner
.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class))
.withPropertyValues(
"opensearch.restclient.ssl.bundle=opensearch-ca",
"spring.ssl.bundle.pem.opensearch-ca.truststore.certificate=classpath:opensearch-demo-ca.pem",
"spring.ssl.bundle.pem.opensearch-ca.options.ciphers=DESede",
"spring.ssl.bundle.pem.opensearch-ca.options.enabled-protocols=TLSv1.3"
)
.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 BuilderCustomizerConfiguration {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-----BEGIN CERTIFICATE-----
MIIExjCCA66gAwIBAgIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcNAQEL
BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt
cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl
IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v
dCBDQTAeFw0yNDAyMjAxNzAwMzZaFw0zNDAyMTcxNzAwMzZaMIGPMRMwEQYKCZIm
iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ
RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290
IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEPyN7J9VGPyJcQmCBl5TGwfSzvVdWwoQU
j9aEsdfFJ6pBCDQSsj8Lv4RqL0dZra7h7SpZLLX/YZcnjikrYC+rP5OwsI9xEE/4
U98CsTBPhIMgqFK6SzNE5494BsAk4cL72dOOc8tX19oDS/PvBULbNkthQ0aAF1dg
vbrHvu7hq7LisB5ZRGHVE1k/AbCs2PaaKkn2jCw/b+U0Ml9qPuuEgz2mAqJDGYoA
WSR4YXrOcrmPuRqbws464YZbJW898/0Pn/U300ed+4YHiNYLLJp51AMkR4YEw969
VRPbWIvLrd0PQBooC/eLrL6rvud/GpYhdQEUx8qcNCKd4bz3OaQ5AgMBAAGjggEW
MIIBEjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQU
F4ffoFrrZhKn1dD4uhJFPLcrAJwwgc8GA1UdIwSBxzCBxIAUF4ffoFrrZhKn1dD4
uhJFPLcrAJyhgZWkgZIwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJ
k/IsZAEZFgdleGFtcGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYD
VQQLDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUg
Q29tIEluYy4gUm9vdCBDQYIUDWQJmWZ9xBTsQUeOt9F5YSPpqOIwDQYJKoZIhvcN
AQELBQADggEBAL3Q3AHUhMiLUy6OlLSt8wX9I2oNGDKbBu0atpUNDztk/0s3YLQC
YuXgN4KrIcMXQIuAXCx407c+pIlT/T1FNn+VQXwi56PYzxQKtlpoKUL3oPQE1d0V
6EoiNk+6UodvyZqpdQu7fXVentRMk1QX7D9otmiiNuX+GSxJhJC2Lyzw65O9EUgG
1yVJon6RkUGtqBqKIuLksKwEr//ELnjmXit4LQKSnqKr0FTCB7seIrKJNyb35Qnq
qy9a/Unhokrmdda1tr6MbqU8l7HmxLuSd/Ky+L0eDNtYv6YfMewtjg0TtAnFyQov
rdXmeq1dy9HLo3Ds4AFz3Gx9076TxcRS/iI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEyTCCA7GgAwIBAgIGAWLrc1O2MA0GCSqGSIb3DQEBCwUAMIGPMRMwEQYKCZIm
iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ
RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290
IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwHhcNMTgwNDIy
MDM0MzQ3WhcNMjgwNDE5MDM0MzQ3WjBeMRIwEAYKCZImiZPyLGQBGRYCZGUxDTAL
BgNVBAcMBHRlc3QxDTALBgNVBAoMBG5vZGUxDTALBgNVBAsMBG5vZGUxGzAZBgNV
BAMMEm5vZGUtMC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJa+f476vLB+AwK53biYByUwN+40D8jMIovGXm6wgT8+9Sbs899dDXgt
9CE1Beo65oP1+JUz4c7UHMrCY3ePiDt4cidHVzEQ2g0YoVrQWv0RedS/yx/DKhs8
Pw1O715oftP53p/2ijD5DifFv1eKfkhFH+lwny/vMSNxellpl6NxJTiJVnQ9HYOL
gf2t971ITJHnAuuxUF48HcuNovW4rhtkXef8kaAN7cE3LU+A9T474ULNCKkEFPIl
ZAKN3iJNFdVsxrTU+CUBHzk73Do1cCkEvJZ0ZFjp0Z3y8wLY/gqWGfGVyA9l2CUq
eIZNf55PNPtGzOrvvONiui48vBKH1LsCAwEAAaOCAVkwggFVMIG8BgNVHSMEgbQw
gbGAFJI1DOAPHitF9k0583tfouYSl0BzoYGVpIGSMIGPMRMwEQYKCZImiZPyLGQB
GRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQRXhhbXBs
ZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290IENBMSEw
HwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0GCAQEwHQYDVR0OBBYEFKyv
78ZmFjVKM9g7pMConYH7FVBHMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXg
MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA1BgNVHREELjAsiAUq
AwQFBYISbm9kZS0wLmV4YW1wbGUuY29tgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZI
hvcNAQELBQADggEBAIOKuyXsFfGv1hI/Lkpd/73QNqjqJdxQclX57GOMWNbOM5H0
5/9AOIZ5JQsWULNKN77aHjLRr4owq2jGbpc/Z6kAd+eiatkcpnbtbGrhKpOtoEZy
8KuslwkeixpzLDNISSbkeLpXz4xJI1ETMN/VG8ZZP1bjzlHziHHDu0JNZ6TnNzKr
XzCGMCohFfem8vnKNnKUneMQMvXd3rzUaAgvtf7Hc2LTBlf4fZzZF1EkwdSXhaMA
1lkfHiqOBxtgeDLxCHESZ2fqgVqsWX+t3qHQfivcPW6txtDyrFPRdJOGhiMGzT/t
e/9kkAtQRgpTb3skYdIOOUOV0WGQ60kJlFhAzIs=
-----END CERTIFICATE-----
Loading