Add SSL service connection support for ElasticSearch

See gh-41137
This commit is contained in:
Moritz Halbritter
2025-02-11 10:23:21 +01:00
parent 7cf9cc74a2
commit 789d30deab
13 changed files with 583 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-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.
@@ -16,15 +16,26 @@
package org.springframework.boot.testcontainers.service.connection.elasticsearch;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.List;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchConnectionDetails.Node.Protocol;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslStoreBundle;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory;
import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.boot.testcontainers.service.connection.Ssl;
/**
* {@link ContainerConnectionDetailsFactory} to create
@@ -53,15 +64,83 @@ class ElasticsearchContainerConnectionDetailsFactory
private static final class ElasticsearchContainerConnectionDetails
extends ContainerConnectionDetails<ElasticsearchContainer> implements ElasticsearchConnectionDetails {
private volatile SslBundle sslBundle;
private ElasticsearchContainerConnectionDetails(ContainerConnectionSource<ElasticsearchContainer> source) {
super(source);
}
@Override
public String getUsername() {
return "elastic";
}
@Override
public String getPassword() {
return getContainer().getEnvMap().get("ELASTIC_PASSWORD");
}
@Override
public List<Node> getNodes() {
String host = getContainer().getHost();
Integer port = getContainer().getMappedPort(DEFAULT_PORT);
return List.of(new Node(host, port, Protocol.HTTP, null, null));
return List.of(new Node(host, port, (getSslBundle() != null) ? Protocol.HTTPS : Protocol.HTTP,
getUsername(), getPassword()));
}
@Override
public SslBundle getSslBundle() {
if (this.sslBundle != null) {
return this.sslBundle;
}
SslBundle sslBundle = super.getSslBundle();
if (sslBundle != null) {
this.sslBundle = sslBundle;
return sslBundle;
}
if (hasAnnotation(Ssl.class)) {
byte[] caCertificate = getContainer().caCertAsBytes().orElse(null);
if (caCertificate != null) {
KeyStore trustStore = createTrustStore(caCertificate);
sslBundle = createSslBundleWithTrustStore(trustStore);
this.sslBundle = sslBundle;
return sslBundle;
}
}
return null;
}
private SslBundle createSslBundleWithTrustStore(KeyStore trustStore) {
return SslBundle.of(new SslStoreBundle() {
@Override
public KeyStore getKeyStore() {
return null;
}
@Override
public String getKeyStorePassword() {
return null;
}
@Override
public KeyStore getTrustStore() {
return trustStore;
}
});
}
private KeyStore createTrustStore(byte[] caCertificate) {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Certificate certificate = certFactory.generateCertificate(new ByteArrayInputStream(caCertificate));
keyStore.setCertificateEntry("ca", certificate);
return keyStore;
}
catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException ex) {
throw new IllegalStateException("Failed to create keystore from CA certificate", ex);
}
}
}