11package org .testcontainers .elasticsearch ;
22
3- import static java .net .HttpURLConnection .HTTP_OK ;
4- import static java .net .HttpURLConnection .HTTP_UNAUTHORIZED ;
5-
6- import java .net .InetSocketAddress ;
7- import java .time .Duration ;
3+ import com .github .dockerjava .api .command .InspectContainerResponse ;
4+ import org .apache .commons .io .IOUtils ;
85import org .testcontainers .containers .GenericContainer ;
9- import org .testcontainers .containers .wait .strategy .HttpWaitStrategy ;
6+ import org .testcontainers .containers .wait .strategy .LogMessageWaitStrategy ;
107import org .testcontainers .utility .Base58 ;
8+ import org .testcontainers .utility .ComparableVersion ;
119import org .testcontainers .utility .DockerImageName ;
1210
11+ import javax .net .ssl .SSLContext ;
12+ import javax .net .ssl .TrustManagerFactory ;
13+ import java .io .ByteArrayInputStream ;
14+ import java .net .InetSocketAddress ;
15+ import java .security .KeyStore ;
16+ import java .security .cert .Certificate ;
17+ import java .security .cert .CertificateFactory ;
18+ import java .util .Optional ;
19+
1320/**
1421 * Represents an elasticsearch docker instance which exposes by default port 9200 and 9300 (transport.tcp.port)
1522 * The docker image is by default fetched from docker.elastic.co/elasticsearch/elasticsearch
1623 */
1724public class ElasticsearchContainer extends GenericContainer <ElasticsearchContainer > {
1825
26+ /**
27+ * Elasticsearch Default Password for Elasticsearch >= 8
28+ */
29+ public static final String ELASTICSEARCH_DEFAULT_PASSWORD = "changeme" ;
30+
1931 /**
2032 * Elasticsearch Default HTTP port
2133 */
@@ -39,7 +51,10 @@ public class ElasticsearchContainer extends GenericContainer<ElasticsearchContai
3951 */
4052 @ Deprecated
4153 protected static final String DEFAULT_TAG = "7.9.2" ;
42- private boolean isOss = false ;
54+
55+ private final boolean isOss ;
56+ private final boolean isAtLeastMajorVersion8 ;
57+ private Optional <byte []> caCertAsBytes = Optional .empty ();
4358
4459 /**
4560 * @deprecated use {@link ElasticsearchContainer(DockerImageName)} instead
@@ -65,23 +80,67 @@ public ElasticsearchContainer(final DockerImageName dockerImageName) {
6580 super (dockerImageName );
6681
6782 dockerImageName .assertCompatibleWith (DEFAULT_IMAGE_NAME , DEFAULT_OSS_IMAGE_NAME );
68-
69- if (dockerImageName .isCompatibleWith (DEFAULT_OSS_IMAGE_NAME )) {
70- this .isOss = true ;
71- }
83+ this .isOss = dockerImageName .isCompatibleWith (DEFAULT_OSS_IMAGE_NAME );
7284
7385 logger ().info ("Starting an elasticsearch container using [{}]" , dockerImageName );
7486 withNetworkAliases ("elasticsearch-" + Base58 .randomString (6 ));
7587 withEnv ("discovery.type" , "single-node" );
7688 addExposedPorts (ELASTICSEARCH_DEFAULT_PORT , ELASTICSEARCH_DEFAULT_TCP_PORT );
77- setWaitStrategy (new HttpWaitStrategy ()
78- .forPort (ELASTICSEARCH_DEFAULT_PORT )
79- .forStatusCodeMatching (response -> response == HTTP_OK || response == HTTP_UNAUTHORIZED )
80- .withStartupTimeout (Duration .ofMinutes (2 )));
89+ this .isAtLeastMajorVersion8 = new ComparableVersion (dockerImageName .getVersionPart ()).isGreaterThanOrEqualTo ("8.0.0" );
90+ // regex that
91+ // matches 8.0 JSON logging with no whitespace between message field and content
92+ // matches 7.x JSON logging with whitespace between message field and content
93+ // matches 6.x text logging with node name in brackets and just a 'started' message till the end of the line
94+ String regex = ".*(\" message\" :\\ s?\" started\" .*|] started\n $)" ;
95+ setWaitStrategy (new LogMessageWaitStrategy ().withRegEx (regex ));
96+ if (isAtLeastMajorVersion8 ) {
97+ withPassword (ELASTICSEARCH_DEFAULT_PASSWORD );
98+ }
99+ }
100+
101+ @ Override
102+ protected void containerIsStarted (InspectContainerResponse containerInfo ) {
103+ if (isAtLeastMajorVersion8 ) {
104+ byte [] bytes = copyFileFromContainer ("/usr/share/elasticsearch/config/certs/http_ca.crt" , IOUtils ::toByteArray );
105+ if (bytes .length > 0 ) {
106+ this .caCertAsBytes = Optional .of (bytes );
107+ }
108+ }
109+ }
110+
111+ /**
112+ * If this is running above Elasticsearch 8, this will return the probably self-signed CA cert that has been extracted
113+ *
114+ * @return byte array optional containing the CA cert extracted from the docker container
115+ */
116+ public Optional <byte []> caCertAsBytes () {
117+ return caCertAsBytes ;
81118 }
82119
83120 /**
84- * Define the Elasticsearch password to set. It enables security behind the scene.
121+ * A SSL context based on the self signed CA, so that using this SSL Context allows to connect to the Elasticsearch service
122+ * @return a customized SSL Context
123+ */
124+ public SSLContext createSslContextFromCa () {
125+ try {
126+ CertificateFactory factory = CertificateFactory .getInstance ("X.509" );
127+ Certificate trustedCa = factory .generateCertificate (new ByteArrayInputStream (caCertAsBytes .get ()));
128+ KeyStore trustStore = KeyStore .getInstance ("pkcs12" );
129+ trustStore .load (null , null );
130+ trustStore .setCertificateEntry ("ca" , trustedCa );
131+
132+ final SSLContext sslContext = SSLContext .getInstance ("TLSv1.3" );
133+ TrustManagerFactory tmfactory = TrustManagerFactory .getInstance (TrustManagerFactory .getDefaultAlgorithm ());
134+ tmfactory .init (trustStore );
135+ sslContext .init (null , tmfactory .getTrustManagers (), null );
136+ return sslContext ;
137+ } catch (Exception e ) {
138+ throw new RuntimeException (e );
139+ }
140+ }
141+
142+ /**
143+ * Define the Elasticsearch password to set. It enables security behind the scene for major version below 8.0.0.
85144 * It's not possible to use security with the oss image.
86145 * @param password Password to set
87146 * @return this
@@ -92,7 +151,10 @@ public ElasticsearchContainer withPassword(String password) {
92151 "Please switch to the default distribution" );
93152 }
94153 withEnv ("ELASTIC_PASSWORD" , password );
95- withEnv ("xpack.security.enabled" , "true" );
154+ if (!isAtLeastMajorVersion8 ) {
155+ // major version 8 is secure by default and does not need this to enable authentication
156+ withEnv ("xpack.security.enabled" , "true" );
157+ }
96158 return this ;
97159 }
98160
0 commit comments