You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2019/11/14 09:13:20 UTC

[james-project] branch master updated (52bf917 -> 2e87e12)

This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.


    from 52bf917  [Refactoring] Simplify MailAddressTest by fully using the power of assertj
     new 1d82a54  JAMES-2905 ElasticSearch authentication configuration
     new 870798f  JAMES-2905 DockerElasticSearch now has two types
     new 0b50da7  JAMES-2905 Update ClientProvider to configure ES authentication
     new f8a5fb6  JAMES-2905 Expand ClientProvider tests with Auth ES
     new e60d88e  JAMES-2905 Update documentation
     new 8b46a4b  JAMES-2905 ElasticSearch configuration various improvements
     new 2e87e12  JAMES-2905 One more option about HostNameVerifier

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../apache/james/backends/es/ClientProvider.java   | 141 ++++++++--
 .../backends/es/ElasticSearchConfiguration.java    | 272 +++++++++++++++++-
 ...iderImplConnectionAuthESIgnoreSSLCheckTest.java |  33 ++-
 ...mplConnectionAuthESOverrideTrustStoreTest.java} |  56 ++--
 ...a => ClientProviderImplConnectionContract.java} |  75 ++---
 .../ClientProviderImplConnectionNoAuthESTest.java  |  12 +-
 ....java => DockerAuthElasticSearchSingleton.java} |   4 +-
 .../james/backends/es/DockerElasticSearch.java     | 309 +++++++++++++++++----
 .../backends/es/DockerElasticSearchSingleton.java  |   2 +-
 .../backends/es/ElasticSearchClusterExtension.java | 115 ++++++++
 .../es/ElasticSearchConfigurationTest.java         | 235 ++++++++++++++++
 .../src/test/resources/auth-es/NginxDockerfile     |   5 +
 .../src/test/resources/auth-es/README.md           |  30 ++
 .../src/test/resources/auth-es/default.crt         |  19 ++
 .../src/test/resources/auth-es/default.key         |  27 ++
 .../src/test/resources/auth-es/nginx-conf/passwd   |   2 +
 .../auth-es/nginx-conf/reverse_elasticsearch.conf  |  12 +
 .../src/test/resources/auth-es/server.jks          | Bin 0 -> 1074 bytes
 .../destination/conf/elasticsearch.properties      |  23 ++
 .../destination/conf/elasticsearch.properties      |  23 ++
 .../destination/conf/elasticsearch.properties      |  23 ++
 .../destination/conf/elasticsearch.properties      |  23 ++
 ...esWithNonCompatibleElasticSearchServerTest.java |   2 +-
 .../apache/james/metric/es/ES2ReporterTest.java    |   2 +-
 .../apache/james/metric/es/ES6ReporterTest.java    |   2 +-
 .../apache/james/util/docker/DockerContainer.java  |  11 +
 src/site/xdoc/server/config-elasticsearch.xml      |  84 +++++-
 27 files changed, 1351 insertions(+), 191 deletions(-)
 copy server/container/guice/memory-guice/src/test/java/org/apache/james/LinshareBlobExportMechanismProvidingTest.java => backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java (54%)
 copy backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/{ElasticSearchHealthCheckConnectionTest.java => ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java} (51%)
 rename backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/{ClientProviderImplConnectionTest.java => ClientProviderImplConnectionContract.java} (56%)
 copy event-sourcing/event-store-cassandra/src/test/java/org/apache/james/eventsourcing/eventstore/cassandra/CassandraEventStoreTest.java => backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionNoAuthESTest.java (74%)
 copy backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/{DockerElasticSearchSingleton.java => DockerAuthElasticSearchSingleton.java} (95%)
 create mode 100644 backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/NginxDockerfile
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/README.md
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/default.crt
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/default.key
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/passwd
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/reverse_elasticsearch.conf
 create mode 100644 backends-common/elasticsearch/src/test/resources/auth-es/server.jks


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 04/07: JAMES-2905 Expand ClientProvider tests with Auth ES

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit f8a5fb607bd47a479f055ea45363bb00d72fbd9d
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Nov 12 16:36:58 2019 +0700

    JAMES-2905 Expand ClientProvider tests with Auth ES
---
 ...iderImplConnectionAuthESIgnoreSSLCheckTest.java | 44 +++++++++++++
 ...ImplConnectionAuthESOverrideTrustStoreTest.java | 46 +++++++++++++
 ...a => ClientProviderImplConnectionContract.java} | 75 ++++++----------------
 .../ClientProviderImplConnectionNoAuthESTest.java  | 31 +++++++++
 .../es/DockerAuthElasticSearchSingleton.java       | 28 ++++++++
 5 files changed, 169 insertions(+), 55 deletions(-)

diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java
new file mode 100644
index 0000000..1de1996
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java
@@ -0,0 +1,44 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.backends.es;
+
+import static org.apache.james.backends.es.ElasticSearchClusterExtension.ElasticSearchCluster;
+
+import java.util.Optional;
+
+import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class ClientProviderImplConnectionAuthESIgnoreSSLCheckTest implements ClientProviderImplConnectionContract {
+
+    @RegisterExtension
+    static ElasticSearchClusterExtension extension = new ElasticSearchClusterExtension(new ElasticSearchCluster(
+        DockerAuthElasticSearchSingleton.INSTANCE,
+        new DockerElasticSearch.WithAuth()));
+
+    @Override
+    public ElasticSearchConfiguration.Builder configurationBuilder() {
+        return ElasticSearchConfiguration.builder()
+            .credential(Optional.of(DockerElasticSearch.WithAuth.DEFAULT_CREDENTIAL))
+            .hostScheme(Optional.of(HostScheme.HTTPS))
+            .sslTrustConfiguration(SSLTrustConfiguration.ignore());
+    }
+}
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java
new file mode 100644
index 0000000..5b92cf7
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java
@@ -0,0 +1,46 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.backends.es;
+
+import java.util.Optional;
+
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class ClientProviderImplConnectionAuthESOverrideTrustStoreTest implements ClientProviderImplConnectionContract {
+
+    private static final String TRUST_STORE_PASSWORD = "mypass";
+    private static final String TRUST_STORE_FILE_PATH = "src/test/resources/auth-es/server.jks";
+
+    @RegisterExtension
+    static ElasticSearchClusterExtension extension = new ElasticSearchClusterExtension(new ElasticSearchClusterExtension.ElasticSearchCluster(
+        DockerAuthElasticSearchSingleton.INSTANCE,
+        new DockerElasticSearch.WithAuth()));
+
+    @Override
+    public ElasticSearchConfiguration.Builder configurationBuilder() {
+        return ElasticSearchConfiguration.builder()
+            .credential(Optional.of(DockerElasticSearch.WithAuth.DEFAULT_CREDENTIAL))
+            .hostScheme(Optional.of(ElasticSearchConfiguration.HostScheme.HTTPS))
+            .sslTrustConfiguration(SSLTrustConfiguration.override(
+                SSLTrustStore.of(TRUST_STORE_FILE_PATH, TRUST_STORE_PASSWORD)));
+    }
+}
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionContract.java
similarity index 56%
rename from backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionTest.java
rename to backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionContract.java
index db6ae42..05a7ec4 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionContract.java
@@ -21,61 +21,25 @@ package org.apache.james.backends.es;
 
 import java.util.concurrent.TimeUnit;
 
-import org.apache.james.util.Host;
-import org.apache.james.util.docker.DockerContainer;
-import org.apache.james.util.docker.Images;
+import org.apache.james.backends.es.ElasticSearchClusterExtension.ElasticSearchCluster;
 import org.awaitility.Awaitility;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.client.RequestOptions;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.index.query.QueryBuilders;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-class ClientProviderImplConnectionTest {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ClientProviderImplConnectionTest.class);
-    private static final int ES_APPLICATIVE_PORT = 9200;
+interface ClientProviderImplConnectionContract {
 
-    static DockerContainer es1 = DockerContainer.fromName(Images.ELASTICSEARCH_6)
-        .withEnv("discovery.type", "single-node")
-        .withAffinityToContainer()
-        .withExposedPorts(ES_APPLICATIVE_PORT);
-
-    DockerContainer es2 = DockerContainer.fromName(Images.ELASTICSEARCH_6)
-        .withEnv("discovery.type", "single-node")
-        .withAffinityToContainer()
-        .withExposedPorts(ES_APPLICATIVE_PORT);
-
-    @BeforeAll
-    static void setUpClass() {
-        es1.start();
-    }
-
-    @BeforeEach
-    void setUp() {
-        es2.start();
-    }
-
-    @AfterEach
-    void tearDown() {
-        es2.stop();
-    }
-
-    @AfterAll
-    static void tearDownClass() {
-        es1.stop();
-    }
+    Logger LOGGER = LoggerFactory.getLogger(ClientProviderImplConnectionContract.class);
 
     @Test
-    void connectingASingleServerShouldWork() {
-        ElasticSearchConfiguration configuration = ElasticSearchConfiguration.builder()
-            .addHost(Host.from(es1.getContainerIp(), ES_APPLICATIVE_PORT))
+    default void connectingASingleServerShouldWork(ElasticSearchCluster esCluster) {
+        ElasticSearchConfiguration configuration = configurationBuilder()
+            .addHost(esCluster.es1.getHttpHost())
             .build();
 
         Awaitility.await()
@@ -85,10 +49,9 @@ class ClientProviderImplConnectionTest {
     }
 
     @Test
-    void connectingAClusterShouldWork() {
-        ElasticSearchConfiguration configuration = ElasticSearchConfiguration.builder()
-            .addHost(Host.from(es1.getContainerIp(), ES_APPLICATIVE_PORT))
-            .addHost(Host.from(es2.getContainerIp(), ES_APPLICATIVE_PORT))
+    default void connectingAClusterShouldWork(ElasticSearchCluster esCluster) {
+        ElasticSearchConfiguration configuration = configurationBuilder()
+            .addHosts(esCluster.getHosts())
             .build();
 
         Awaitility.await()
@@ -98,23 +61,20 @@ class ClientProviderImplConnectionTest {
     }
 
     @Test
-    void connectingAClusterWithAFailedNodeShouldWork() {
-        String es1Ip = es1.getContainerIp();
-        String es2Ip = es2.getContainerIp();
-        es2.stop();
-
-        ElasticSearchConfiguration configuration = ElasticSearchConfiguration.builder()
-            .addHost(Host.from(es1Ip, ES_APPLICATIVE_PORT))
-            .addHost(Host.from(es2Ip, ES_APPLICATIVE_PORT))
+    default void connectingAClusterWithAFailedNodeShouldWork(ElasticSearchCluster esCluster) {
+        ElasticSearchConfiguration configuration = configurationBuilder()
+            .addHosts(esCluster.getHosts())
             .build();
 
+        esCluster.es2.stop();
+
         Awaitility.await()
             .atMost(1, TimeUnit.MINUTES)
             .pollInterval(5, TimeUnit.SECONDS)
             .until(() -> isConnected(new ClientProvider(configuration)));
     }
 
-    private boolean isConnected(ClientProvider clientProvider) {
+    default boolean isConnected(ClientProvider clientProvider) {
         try (RestHighLevelClient client = clientProvider.get()) {
             client.search(
                 new SearchRequest()
@@ -126,4 +86,9 @@ class ClientProviderImplConnectionTest {
             return false;
         }
     }
+
+    default ElasticSearchConfiguration.Builder configurationBuilder() {
+        return ElasticSearchConfiguration.builder();
+    }
 }
+
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionNoAuthESTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionNoAuthESTest.java
new file mode 100644
index 0000000..6fa92d3
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionNoAuthESTest.java
@@ -0,0 +1,31 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.backends.es;
+
+import org.apache.james.backends.es.ElasticSearchClusterExtension.ElasticSearchCluster;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class ClientProviderImplConnectionNoAuthESTest implements ClientProviderImplConnectionContract {
+
+    @RegisterExtension
+    static ElasticSearchClusterExtension extension = new ElasticSearchClusterExtension(new ElasticSearchCluster(
+        DockerElasticSearchSingleton.INSTANCE,
+        new DockerElasticSearch.NoAuth()));
+}
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerAuthElasticSearchSingleton.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerAuthElasticSearchSingleton.java
new file mode 100644
index 0000000..5346ef4
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerAuthElasticSearchSingleton.java
@@ -0,0 +1,28 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.backends.es;
+
+public class DockerAuthElasticSearchSingleton {
+    public static DockerElasticSearch INSTANCE = new DockerElasticSearch.WithAuth();
+
+    static {
+        INSTANCE.start();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 03/07: JAMES-2905 Update ClientProvider to configure ES authentication

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0b50da794a0bd00fef3758afe274fe30ba6a0053
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Nov 12 16:36:31 2019 +0700

    JAMES-2905 Update ClientProvider to configure ES authentication
---
 .../apache/james/backends/es/ClientProvider.java   | 124 ++++++++++++++++++---
 1 file changed, 106 insertions(+), 18 deletions(-)

diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
index 4f35e38..923a820 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
@@ -18,23 +18,35 @@
  ****************************************************************/
 package org.apache.james.backends.es;
 
+import java.io.File;
 import java.io.IOException;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
 import java.time.Duration;
 import java.time.LocalDateTime;
-import java.util.Optional;
 
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Provider;
+import javax.net.ssl.SSLContext;
 
+import org.apache.commons.lang3.NotImplementedException;
 import org.apache.commons.lang3.time.DurationFormatUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.CredentialsProvider;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.apache.http.ssl.TrustStrategy;
+import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy;
 import org.elasticsearch.client.RestClient;
-import org.elasticsearch.client.RestClientBuilder;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -46,14 +58,103 @@ import reactor.core.scheduler.Schedulers;
 
 public class ClientProvider implements Provider<RestHighLevelClient> {
 
+    private static class HttpAsyncClientConfigurer {
+
+        private static final TrustStrategy TRUST_ALL = (x509Certificates, authType) -> true;
+
+        private final ElasticSearchConfiguration configuration;
+
+        private HttpAsyncClientConfigurer(ElasticSearchConfiguration configuration) {
+            this.configuration = configuration;
+        }
+
+        private HttpAsyncClientBuilder configure(HttpAsyncClientBuilder builder) {
+            configureAuthentication(builder);
+            configureHostScheme(builder);
+
+            return builder;
+        }
+
+        private void configureHostScheme(HttpAsyncClientBuilder builder) {
+            HostScheme scheme = configuration.getHostScheme();
+
+            switch (scheme) {
+                case HTTP:
+                    return;
+                case HTTPS:
+                    configureSSLOptions(builder);
+                    return;
+                default:
+                    throw new NotImplementedException(
+                        String.format("unrecognized hostScheme '%s'", scheme.name()));
+            }
+        }
+
+        private void configureSSLOptions(HttpAsyncClientBuilder builder) {
+            try {
+                builder
+                    .setSSLContext(sslContext())
+                    .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
+            } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException e) {
+                throw new RuntimeException("Cannot set SSL options to the builder", e);
+            }
+        }
+
+        private SSLContext sslContext() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException,
+            CertificateException, IOException {
+
+            SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
+
+            SSLValidationStrategy strategy = configuration.getSslTrustConfiguration()
+                .getStrategy();
+
+            switch (strategy) {
+                case DEFAULT:
+                    return sslContextBuilder.build();
+                case IGNORE:
+                    return sslContextBuilder.loadTrustMaterial(TRUST_ALL)
+                        .build();
+                case OVERRIDE:
+                    return applyTrustStore(sslContextBuilder)
+                        .build();
+                default:
+                    throw new NotImplementedException(
+                        String.format("unrecognized strategy '%s'", strategy.name()));
+            }
+        }
+
+        private SSLContextBuilder applyTrustStore(SSLContextBuilder sslContextBuilder) throws CertificateException, NoSuchAlgorithmException,
+            KeyStoreException, IOException {
+
+            SSLTrustStore trustStore = configuration.getSslTrustConfiguration()
+                .getTrustStore()
+                .orElseThrow(() -> new IllegalStateException("SSLTrustStore cannot to be empty"));
+
+            return sslContextBuilder
+                .loadTrustMaterial(new File(trustStore.getFilePath()), trustStore.getPassword().toCharArray());
+        }
+
+        private void configureAuthentication(HttpAsyncClientBuilder builder) {
+            configuration.getCredential()
+                .ifPresent(credential -> {
+                    CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+                    credentialsProvider.setCredentials(AuthScope.ANY,
+                        new UsernamePasswordCredentials(credential.getUsername(), credential.getPassword()));
+                    builder.setDefaultCredentialsProvider(credentialsProvider);
+                });
+        }
+    }
+
     private static final Logger LOGGER = LoggerFactory.getLogger(ClientProvider.class);
 
     private final ElasticSearchConfiguration configuration;
     private final RestHighLevelClient client;
+    private final HttpAsyncClientConfigurer httpAsyncClientConfigurer;
 
     @Inject
     @VisibleForTesting
     ClientProvider(ElasticSearchConfiguration configuration) {
+        this.httpAsyncClientConfigurer = new HttpAsyncClientConfigurer(configuration);
         this.configuration = configuration;
         this.client = connect(configuration);
     }
@@ -73,27 +174,14 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
 
     private RestHighLevelClient connectToCluster(ElasticSearchConfiguration configuration) {
         LOGGER.info("Trying to connect to ElasticSearch service at {}", LocalDateTime.now());
-        RestClientBuilder restClientBuilder = RestClient.builder(hostsToHttpHosts());
 
         return new RestHighLevelClient(
-            credentialsProvider(configuration)
-                .map(provider -> restClientBuilder.setHttpClientConfigCallback(
-                    httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(provider)))
-                .orElse(restClientBuilder)
+            RestClient
+                .builder(hostsToHttpHosts())
+                .setHttpClientConfigCallback(httpAsyncClientConfigurer::configure)
                 .setMaxRetryTimeoutMillis(Math.toIntExact(configuration.getRequestTimeout().toMillis())));
     }
 
-    private Optional<CredentialsProvider> credentialsProvider(ElasticSearchConfiguration configuration) {
-        return configuration.getCredential()
-            .map(credential -> {
-                CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
-                credentialsProvider.setCredentials(AuthScope.ANY,
-                    new UsernamePasswordCredentials(credential.getUsername(), credential.getPassword()));
-
-                return credentialsProvider;
-            });
-    }
-
     private HttpHost[] hostsToHttpHosts() {
         return configuration.getHosts().stream()
             .map(host -> new HttpHost(host.getHostName(), host.getPort(), configuration.getHostScheme().name()))


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 01/07: JAMES-2905 ElasticSearch authentication configuration

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 1d82a54003a8bbe465546ef7382922aba367ed1c
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Nov 12 16:33:20 2019 +0700

    JAMES-2905 ElasticSearch authentication configuration
---
 .../backends/es/ElasticSearchConfiguration.java    | 165 ++++++++++++++++++++-
 .../es/ElasticSearchConfigurationTest.java         | 137 +++++++++++++++++
 2 files changed, 298 insertions(+), 4 deletions(-)

diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
index 875bf01..82ce0d6 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
@@ -19,15 +19,20 @@
 
 package org.apache.james.backends.es;
 
+import static org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy.OVERRIDE;
+
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.stream.Stream;
 
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy;
 import org.apache.james.util.Host;
 
 import com.github.steveash.guavate.Guavate;
@@ -98,6 +103,115 @@ public class ElasticSearchConfiguration {
         }
     }
 
+    public static class SSLTrustConfiguration {
+
+        public enum SSLValidationStrategy {
+            DEFAULT,
+            IGNORE,
+            OVERRIDE;
+
+            static SSLValidationStrategy from(String rawValue) {
+                Preconditions.checkNotNull(rawValue);
+
+                return Stream.of(values())
+                    .filter(strategy -> strategy.name().equalsIgnoreCase(rawValue))
+                    .findAny()
+                    .orElseThrow(() -> new IllegalArgumentException(String.format("invalid strategy '%s'", rawValue)));
+
+            }
+        }
+
+        public static class SSLTrustStore {
+
+            public static SSLTrustStore of(String filePath, String password) {
+                return new SSLTrustStore(filePath, password);
+            }
+
+            private final String filePath;
+            private final String password;
+
+            private SSLTrustStore(String filePath, String password) {
+                Preconditions.checkNotNull(filePath, ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " is specified");
+                Preconditions.checkNotNull(password, ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " is specified");
+
+                this.filePath = filePath;
+                this.password = password;
+            }
+
+            public String getFilePath() {
+                return filePath;
+            }
+
+            public String getPassword() {
+                return password;
+            }
+
+            @Override
+            public final boolean equals(Object o) {
+                if (o instanceof SSLTrustStore) {
+                    SSLTrustStore that = (SSLTrustStore) o;
+
+                    return Objects.equals(this.filePath, that.filePath)
+                        && Objects.equals(this.password, that.password);
+                }
+                return false;
+            }
+
+            @Override
+            public final int hashCode() {
+                return Objects.hash(filePath, password);
+            }
+        }
+
+        static SSLTrustConfiguration defaultBehavior() {
+            return new SSLTrustConfiguration(SSLValidationStrategy.DEFAULT, Optional.empty());
+        }
+
+        static SSLTrustConfiguration ignore() {
+            return new SSLTrustConfiguration(SSLValidationStrategy.IGNORE, Optional.empty());
+        }
+
+        static SSLTrustConfiguration override(SSLTrustStore sslTrustStore) {
+            return new SSLTrustConfiguration(OVERRIDE, Optional.of(sslTrustStore));
+        }
+
+        private final SSLValidationStrategy strategy;
+        private final Optional<SSLTrustStore> trustStore;
+
+        private SSLTrustConfiguration(SSLValidationStrategy strategy, Optional<SSLTrustStore> trustStore) {
+            Preconditions.checkNotNull(strategy);
+            Preconditions.checkNotNull(trustStore);
+            Preconditions.checkArgument(strategy != OVERRIDE || trustStore.isPresent(), "OVERRIDE strategy requires trustStore to be present");
+
+            this.strategy = strategy;
+            this.trustStore = trustStore;
+        }
+
+        public SSLValidationStrategy getStrategy() {
+            return strategy;
+        }
+
+        public Optional<SSLTrustStore> getTrustStore() {
+            return trustStore;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof SSLTrustConfiguration) {
+                SSLTrustConfiguration that = (SSLTrustConfiguration) o;
+
+                return Objects.equals(this.strategy, that.strategy)
+                    && Objects.equals(this.trustStore, that.trustStore);
+            }
+            return false;
+        }
+
+        @Override
+        public final int hashCode() {
+            return Objects.hash(strategy, trustStore);
+        }
+    }
+
     public static class Builder {
 
         private final ImmutableList.Builder<Host> hosts;
@@ -109,6 +223,7 @@ public class ElasticSearchConfiguration {
         private Optional<Duration> requestTimeout;
         private Optional<HostScheme> hostScheme;
         private Optional<Credential> credential;
+        private Optional<SSLTrustConfiguration> sslTrustConfiguration;
 
         public Builder() {
             hosts = ImmutableList.builder();
@@ -120,6 +235,7 @@ public class ElasticSearchConfiguration {
             requestTimeout = Optional.empty();
             hostScheme = Optional.empty();
             credential = Optional.empty();
+            sslTrustConfiguration = Optional.empty();
         }
 
         public Builder addHost(Host host) {
@@ -175,6 +291,16 @@ public class ElasticSearchConfiguration {
             return this;
         }
 
+        public Builder sslTrustConfiguration(SSLTrustConfiguration sslTrustConfiguration) {
+            this.sslTrustConfiguration = Optional.ofNullable(sslTrustConfiguration);
+            return this;
+        }
+
+        public Builder sslTrustConfiguration(Optional<SSLTrustConfiguration> sslTrustStore) {
+            this.sslTrustConfiguration = sslTrustStore;
+            return this;
+        }
+
         public ElasticSearchConfiguration build() {
             ImmutableList<Host> hosts = this.hosts.build();
             Preconditions.checkState(!hosts.isEmpty(), "You need to specify ElasticSearch host");
@@ -187,7 +313,8 @@ public class ElasticSearchConfiguration {
                 maxRetries.orElse(DEFAULT_CONNECTION_MAX_RETRIES),
                 requestTimeout.orElse(DEFAULT_REQUEST_TIMEOUT),
                 hostScheme.orElse(DEFAULT_SCHEME),
-                credential);
+                credential,
+                sslTrustConfiguration.orElse(DEFAULT_SSL_TRUST_CONFIGURATION));
         }
     }
 
@@ -199,6 +326,9 @@ public class ElasticSearchConfiguration {
     public static final String ELASTICSEARCH_MASTER_HOST = "elasticsearch.masterHost";
     public static final String ELASTICSEARCH_PORT = "elasticsearch.port";
     public static final String ELASTICSEARCH_HOST_SCHEME = "elasticsearch.hostScheme";
+    public static final String ELASTICSEARCH_HTTPS_SSL_VALIDATION_STRATEGY = "elasticsearch.hostScheme.https.sslValidationStrategy";
+    public static final String ELASTICSEARCH_HTTPS_TRUST_STORE_PATH = "elasticsearch.hostScheme.https.trustStorePath";
+    public static final String ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD = "elasticsearch.hostScheme.https.trustStorePassword";
     public static final String ELASTICSEARCH_USER = "elasticsearch.user";
     public static final String ELASTICSEARCH_PASSWORD = "elasticsearch.password";
     public static final String ELASTICSEARCH_NB_REPLICA = "elasticsearch.nb.replica";
@@ -217,6 +347,7 @@ public class ElasticSearchConfiguration {
     public static final String LOCALHOST = "127.0.0.1";
     public static final Optional<Integer> DEFAULT_PORT_AS_OPTIONAL = Optional.of(DEFAULT_PORT);
     public static final HostScheme DEFAULT_SCHEME = HostScheme.HTTP;
+    public static final SSLTrustConfiguration DEFAULT_SSL_TRUST_CONFIGURATION = SSLTrustConfiguration.defaultBehavior();
 
     public static final ElasticSearchConfiguration DEFAULT_CONFIGURATION = builder()
         .addHost(Host.from(LOCALHOST, DEFAULT_PORT))
@@ -227,6 +358,7 @@ public class ElasticSearchConfiguration {
             .addHosts(getHosts(configuration))
             .hostScheme(getHostScheme(configuration))
             .credential(getCredential(configuration))
+            .sslTrustConfiguration(sslTrustConfiguration(configuration))
             .nbShards(configuration.getInteger(ELASTICSEARCH_NB_SHARDS, DEFAULT_NB_SHARDS))
             .nbReplica(configuration.getInteger(ELASTICSEARCH_NB_REPLICA, DEFAULT_NB_REPLICA))
             .waitForActiveShards(configuration.getInteger(WAIT_FOR_ACTIVE_SHARDS, DEFAULT_WAIT_FOR_ACTIVE_SHARDS))
@@ -235,6 +367,23 @@ public class ElasticSearchConfiguration {
             .build();
     }
 
+    private static Optional<SSLTrustConfiguration> sslTrustConfiguration(Configuration configuration) {
+        return Optional.ofNullable(configuration.getString(ELASTICSEARCH_HTTPS_SSL_VALIDATION_STRATEGY))
+            .map(SSLValidationStrategy::from)
+            .map(strategy -> new SSLTrustConfiguration(strategy, getSSLTrustStore(configuration)));
+    }
+
+    private static Optional<SSLTrustStore> getSSLTrustStore(Configuration configuration) {
+        String trustStorePath = configuration.getString(ELASTICSEARCH_HTTPS_TRUST_STORE_PATH);
+        String trustStorePassword = configuration.getString(ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD);
+
+        if (trustStorePath == null && trustStorePassword == null) {
+            return Optional.empty();
+        }
+
+        return Optional.of(SSLTrustStore.of(trustStorePath, trustStorePassword));
+    }
+
     private static Optional<HostScheme> getHostScheme(Configuration configuration) {
         return Optional.ofNullable(configuration.getString(ELASTICSEARCH_HOST_SCHEME))
             .map(HostScheme::of);
@@ -295,9 +444,10 @@ public class ElasticSearchConfiguration {
     private final Duration requestTimeout;
     private final HostScheme hostScheme;
     private final Optional<Credential> credential;
+    private final SSLTrustConfiguration sslTrustConfiguration;
 
     private ElasticSearchConfiguration(ImmutableList<Host> hosts, int nbShards, int nbReplica, int waitForActiveShards, int minDelay, int maxRetries, Duration requestTimeout,
-                                       HostScheme hostScheme, Optional<Credential> credential) {
+                                       HostScheme hostScheme, Optional<Credential> credential, SSLTrustConfiguration sslTrustConfiguration) {
         this.hosts = hosts;
         this.nbShards = nbShards;
         this.nbReplica = nbReplica;
@@ -307,6 +457,7 @@ public class ElasticSearchConfiguration {
         this.requestTimeout = requestTimeout;
         this.hostScheme = hostScheme;
         this.credential = credential;
+        this.sslTrustConfiguration = sslTrustConfiguration;
     }
 
     public ImmutableList<Host> getHosts() {
@@ -345,6 +496,10 @@ public class ElasticSearchConfiguration {
         return credential;
     }
 
+    public SSLTrustConfiguration getSslTrustConfiguration() {
+        return sslTrustConfiguration;
+    }
+
     @Override
     public final boolean equals(Object o) {
         if (o instanceof ElasticSearchConfiguration) {
@@ -358,13 +513,15 @@ public class ElasticSearchConfiguration {
                 && Objects.equals(this.hosts, that.hosts)
                 && Objects.equals(this.requestTimeout, that.requestTimeout)
                 && Objects.equals(this.hostScheme, that.hostScheme)
-                && Objects.equals(this.credential, that.credential);
+                && Objects.equals(this.credential, that.credential)
+                && Objects.equals(this.sslTrustConfiguration, that.sslTrustConfiguration);
         }
         return false;
     }
 
     @Override
     public final int hashCode() {
-        return Objects.hash(hosts, nbShards, nbReplica, waitForActiveShards, minDelay, maxRetries, requestTimeout, hostScheme, credential);
+        return Objects.hash(hosts, nbShards, nbReplica, waitForActiveShards, minDelay, maxRetries, requestTimeout,
+            hostScheme, credential, sslTrustConfiguration);
     }
 }
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
index 5013204..6cb1867 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
@@ -29,6 +29,8 @@ import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.backends.es.ElasticSearchConfiguration.Credential;
 import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
 import org.apache.james.util.Host;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -59,6 +61,141 @@ class ElasticSearchConfigurationTest {
         }
     }
 
+    @Nested
+    class SSLTrustConfigurationTest {
+
+        @Test
+        void sslTrustStoreShouldMatchBeanContact() {
+            EqualsVerifier.forClass(SSLTrustStore.class)
+                .verify();
+        }
+
+        @Test
+        void shouldMatchBeanContact() {
+            EqualsVerifier.forClass(SSLTrustConfiguration.class)
+                .verify();
+        }
+
+        @Nested
+        class WithSSLValidationStrategy {
+
+            @Test
+            void getSSLConfigurationShouldReturnDefaultValueWhenEmpty() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslTrustConfiguration())
+                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+            }
+
+            @Test
+            void getSSLConfigurationShouldAcceptCaseInsensitiveStrategy() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "DEfault");
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslTrustConfiguration())
+                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+            }
+
+            @Test
+            void fromPropertiesShouldThrowWhenInvalidStrategy() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "invalid");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("invalid strategy 'invalid'");
+            }
+        }
+
+        @Nested
+        class WhenDefault {
+
+            @Test
+            void getSSLConfigurationShouldReturnConfiguredValue() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "default");
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslTrustConfiguration())
+                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+            }
+        }
+
+        @Nested
+        class WhenIgnore {
+
+            @Test
+            void getSSLConfigurationShouldReturnConfiguredValue() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "ignore");
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslTrustConfiguration())
+                    .isEqualTo(SSLTrustConfiguration.ignore());
+            }
+        }
+
+        @Nested
+        class WhenOverride {
+
+            @Test
+            void fromPropertiesShouldThrowWhenOnlyTrustStorePathProvided() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePath", "/home/james/ServerTrustStore.jks");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(NullPointerException.class)
+                    .hasMessage("password cannot be null when filePath is specified");
+            }
+
+            @Test
+            void fromPropertiesShouldThrowWhenOnlyTrustStorePasswordProvided() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePassword", "secret");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(NullPointerException.class)
+                    .hasMessage("filePath cannot be null when password is specified");
+            }
+
+            @Test
+            void getSSLConfigurationShouldReturnConfiguredValue() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                String strategy = "override";
+                String trustStorePath = "/home/james/ServerTrustStore.jks";
+                String trustStorePassword = "secret";
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", strategy);
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePath", trustStorePath);
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePassword", trustStorePassword);
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslTrustConfiguration())
+                    .isEqualTo(SSLTrustConfiguration.override(
+                        SSLTrustStore.of(trustStorePath, trustStorePassword)));
+            }
+        }
+    }
+
     @Test
     void elasticSearchConfigurationShouldRespectBeanContract() {
         EqualsVerifier.forClass(ElasticSearchConfiguration.class)


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 02/07: JAMES-2905 DockerElasticSearch now has two types

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 870798f43e11be79bbcfdce8ed4e7ccc40f16cf0
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Nov 12 16:35:47 2019 +0700

    JAMES-2905 DockerElasticSearch now has two types
    
     - No authentication
     - Has authentication
---
 .../james/backends/es/DockerElasticSearch.java     | 309 +++++++++++++++++----
 .../backends/es/DockerElasticSearchSingleton.java  |   2 +-
 .../backends/es/ElasticSearchClusterExtension.java | 115 ++++++++
 .../src/test/resources/auth-es/NginxDockerfile     |   5 +
 .../src/test/resources/auth-es/README.md           |  30 ++
 .../src/test/resources/auth-es/default.crt         |  19 ++
 .../src/test/resources/auth-es/default.key         |  27 ++
 .../src/test/resources/auth-es/nginx-conf/passwd   |   2 +
 .../auth-es/nginx-conf/reverse_elasticsearch.conf  |  12 +
 .../src/test/resources/auth-es/server.jks          | Bin 0 -> 1074 bytes
 ...esWithNonCompatibleElasticSearchServerTest.java |   2 +-
 .../apache/james/metric/es/ES2ReporterTest.java    |   2 +-
 .../apache/james/metric/es/ES6ReporterTest.java    |   2 +-
 .../apache/james/util/docker/DockerContainer.java  |  11 +
 14 files changed, 487 insertions(+), 51 deletions(-)

diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
index e3365b2..2424477 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
@@ -19,33 +19,98 @@
 
 package org.apache.james.backends.es;
 
+import static org.apache.james.backends.es.DockerElasticSearch.Fixture.ES_HTTP_PORT;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.cert.X509Certificate;
 import java.time.Duration;
 import java.util.Optional;
 
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
 import org.apache.http.HttpStatus;
+import org.apache.james.backends.es.ElasticSearchConfiguration.Credential;
+import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
 import org.apache.james.util.Host;
 import org.apache.james.util.docker.DockerContainer;
 import org.apache.james.util.docker.Images;
 import org.apache.james.util.docker.RateLimiters;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
 import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy;
+import org.testcontainers.images.builder.ImageFromDockerfile;
 
 import com.google.common.collect.ImmutableMap;
 
+import feign.Client;
 import feign.Feign;
 import feign.Logger;
 import feign.RequestLine;
 import feign.Response;
+import feign.auth.BasicAuthRequestInterceptor;
 import feign.slf4j.Slf4jLogger;
 
-public class DockerElasticSearch {
+public interface DockerElasticSearch {
 
     interface ElasticSearchAPI {
 
-        static ElasticSearchAPI from(Host esHttpHost) {
-            return Feign.builder()
-                .logger(new Slf4jLogger(ElasticSearchAPI.class))
-                .logLevel(Logger.Level.FULL)
-                .target(ElasticSearchAPI.class, "http://" + esHttpHost.getHostName() + ":" + esHttpHost.getPort());
+        class Builder {
+            private static final HostnameVerifier ACCEPT_ANY_HOST = (hostname1, sslSession) -> true;
+            private static final TrustManager[] TRUST_ALL = new TrustManager[] {
+                new X509TrustManager() {
+
+                    public X509Certificate[] getAcceptedIssuers() {
+                        return new X509Certificate[0];
+                    }
+                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
+                    }
+                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
+                    }
+                }
+            };
+
+            private final Feign.Builder requestBuilder;
+            private final URL esURL;
+
+            public Builder(URL esURL) {
+                this.esURL = esURL;
+                this.requestBuilder = Feign.builder()
+                    .logger(new Slf4jLogger(ElasticSearchAPI.class))
+                    .logLevel(Logger.Level.FULL);
+            }
+
+            public Builder credential(Credential credential) {
+                requestBuilder.requestInterceptor(
+                    new BasicAuthRequestInterceptor(credential.getUsername(), credential.getPassword()));
+                return this;
+            }
+
+            public Builder disableSSLValidation() throws Exception {
+                SSLContext sc = SSLContext.getInstance("SSL");
+                sc.init(null, TRUST_ALL, new java.security.SecureRandom());
+                SSLSocketFactory factory = sc.getSocketFactory();
+                HttpsURLConnection.setDefaultSSLSocketFactory(factory);
+                Client ignoredSSLClient = new Client.Default(factory, ACCEPT_ANY_HOST);
+
+                requestBuilder.client(ignoredSSLClient);
+
+                return this;
+            }
+
+            public ElasticSearchAPI build() {
+                return requestBuilder.target(ElasticSearchAPI.class, esURL.toString());
+            }
+        }
+
+        static Builder builder(URL esURL) {
+            return new Builder(esURL);
         }
 
         @RequestLine("DELETE /_all")
@@ -55,85 +120,235 @@ public class DockerElasticSearch {
         Response flush();
     }
 
-    private static final int ES_HTTP_PORT = 9200;
+    interface Fixture {
+        int ES_HTTP_PORT = 9200;
+    }
 
-    private final DockerContainer eSContainer;
+    class NoAuth implements DockerElasticSearch {
 
-    public DockerElasticSearch() {
-        this(Images.ELASTICSEARCH_6);
-    }
+        static DockerContainer defaultContainer(String imageName) {
+            return DockerContainer.fromName(imageName)
+                .withTmpFs(ImmutableMap.of("/usr/share/elasticsearch/data", "rw,size=200m"))
+                .withExposedPorts(ES_HTTP_PORT)
+                .withEnv("discovery.type", "single-node")
+                .withAffinityToContainer()
+                .waitingFor(new HostPortWaitStrategy().withRateLimiter(RateLimiters.TWENTIES_PER_SECOND));
+        }
 
-    public DockerElasticSearch(String imageName) {
-        this.eSContainer = DockerContainer.fromName(imageName)
-            .withTmpFs(ImmutableMap.of("/usr/share/elasticsearch/data", "rw,size=200m"))
-            .withExposedPorts(ES_HTTP_PORT)
-            .withEnv("discovery.type", "single-node")
-            .withAffinityToContainer()
-            .waitingFor(new HostPortWaitStrategy().withRateLimiter(RateLimiters.TWENTIES_PER_SECOND));
-    }
+        private final DockerContainer eSContainer;
 
-    public void start() {
-        if (!eSContainer.isRunning()) {
-            eSContainer.start();
+        public NoAuth() {
+            this(Images.ELASTICSEARCH_6);
         }
-    }
 
-    public void stop() {
-        eSContainer.stop();
-    }
+        public NoAuth(String imageName) {
+            this.eSContainer = defaultContainer(imageName);
+        }
 
-    public int getHttpPort() {
-        return eSContainer.getMappedPort(ES_HTTP_PORT);
-    }
+        public NoAuth(DockerContainer eSContainer) {
+            this.eSContainer = eSContainer;
+        }
+
+        public void start() {
+            if (!isRunning()) {
+                eSContainer.start();
+            }
+        }
+
+        public void stop() {
+            eSContainer.stop();
+        }
+
+        public int getHttpPort() {
+            return eSContainer.getMappedPort(ES_HTTP_PORT);
+        }
+
+        public String getIp() {
+            return eSContainer.getHostIp();
+        }
+
+        public Host getHttpHost() {
+            return Host.from(getIp(), getHttpPort());
+        }
+
+        public void pause() {
+            eSContainer.pause();
+        }
+
+        public void unpause() {
+            eSContainer.unpause();
+        }
 
-    public String getIp() {
-        return eSContainer.getHostIp();
+        @Override
+        public boolean isRunning() {
+            return eSContainer.isRunning();
+        }
     }
 
-    public Host getHttpHost() {
-        return Host.from(getIp(), getHttpPort());
+    class WithAuth implements DockerElasticSearch {
+
+        private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(WithAuth.class);
+
+        private static final String DEFAULT_USERNAME = "elasticsearch";
+        private static final String DEFAULT_PASSWORD = "secret";
+        public static final Credential DEFAULT_CREDENTIAL =
+            Credential.of(DEFAULT_USERNAME, DEFAULT_PASSWORD);
+
+        private final DockerElasticSearch.NoAuth elasticSearch;
+        private final DockerContainer nginx;
+        private final Network network;
+
+        public WithAuth() {
+            this(Images.ELASTICSEARCH_6);
+        }
+
+        WithAuth(String imageName) {
+            this.network = Network.newNetwork();
+            this.elasticSearch = new DockerElasticSearch.NoAuth(
+                DockerElasticSearch.NoAuth
+                    .defaultContainer(imageName)
+                    .withLogConsumer(frame -> LOGGER.debug("[ElasticSearch] " + frame.getUtf8String()))
+                    .withNetwork(network)
+                    .withNetworkAliases("elasticsearch"));
+
+            this.nginx = new DockerContainer(
+                    new GenericContainer<>(
+                        new ImageFromDockerfile()
+                        .withFileFromClasspath("conf/nginx-conf/", "auth-es/nginx-conf/")
+                        .withFileFromClasspath("conf/default.crt", "auth-es/default.crt")
+                        .withFileFromClasspath("conf/default.key", "auth-es/default.key")
+                        .withFileFromClasspath("Dockerfile", "auth-es/NginxDockerfile")))
+                .withExposedPorts(ES_HTTP_PORT)
+                .withLogConsumer(frame -> LOGGER.debug("[NGINX] " + frame.getUtf8String()))
+                .withNetwork(network);
+        }
+
+
+        public void start() {
+            elasticSearch.start();
+            nginx.start();
+        }
+
+        public void stop() {
+            nginx.stop();
+            elasticSearch.stop();
+        }
+
+        public int getHttpPort() {
+            return nginx.getMappedPort(ES_HTTP_PORT);
+        }
+
+        public String getIp() {
+            return nginx.getHostIp();
+        }
+
+        @Override
+        public ElasticSearchAPI esAPI() {
+            try {
+                return ElasticSearchAPI.builder(getUrl())
+                    .credential(DEFAULT_CREDENTIAL)
+                    .disableSSLValidation()
+                    .build();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public URL getUrl() {
+            try {
+                return new URL("https://" + getIp() + ":" + getHttpPort());
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        public ElasticSearchConfiguration configuration(Optional<Duration> requestTimeout) {
+            return configurationBuilder(requestTimeout)
+                .hostScheme(Optional.of(HostScheme.HTTPS))
+                .build();
+        }
+
+        public void pause() {
+            nginx.pause();
+            elasticSearch.pause();
+        }
+
+        public void unpause() {
+            elasticSearch.unpause();
+            nginx.unpause();
+        }
+
+        @Override
+        public boolean isRunning() {
+            return nginx.isRunning() && elasticSearch.isRunning();
+        }
     }
 
-    public void pause() {
-        eSContainer.pause();
+    void start();
+
+    void stop();
+
+    int getHttpPort();
+
+    String getIp();
+
+    void pause();
+
+    void unpause();
+
+    boolean isRunning();
+
+    default URL getUrl() {
+        try {
+            return new URL("http://" + getIp() + ":" + getHttpPort());
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
     }
 
-    public void unpause() {
-        eSContainer.unpause();
+    default Host getHttpHost() {
+        return Host.from(getIp(), getHttpPort());
     }
 
-    public void cleanUpData() {
+    default void cleanUpData() {
         if (esAPI().deleteAllIndexes().status() != HttpStatus.SC_OK) {
             throw new IllegalStateException("Failed to delete all data from ElasticSearch");
         }
     }
 
-    public void flushIndices() {
+    default void flushIndices() {
         if (esAPI().flush().status() != HttpStatus.SC_OK) {
             throw new IllegalStateException("Failed to flush ElasticSearch");
         }
     }
 
-    public ElasticSearchConfiguration configuration(Optional<Duration> requestTimeout) {
+    default ElasticSearchConfiguration configuration(Optional<Duration> requestTimeout) {
+        return configurationBuilder(requestTimeout)
+            .build();
+    }
+
+    default ElasticSearchConfiguration.Builder configurationBuilder(Optional<Duration> requestTimeout) {
         return ElasticSearchConfiguration.builder()
             .addHost(getHttpHost())
-            .requestTimeout(requestTimeout)
-            .build();
+            .requestTimeout(requestTimeout);
     }
 
-    public ElasticSearchConfiguration configuration() {
+    default ElasticSearchConfiguration configuration() {
         return configuration(Optional.empty());
     }
 
-    public ClientProvider clientProvider() {
+    default ClientProvider clientProvider() {
         return new ClientProvider(configuration(Optional.empty()));
     }
 
-    public ClientProvider clientProvider(Duration requestTimeout) {
+    default ClientProvider clientProvider(Duration requestTimeout) {
         return new ClientProvider(configuration(Optional.of(requestTimeout)));
     }
 
-    private ElasticSearchAPI esAPI() {
-        return ElasticSearchAPI.from(getHttpHost());
+    default ElasticSearchAPI esAPI() {
+        return ElasticSearchAPI.builder(getUrl())
+            .build();
     }
 }
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearchSingleton.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearchSingleton.java
index e3d409a..2175b34 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearchSingleton.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearchSingleton.java
@@ -20,7 +20,7 @@
 package org.apache.james.backends.es;
 
 public class DockerElasticSearchSingleton {
-    public static DockerElasticSearch INSTANCE = new DockerElasticSearch();
+    public static DockerElasticSearch INSTANCE = new DockerElasticSearch.NoAuth();
 
     static {
         INSTANCE.start();
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
new file mode 100644
index 0000000..3e73fe0
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
@@ -0,0 +1,115 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.backends.es;
+
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.apache.james.util.Host;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolutionException;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+class ElasticSearchClusterExtension implements AfterAllCallback, BeforeAllCallback, AfterEachCallback, ParameterResolver {
+
+    static class ElasticSearchCluster {
+        DockerElasticSearch es1;
+        DockerElasticSearch es2;
+
+        ElasticSearchCluster(DockerElasticSearch es1, DockerElasticSearch es2) {
+            this.es1 = es1;
+            this.es2 = es2;
+        }
+
+        void start() {
+            doInParallel(es1::start, es2::start);
+        }
+
+        void cleanUp() {
+            doInParallel(() -> {
+                    if (es1.isRunning()) {
+                        es1.cleanUpData();
+                }},
+                () -> {
+                    if (es1.isRunning()) {
+                        es1.cleanUpData();
+                }});
+        }
+
+        void stop() {
+            doInParallel(es2::stop);
+        }
+
+        List<Host> getHosts() {
+            return ImmutableList.of(es1.getHttpHost(), es2.getHttpHost());
+        }
+
+        private void doInParallel(Runnable...runnables) {
+            Flux.fromStream(Stream.of(runnables)
+                    .map(Mono::fromRunnable))
+                .parallel(runnables.length)
+                .runOn(Schedulers.boundedElastic())
+                .flatMap(Function.identity())
+                .then()
+                .block();
+        }
+    }
+    
+    private final ElasticSearchCluster esCluster;
+
+    ElasticSearchClusterExtension(ElasticSearchCluster esCluster) {
+        this.esCluster = esCluster;
+    }
+
+    @Override
+    public void beforeAll(ExtensionContext extensionContext) throws Exception {
+        esCluster.start();
+    }
+
+    @Override
+    public void afterEach(ExtensionContext extensionContext) throws Exception {
+        esCluster.cleanUp();
+    }
+
+    @Override
+    public void afterAll(ExtensionContext extensionContext) throws Exception {
+        esCluster.stop();
+    }
+
+    @Override
+    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+        return parameterContext.getParameter().getType() == ElasticSearchCluster.class;
+    }
+
+    @Override
+    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
+        return esCluster;
+    }
+}
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/NginxDockerfile b/backends-common/elasticsearch/src/test/resources/auth-es/NginxDockerfile
new file mode 100644
index 0000000..5a0f976
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/NginxDockerfile
@@ -0,0 +1,5 @@
+FROM nginx:1.15.1
+
+COPY conf/nginx-conf/ /etc/nginx/conf.d/
+COPY conf/default.crt /etc/ssl/certs/default.crt
+COPY conf/default.key /etc/ssl/private/default.key
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/README.md b/backends-common/elasticsearch/src/test/resources/auth-es/README.md
new file mode 100644
index 0000000..b7d945e
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/README.md
@@ -0,0 +1,30 @@
+## Resources explanation
+
+### nginx-conf
+
+Contains nginx configuration files:
+ - reverse_elasticsearch.conf: allow nginx to be the proxy connecting to ElasticSearch
+ - passwd: Nginx credentials file store, each record follow the format: `username:encrypted-password`
+
+### default.crt & default.key
+
+public (.crt) and private (.key) of the self signed SSL certification. It will be loaded by nginx
+
+### server.jks
+
+Once you use a http client connect to nginx by `https` protocol, 
+the default behavior of the client is rejecting the connection because 
+self signed SSL certification provided by nginx is not recognized by the 
+client. To deal with this problem, there are two ways to configure the client:
+
+ - Not recommended in production, ignore SSL Validation
+ - Configure the client to use local TrustStore file containing the .crt, 
+ then it should trust nginx.
+ 
+The `server.jks` is generated by the command
+```
+keytool -import -v -trustcacerts -file default.crt -keystore server.jks -keypass mypass -storepass mypass
+```
+
+With: 
+ - password: `mypass`
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/default.crt b/backends-common/elasticsearch/src/test/resources/auth-es/default.crt
new file mode 100644
index 0000000..d9aa6ca
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/default.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe4CCQDbp5K1cKRJgjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMB4XDTE4MDcwMjEyNDkxMloXDTE5MDcwMjEyNDkxMlowRTELMAkG
+A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
+IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AMMgzSF3uTnjSPwX9i1FEhdpKCDWAfWdg/arMWI1wdNjaz5jwA0Y4hPXN14ArIkH
+ZCb9NPJZbUzcryeTvwEbZ8zqQ5+ghiGfGh9PFcz+RArOY2pPnIHOXVZaBIWxkzlF
+atdJodNhbkxt+gmPi5xFlwDqPMijnAOI4gLcxQPG7mWyRpoD8YUzmMNFTJr1xFAV
+XcJU+FJPP9zhOFRkGXnDupp8z9RYew+b39VGga6zHFGIP+iIMVJnEmW2/TQ1KE52
+b5B45bfGxlF7k003kIeaZsHbaIshdNMIz+w4bzywXNXyAfZccRGfraNXuSv0jAPS
+O+nF9yyAocnz3QXOsmSGz28CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAFRDXedF9
+hjuEmLjETBwhh5Zgg102GDJp58ZBkLE1IaT4mrbK90mhpzxsaxbuqOdu3PXJV4tc
+SEocxj5hk5ivVZiWk/j8YJmB8ZOUKdeOxc1tfratlonJ85AY+6m02+FhjUnWobmg
+HVBepFTJt1iaTgOpVf8YpbYW+L6+kWV9C2CtT3WUhxtllKjokbF+YVN7LnDlbynM
+80+aGm+rdg+TUusg+T/2FeVlDyBgbqagBcCQCQacWD2+zzL28XB3PwRE6oU/tKuu
+rbCsGZE0WH8Pc7p6J/alRr+foQiu7KKJKJ8pO1qdy716DlmPIDN23iZROIu1GKKl
+ZIKfyefPUcwHyw==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/default.key b/backends-common/elasticsearch/src/test/resources/auth-es/default.key
new file mode 100644
index 0000000..ebc29bb
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/default.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAwyDNIXe5OeNI/Bf2LUUSF2koINYB9Z2D9qsxYjXB02NrPmPA
+DRjiE9c3XgCsiQdkJv008lltTNyvJ5O/ARtnzOpDn6CGIZ8aH08VzP5ECs5jak+c
+gc5dVloEhbGTOUVq10mh02FuTG36CY+LnEWXAOo8yKOcA4jiAtzFA8buZbJGmgPx
+hTOYw0VMmvXEUBVdwlT4Uk8/3OE4VGQZecO6mnzP1Fh7D5vf1UaBrrMcUYg/6Igx
+UmcSZbb9NDUoTnZvkHjlt8bGUXuTTTeQh5pmwdtoiyF00wjP7DhvPLBc1fIB9lxx
+EZ+to1e5K/SMA9I76cX3LIChyfPdBc6yZIbPbwIDAQABAoIBAQC+DHmeyjQPDK8u
+UcxE5UwsnxAlgysfZY59Ntn/dKpW0DNI7fDFEOJJPsm/ddqWECvcyavDC5SoUx0J
+6B4V3vJnpBe/JqLdpk/UBLwQD+qHt+MeTXH6/9rq2vwilwT0uAbKrv29mtzJ9ied
+HkF1MFZV3s7bAHdY/f5bmVkOR7g0NQ6QLuXYiLDSHpu/7dMVBNS9qMNL292cZbpi
+KJZUvyrK6jSOh9Dt3fmWVdjRp5nGd/Z0r32A8UdoWiQ3cXe9uwXVxz8+ZGkP4t44
+Ob+YcgHUEz6iI2o7nqWpNtPI+hBWH1YK/Szlay3rTZSKvkNG1Sdwm1UhoJqNFucC
+7ZWkH8GxAoGBAOSpP9lrB0sXCGwhD3xbAYCZsTgomSQ13X6/daKdN9fxpegK7v5r
+baojBoSvIKiKezHzMiL5IiEhESXEvXQigFNORdf9mFs9Z9ec4I/96dQNYM4d1QtM
+nz3dABt62po8ROCA4Y26ar+OmcJ2kv0RPuJCDGkrY1Is7SNTtGi+HW55AoGBANp1
+MJi3VHS9oEF8dslgLkZMZWPgcwJK2Y3wYIkatmxuVkWYdRFPqWWwlXUU17KH7eqm
+iEONLNyI7g/duFFTbz52MkeMpX6xarXddYkS2HMDUqSTgt3T+ULHVhTtlEEBXg6m
+5cx/eg8niJ6VH+vFtRcYrActLg751mjNgz8fmBMnAoGBAMwj4fHsIIXEWWuXlGi0
+IsI59EKr1BTE1isLKS11aN6sSS7BXB7dr/k97drVMrXldBjjArbAbtze1Z0/aNhe
+2OwGrEopiDSkvKl8z+sdh/0duHgroADHdj9Xp3nhE3qxJdi8lyHd9OFxoQpAq/es
+xtRenQ/jgXdizo9EcoM0f7gZAoGARw6ZSQNQyEwpxUM7zBKp2pnc3NjT7+nyy7Vl
+YRM8RRa6nxaQ9ZmIyxfd9WBbLKoEHDAg4IAaGH29mZGP83wFEjcV8anw5r3ErPUk
+1vo3R2nMRtXoWkkpqav29wW0FFTiiDeHRbYtOKKuUI7G/ESpu6J9yjPK6HohYKOA
+Tlbzez8CgYAFhpoeHO2X06pEsFeI6rMtc0rS0EHuVuqfjjyEMOyQZ8BsCWzDZBDW
+N9rxd0yz80H1Q33dwzrkiVu15e+JMNb5I80Ou0t+LQVYpvhtM89yKoLFk2HqL+eH
+wQJ/Fszj8fwdwx+k2DEve8MVHSx/w9CpwM7B2yGxcUFKi/JSM1rQEw==
+-----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/passwd b/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/passwd
new file mode 100644
index 0000000..c489f73
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/passwd
@@ -0,0 +1,2 @@
+# the default credential: elasticsearch:secret
+elasticsearch:$apr1$m5FvKpw7$EZ9UEUCilHyKTIqQ57nWG1
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/reverse_elasticsearch.conf b/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/reverse_elasticsearch.conf
new file mode 100644
index 0000000..77573cf
--- /dev/null
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/nginx-conf/reverse_elasticsearch.conf
@@ -0,0 +1,12 @@
+server {
+    listen 9200 ssl;
+    ssl_certificate /etc/ssl/certs/default.crt;
+    ssl_certificate_key /etc/ssl/private/default.key;
+    server_name reverse_elasticsearch;
+    auth_basic "Restricted Area";
+    auth_basic_user_file /etc/nginx/conf.d/passwd;
+
+    location / {
+        proxy_pass http://elasticsearch:9200;
+    }
+}
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/server.jks b/backends-common/elasticsearch/src/test/resources/auth-es/server.jks
new file mode 100644
index 0000000..10ef996
Binary files /dev/null and b/backends-common/elasticsearch/src/test/resources/auth-es/server.jks differ
diff --git a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/JamesWithNonCompatibleElasticSearchServerTest.java b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/JamesWithNonCompatibleElasticSearchServerTest.java
index 3e16305..ebe9150 100644
--- a/server/container/guice/cassandra-guice/src/test/java/org/apache/james/JamesWithNonCompatibleElasticSearchServerTest.java
+++ b/server/container/guice/cassandra-guice/src/test/java/org/apache/james/JamesWithNonCompatibleElasticSearchServerTest.java
@@ -39,7 +39,7 @@ class JamesWithNonCompatibleElasticSearchServerTest {
 
     private static final int LIMIT_MAX_MESSAGES = 10;
 
-    static DockerElasticSearch dockerES2 = new DockerElasticSearch(Images.ELASTICSEARCH_2);
+    static DockerElasticSearch dockerES2 = new DockerElasticSearch.NoAuth(Images.ELASTICSEARCH_2);
 
     @RegisterExtension
     static JamesServerExtension testExtension = new JamesServerBuilder()
diff --git a/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES2ReporterTest.java b/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES2ReporterTest.java
index eb23804..1e09cf5 100644
--- a/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES2ReporterTest.java
+++ b/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES2ReporterTest.java
@@ -27,5 +27,5 @@ class ES2ReporterTest extends ESReporterContract {
 
     @RegisterExtension
     static DockerElasticSearchExtension testExtension = new DockerElasticSearchExtension(
-        new DockerElasticSearch(Images.ELASTICSEARCH_2));
+        new DockerElasticSearch.NoAuth(Images.ELASTICSEARCH_2));
 }
diff --git a/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES6ReporterTest.java b/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES6ReporterTest.java
index 0c201a4..ab7a9d4 100644
--- a/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES6ReporterTest.java
+++ b/server/container/metrics/metrics-es-reporter/src/test/java/org/apache/james/metric/es/ES6ReporterTest.java
@@ -25,5 +25,5 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 class ES6ReporterTest extends ESReporterContract {
 
     @RegisterExtension
-    static DockerElasticSearchExtension testExtension = new DockerElasticSearchExtension(new DockerElasticSearch());
+    static DockerElasticSearchExtension testExtension = new DockerElasticSearchExtension(new DockerElasticSearch.NoAuth());
 }
diff --git a/server/testing/src/main/java/org/apache/james/util/docker/DockerContainer.java b/server/testing/src/main/java/org/apache/james/util/docker/DockerContainer.java
index 07904a1..73b9162 100644
--- a/server/testing/src/main/java/org/apache/james/util/docker/DockerContainer.java
+++ b/server/testing/src/main/java/org/apache/james/util/docker/DockerContainer.java
@@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory;
 import org.testcontainers.DockerClientFactory;
 import org.testcontainers.containers.Container;
 import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.Network;
 import org.testcontainers.containers.output.OutputFrame;
 import org.testcontainers.containers.wait.strategy.WaitStrategy;
 import org.testcontainers.images.builder.ImageFromDockerfile;
@@ -81,6 +82,16 @@ public class DockerContainer implements TestRule {
         return this;
     }
 
+    public DockerContainer withNetwork(Network network) {
+        container.withNetwork(network);
+        return this;
+    }
+
+    public DockerContainer withNetworkAliases(String... aliases) {
+        container.withNetworkAliases(aliases);
+        return this;
+    }
+
     public DockerContainer withLogConsumer(Consumer<OutputFrame> consumer) {
         container.withLogConsumer(consumer);
         return this;


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 05/07: JAMES-2905 Update documentation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit e60d88ef8b8706be3cd954320253aaec3449d066
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Nov 12 16:37:17 2019 +0700

    JAMES-2905 Update documentation
---
 .../destination/conf/elasticsearch.properties      | 18 +++++-
 .../destination/conf/elasticsearch.properties      | 17 ++++++
 .../destination/conf/elasticsearch.properties      | 18 +++++-
 .../destination/conf/elasticsearch.properties      | 17 ++++++
 src/site/xdoc/server/config-elasticsearch.xml      | 66 +++++++++++++++++++++-
 5 files changed, 131 insertions(+), 5 deletions(-)

diff --git a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
index 8302e15..f28c38a 100644
--- a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
@@ -25,8 +25,22 @@
 elasticsearch.masterHost=elasticsearch
 elasticsearch.port=9200
 
-# Optional. Only http or https are accepted, default is http
-# elasticsearch.hostScheme=http
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# elasticsearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
 # Optional.
 # Basic auth username to access elasticsearch.
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
index 69c0eee..3490e61 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
@@ -27,6 +27,23 @@ elasticsearch.port=9200
 # Optional. Only http or https are accepted, default is http
 # elasticsearch.hostScheme=http
 
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# elasticsearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
index 69c0eee..7c23c72 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
@@ -24,8 +24,22 @@
 elasticsearch.masterHost=elasticsearch
 elasticsearch.port=9200
 
-# Optional. Only http or https are accepted, default is http
-# elasticsearch.hostScheme=http
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# elasticsearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
 # Optional.
 # Basic auth username to access elasticsearch.
diff --git a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
index 8302e15..077e76c 100644
--- a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
@@ -28,6 +28,23 @@ elasticsearch.port=9200
 # Optional. Only http or https are accepted, default is http
 # elasticsearch.hostScheme=http
 
+# Optional, default is `default`
+# Choosing the SSL check strategy when using https scheme
+# default: Use the default SSL TrustStore of the system.
+# ignore: Ignore SSL Validation check (not recommended).
+# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# elasticsearch.hostScheme.https.sslValidationStrategy=default
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePath=/file/to/trust/keystore.jks
+
+# Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
+# Configure Elasticsearch rest client to use this trustStore file with the specified password.
+# You need to specify both trustStorePath and trustStorePassword
+# elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/src/site/xdoc/server/config-elasticsearch.xml b/src/site/xdoc/server/config-elasticsearch.xml
index b7a6213..77f0fc4 100644
--- a/src/site/xdoc/server/config-elasticsearch.xml
+++ b/src/site/xdoc/server/config-elasticsearch.xml
@@ -40,7 +40,11 @@
           <dd>Is the port of ElasticSearch master</dd>
 
           <dt><strong>elasticsearch.hostScheme</strong></dt>
-          <dd>Optional. Only http or https are accepted, default is http</dd>
+          <dd>
+              Optional. Only http or https are accepted, default is http. In case of <strong>https</strong>,
+              and you want to override the default SSL Validation behavior of the client,
+              consult the section <strong>SSL Trusting Configuration</strong> for more details.
+          </dd>
 
           <dt><strong>elasticsearch.user</strong></dt>
           <dd>
@@ -186,6 +190,66 @@
 
     </section>
 
+    <section name="SSL Trusting Configuration">
+
+        <p>
+            By default James will use the system TrustStore to validate https server certificates, if the certificate on
+            ES side is already in the system TrustStore, you can leave the sslValidationStrategy property empty or set it to default.
+        </p>
+
+        <dl>
+            <dt><strong>elasticsearch.hostScheme.https.sslValidationStrategy</strong></dt>
+            <dd>
+              Optional. Accept only <strong>default</strong>, <strong>ignore</strong>, <strong>override</strong>. Default is <strong>default</strong>
+            </dd>
+            <dd>
+                default: Use the default SSL TrustStore of the system.
+                ignore: Ignore SSL Validation check (not recommended).
+                override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+            </dd>
+        </dl>
+
+        <p>
+            In some cases, you want to secure ES to protect it from unauthorized requests,
+            assuming with the ES is using <strong>https</strong> with a self signed certificate.
+            Which means you should configure the ES RestHighLevelClient to trust your self signed certificate.
+
+            There are two ways on client side: ignoring SSL check or configure to trust the server's certificate.
+            In case you want to ignore the SSL check, simply, just don't specify below options. Otherwise, configuring the trust
+            requires some prerequisites and they are explained in below block.
+
+            A certificate normally contains two parts: a public part in .crt file, another private part in .key file.
+            To trust the server, the client need to be acknowledged that the server's certificate is in the list of
+            client's TrustStore. Basically, you can create a local TrustStore file containing the public part of a remote server
+            by execute this command:
+        </p>
+
+        <code><pre>
+            keytool -import -v -trustcacerts -file certificatePublicFile.crt -keystore trustStoreFileName.jks -keypass fillThePassword -storepass fillThePassword
+        </pre></code>
+
+        <p>
+            When there is a TrustStore file and the password to read, fill two options <strong>trustStorePath</strong>
+            and <strong>trustStorePassword</strong> with the TrustStore location and the password. ES client will accept
+            the certificate of ES service.
+        </p>
+
+        <dl>
+            <dt><strong>elasticsearch.hostScheme.https.trustStorePath</strong></dt>
+            <dd>
+              Optional. Use it when https is configured in elasticsearch.hostScheme, and sslValidationStrategy is <strong>override</strong>
+              Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
+              Once, you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
+            </dd>
+
+            <dt><strong>elasticsearch.hostScheme.https.trustStorePassword</strong></dt>
+            <dd>
+              Optional. Use it when https is configured in elasticsearch.hostScheme, and sslValidationStrategy is <strong>override</strong>
+              Configure Elasticsearch rest client to use this trustStore file with the specified password.
+              Once, you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
+            </dd>
+        </dl>
+    </section>
 </body>
 
 </document>


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 07/07: JAMES-2905 One more option about HostNameVerifier

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 2e87e12dbce48737c21ad29138e445ceb1b8a35b
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Wed Nov 13 18:20:06 2019 +0700

    JAMES-2905 One more option about HostNameVerifier
---
 .../apache/james/backends/es/ClientProvider.java   |  29 ++++-
 .../backends/es/ElasticSearchConfiguration.java    | 144 ++++++++++++++++-----
 ...iderImplConnectionAuthESIgnoreSSLCheckTest.java |   7 +-
 ...ImplConnectionAuthESOverrideTrustStoreTest.java |  10 +-
 .../es/ElasticSearchConfigurationTest.java         | 118 +++++++++++++----
 .../destination/conf/elasticsearch.properties      |   6 +
 .../destination/conf/elasticsearch.properties      |   6 +
 .../destination/conf/elasticsearch.properties      |   6 +
 .../destination/conf/elasticsearch.properties      |   6 +
 src/site/xdoc/server/config-elasticsearch.xml      |  15 +++
 10 files changed, 282 insertions(+), 65 deletions(-)

diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
index 50a51e7..1ff14cd 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
@@ -38,13 +38,15 @@ import org.apache.http.HttpHost;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.CredentialsProvider;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
 import org.apache.http.ssl.SSLContextBuilder;
 import org.apache.http.ssl.TrustStrategy;
 import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.HostNameVerifier;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLValidationStrategy;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.slf4j.Logger;
@@ -60,7 +62,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
     private static class HttpAsyncClientConfigurer {
 
         private static final TrustStrategy TRUST_ALL = (x509Certificates, authType) -> true;
-        private static final HostnameVerifier ACCEPT_ANY_HOST = (hostname, sslSession) -> true;
+        private static final HostnameVerifier ACCEPT_ANY_HOSTNAME = (hostname, sslSession) -> true;
 
         private final ElasticSearchConfiguration configuration;
 
@@ -94,7 +96,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
             try {
                 builder
                     .setSSLContext(sslContext())
-                    .setSSLHostnameVerifier(ACCEPT_ANY_HOST);
+                    .setSSLHostnameVerifier(hostnameVerifier());
             } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException e) {
                 throw new RuntimeException("Cannot set SSL options to the builder", e);
             }
@@ -105,7 +107,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
 
             SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
 
-            SSLValidationStrategy strategy = configuration.getSslTrustConfiguration()
+            SSLValidationStrategy strategy = configuration.getSslConfiguration()
                 .getStrategy();
 
             switch (strategy) {
@@ -123,10 +125,25 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
             }
         }
 
+        private HostnameVerifier hostnameVerifier() {
+            HostNameVerifier hostnameVerifier = configuration.getSslConfiguration()
+                .getHostNameVerifier();
+
+            switch (hostnameVerifier) {
+                case DEFAULT:
+                    return new DefaultHostnameVerifier();
+                case ACCEPT_ANY_HOSTNAME:
+                    return ACCEPT_ANY_HOSTNAME;
+                default:
+                    throw new NotImplementedException(
+                        String.format("unrecognized HostNameVerifier '%s'", hostnameVerifier.name()));
+            }
+        }
+
         private SSLContextBuilder applyTrustStore(SSLContextBuilder sslContextBuilder) throws CertificateException, NoSuchAlgorithmException,
             KeyStoreException, IOException {
 
-            SSLTrustStore trustStore = configuration.getSslTrustConfiguration()
+            SSLTrustStore trustStore = configuration.getSslConfiguration()
                 .getTrustStore()
                 .orElseThrow(() -> new IllegalStateException("SSLTrustStore cannot to be empty"));
 
diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
index 47681a5..16727ea 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
@@ -19,7 +19,7 @@
 
 package org.apache.james.backends.es;
 
-import static org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy.OVERRIDE;
+import static org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLValidationStrategy.OVERRIDE;
 
 import java.io.File;
 import java.nio.file.Files;
@@ -35,8 +35,9 @@ import java.util.stream.Stream;
 
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.HostNameVerifier;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLValidationStrategy;
 import org.apache.james.util.Host;
 
 import com.github.steveash.guavate.Guavate;
@@ -110,7 +111,7 @@ public class ElasticSearchConfiguration {
         }
     }
 
-    public static class SSLTrustConfiguration {
+    public static class SSLConfiguration {
 
         public enum SSLValidationStrategy {
             DEFAULT,
@@ -128,6 +129,21 @@ public class ElasticSearchConfiguration {
             }
         }
 
+        public enum HostNameVerifier {
+            DEFAULT,
+            ACCEPT_ANY_HOSTNAME;
+
+            static HostNameVerifier from(String rawValue) {
+                Preconditions.checkNotNull(rawValue);
+
+                return Stream.of(values())
+                    .filter(verifier -> verifier.name().equalsIgnoreCase(rawValue))
+                    .findAny()
+                    .orElseThrow(() -> new IllegalArgumentException(String.format("invalid HostNameVerifier '%s'", rawValue)));
+
+            }
+        }
+
         public static class SSLTrustStore {
 
             public static SSLTrustStore of(String filePath, String password) {
@@ -174,28 +190,79 @@ public class ElasticSearchConfiguration {
             }
         }
 
-        static SSLTrustConfiguration defaultBehavior() {
-            return new SSLTrustConfiguration(SSLValidationStrategy.DEFAULT, Optional.empty());
+        static class Builder {
+
+            interface RequireSSLStrategyTrustStore {
+                RequireHostNameVerifier sslStrategy(SSLValidationStrategy strategy, Optional<SSLTrustStore> trustStore);
+
+                default RequireHostNameVerifier strategyIgnore() {
+                    return sslStrategy(SSLValidationStrategy.IGNORE, Optional.empty());
+                }
+
+                default RequireHostNameVerifier strategyOverride(SSLTrustStore trustStore) {
+                    return sslStrategy(SSLValidationStrategy.OVERRIDE, Optional.of(trustStore));
+                }
+
+                default RequireHostNameVerifier strategyDefault() {
+                    return sslStrategy(SSLValidationStrategy.DEFAULT, Optional.empty());
+                }
+            }
+
+            interface RequireHostNameVerifier {
+                ReadyToBuild hostNameVerifier(HostNameVerifier hostNameVerifier);
+
+                default ReadyToBuild acceptAnyHostNameVerifier() {
+                    return hostNameVerifier(HostNameVerifier.ACCEPT_ANY_HOSTNAME);
+                }
+
+                default ReadyToBuild defaultHostNameVerifier() {
+                    return hostNameVerifier(HostNameVerifier.DEFAULT);
+                }
+            }
+
+            static class ReadyToBuild {
+                private final SSLValidationStrategy sslValidationStrategy;
+                private final HostNameVerifier hostNameVerifier;
+                private Optional<SSLTrustStore> sslTrustStore;
+
+                private ReadyToBuild(SSLValidationStrategy sslValidationStrategy, HostNameVerifier hostNameVerifier, Optional<SSLTrustStore> sslTrustStore) {
+                    this.sslValidationStrategy = sslValidationStrategy;
+                    this.hostNameVerifier = hostNameVerifier;
+                    this.sslTrustStore = sslTrustStore;
+                }
+
+                public ReadyToBuild sslTrustStore(SSLTrustStore sslTrustStore) {
+                    this.sslTrustStore = Optional.of(sslTrustStore);
+                    return this;
+                }
+
+                public SSLConfiguration build() {
+                    return new SSLConfiguration(sslValidationStrategy, hostNameVerifier, sslTrustStore);
+                }
+            }
         }
 
-        static SSLTrustConfiguration ignore() {
-            return new SSLTrustConfiguration(SSLValidationStrategy.IGNORE, Optional.empty());
+        static SSLConfiguration defaultBehavior() {
+            return new SSLConfiguration(SSLValidationStrategy.DEFAULT, HostNameVerifier.DEFAULT, Optional.empty());
         }
 
-        static SSLTrustConfiguration override(SSLTrustStore sslTrustStore) {
-            return new SSLTrustConfiguration(OVERRIDE, Optional.of(sslTrustStore));
+        static Builder.RequireSSLStrategyTrustStore builder() {
+            return (strategy, trustStore) -> hostNameVerifier -> new Builder.ReadyToBuild(strategy, hostNameVerifier, trustStore);
         }
 
         private final SSLValidationStrategy strategy;
+        private final HostNameVerifier hostNameVerifier;
         private final Optional<SSLTrustStore> trustStore;
 
-        private SSLTrustConfiguration(SSLValidationStrategy strategy, Optional<SSLTrustStore> trustStore) {
+        private SSLConfiguration(SSLValidationStrategy strategy, HostNameVerifier hostNameVerifier, Optional<SSLTrustStore> trustStore) {
             Preconditions.checkNotNull(strategy);
             Preconditions.checkNotNull(trustStore);
+            Preconditions.checkNotNull(hostNameVerifier);
             Preconditions.checkArgument(strategy != OVERRIDE || trustStore.isPresent(), OVERRIDE.name() + " strategy requires trustStore to be present");
 
             this.strategy = strategy;
             this.trustStore = trustStore;
+            this.hostNameVerifier = hostNameVerifier;
         }
 
         public SSLValidationStrategy getStrategy() {
@@ -206,20 +273,25 @@ public class ElasticSearchConfiguration {
             return trustStore;
         }
 
+        public HostNameVerifier getHostNameVerifier() {
+            return hostNameVerifier;
+        }
+
         @Override
         public final boolean equals(Object o) {
-            if (o instanceof SSLTrustConfiguration) {
-                SSLTrustConfiguration that = (SSLTrustConfiguration) o;
+            if (o instanceof SSLConfiguration) {
+                SSLConfiguration that = (SSLConfiguration) o;
 
                 return Objects.equals(this.strategy, that.strategy)
-                    && Objects.equals(this.trustStore, that.trustStore);
+                    && Objects.equals(this.trustStore, that.trustStore)
+                    && Objects.equals(this.hostNameVerifier, that.hostNameVerifier);
             }
             return false;
         }
 
         @Override
         public final int hashCode() {
-            return Objects.hash(strategy, trustStore);
+            return Objects.hash(strategy, trustStore, hostNameVerifier);
         }
     }
 
@@ -234,7 +306,7 @@ public class ElasticSearchConfiguration {
         private Optional<Duration> requestTimeout;
         private Optional<HostScheme> hostScheme;
         private Optional<Credential> credential;
-        private Optional<SSLTrustConfiguration> sslTrustConfiguration;
+        private Optional<SSLConfiguration> sslTrustConfiguration;
 
         public Builder() {
             hosts = ImmutableList.builder();
@@ -302,12 +374,12 @@ public class ElasticSearchConfiguration {
             return this;
         }
 
-        public Builder sslTrustConfiguration(SSLTrustConfiguration sslTrustConfiguration) {
-            this.sslTrustConfiguration = Optional.of(sslTrustConfiguration);
+        public Builder sslTrustConfiguration(SSLConfiguration sslConfiguration) {
+            this.sslTrustConfiguration = Optional.of(sslConfiguration);
             return this;
         }
 
-        public Builder sslTrustConfiguration(Optional<SSLTrustConfiguration> sslTrustStore) {
+        public Builder sslTrustConfiguration(Optional<SSLConfiguration> sslTrustStore) {
             this.sslTrustConfiguration = sslTrustStore;
             return this;
         }
@@ -338,6 +410,7 @@ public class ElasticSearchConfiguration {
     public static final String ELASTICSEARCH_PORT = "elasticsearch.port";
     public static final String ELASTICSEARCH_HOST_SCHEME = "elasticsearch.hostScheme";
     public static final String ELASTICSEARCH_HTTPS_SSL_VALIDATION_STRATEGY = "elasticsearch.hostScheme.https.sslValidationStrategy";
+    public static final String ELASTICSEARCH_HTTPS_HOSTNAME_VERIFIER = "elasticsearch.hostScheme.https.hostNameVerifier";
     public static final String ELASTICSEARCH_HTTPS_TRUST_STORE_PATH = "elasticsearch.hostScheme.https.trustStorePath";
     public static final String ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD = "elasticsearch.hostScheme.https.trustStorePassword";
     public static final String ELASTICSEARCH_USER = "elasticsearch.user";
@@ -358,7 +431,7 @@ public class ElasticSearchConfiguration {
     public static final String LOCALHOST = "127.0.0.1";
     public static final Optional<Integer> DEFAULT_PORT_AS_OPTIONAL = Optional.of(DEFAULT_PORT);
     public static final HostScheme DEFAULT_SCHEME = HostScheme.HTTP;
-    public static final SSLTrustConfiguration DEFAULT_SSL_TRUST_CONFIGURATION = SSLTrustConfiguration.defaultBehavior();
+    public static final SSLConfiguration DEFAULT_SSL_TRUST_CONFIGURATION = SSLConfiguration.defaultBehavior();
 
     public static final ElasticSearchConfiguration DEFAULT_CONFIGURATION = builder()
         .addHost(Host.from(LOCALHOST, DEFAULT_PORT))
@@ -378,10 +451,21 @@ public class ElasticSearchConfiguration {
             .build();
     }
 
-    private static Optional<SSLTrustConfiguration> sslTrustConfiguration(Configuration configuration) {
-        return Optional.ofNullable(configuration.getString(ELASTICSEARCH_HTTPS_SSL_VALIDATION_STRATEGY))
+    private static SSLConfiguration sslTrustConfiguration(Configuration configuration) {
+        SSLValidationStrategy sslStrategy = Optional
+            .ofNullable(configuration.getString(ELASTICSEARCH_HTTPS_SSL_VALIDATION_STRATEGY))
             .map(SSLValidationStrategy::from)
-            .map(strategy -> new SSLTrustConfiguration(strategy, getSSLTrustStore(configuration)));
+            .orElse(SSLValidationStrategy.DEFAULT);
+
+        HostNameVerifier hostNameVerifier = Optional
+            .ofNullable(configuration.getString(ELASTICSEARCH_HTTPS_HOSTNAME_VERIFIER))
+            .map(HostNameVerifier::from)
+            .orElse(HostNameVerifier.DEFAULT);
+
+        return SSLConfiguration.builder()
+            .sslStrategy(sslStrategy, getSSLTrustStore(configuration))
+            .hostNameVerifier(hostNameVerifier)
+            .build();
     }
 
     private static Optional<SSLTrustStore> getSSLTrustStore(Configuration configuration) {
@@ -455,10 +539,10 @@ public class ElasticSearchConfiguration {
     private final Duration requestTimeout;
     private final HostScheme hostScheme;
     private final Optional<Credential> credential;
-    private final SSLTrustConfiguration sslTrustConfiguration;
+    private final SSLConfiguration sslConfiguration;
 
     private ElasticSearchConfiguration(ImmutableList<Host> hosts, int nbShards, int nbReplica, int waitForActiveShards, int minDelay, int maxRetries, Duration requestTimeout,
-                                       HostScheme hostScheme, Optional<Credential> credential, SSLTrustConfiguration sslTrustConfiguration) {
+                                       HostScheme hostScheme, Optional<Credential> credential, SSLConfiguration sslConfiguration) {
         this.hosts = hosts;
         this.nbShards = nbShards;
         this.nbReplica = nbReplica;
@@ -468,7 +552,7 @@ public class ElasticSearchConfiguration {
         this.requestTimeout = requestTimeout;
         this.hostScheme = hostScheme;
         this.credential = credential;
-        this.sslTrustConfiguration = sslTrustConfiguration;
+        this.sslConfiguration = sslConfiguration;
     }
 
     public ImmutableList<Host> getHosts() {
@@ -507,8 +591,8 @@ public class ElasticSearchConfiguration {
         return credential;
     }
 
-    public SSLTrustConfiguration getSslTrustConfiguration() {
-        return sslTrustConfiguration;
+    public SSLConfiguration getSslConfiguration() {
+        return sslConfiguration;
     }
 
     @Override
@@ -525,7 +609,7 @@ public class ElasticSearchConfiguration {
                 && Objects.equals(this.requestTimeout, that.requestTimeout)
                 && Objects.equals(this.hostScheme, that.hostScheme)
                 && Objects.equals(this.credential, that.credential)
-                && Objects.equals(this.sslTrustConfiguration, that.sslTrustConfiguration);
+                && Objects.equals(this.sslConfiguration, that.sslConfiguration);
         }
         return false;
     }
@@ -533,6 +617,6 @@ public class ElasticSearchConfiguration {
     @Override
     public final int hashCode() {
         return Objects.hash(hosts, nbShards, nbReplica, waitForActiveShards, minDelay, maxRetries, requestTimeout,
-            hostScheme, credential, sslTrustConfiguration);
+            hostScheme, credential, sslConfiguration);
     }
 }
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java
index 1de1996..a97c6a3 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESIgnoreSSLCheckTest.java
@@ -24,7 +24,7 @@ import static org.apache.james.backends.es.ElasticSearchClusterExtension.Elastic
 import java.util.Optional;
 
 import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 class ClientProviderImplConnectionAuthESIgnoreSSLCheckTest implements ClientProviderImplConnectionContract {
@@ -39,6 +39,9 @@ class ClientProviderImplConnectionAuthESIgnoreSSLCheckTest implements ClientProv
         return ElasticSearchConfiguration.builder()
             .credential(Optional.of(DockerElasticSearch.WithAuth.DEFAULT_CREDENTIAL))
             .hostScheme(Optional.of(HostScheme.HTTPS))
-            .sslTrustConfiguration(SSLTrustConfiguration.ignore());
+            .sslTrustConfiguration(SSLConfiguration.builder()
+                .strategyIgnore()
+                .acceptAnyHostNameVerifier()
+                .build());
     }
 }
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java
index 5b92cf7..7493681 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ClientProviderImplConnectionAuthESOverrideTrustStoreTest.java
@@ -21,8 +21,8 @@ package org.apache.james.backends.es;
 
 import java.util.Optional;
 
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLTrustStore;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 public class ClientProviderImplConnectionAuthESOverrideTrustStoreTest implements ClientProviderImplConnectionContract {
@@ -40,7 +40,9 @@ public class ClientProviderImplConnectionAuthESOverrideTrustStoreTest implements
         return ElasticSearchConfiguration.builder()
             .credential(Optional.of(DockerElasticSearch.WithAuth.DEFAULT_CREDENTIAL))
             .hostScheme(Optional.of(ElasticSearchConfiguration.HostScheme.HTTPS))
-            .sslTrustConfiguration(SSLTrustConfiguration.override(
-                SSLTrustStore.of(TRUST_STORE_FILE_PATH, TRUST_STORE_PASSWORD)));
+            .sslTrustConfiguration(SSLConfiguration.builder()
+                .strategyOverride(SSLTrustStore.of(TRUST_STORE_FILE_PATH, TRUST_STORE_PASSWORD))
+                .acceptAnyHostNameVerifier()
+                .build());
     }
 }
\ No newline at end of file
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
index cf50942..35bf3f4 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
@@ -29,8 +29,8 @@ import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.backends.es.ElasticSearchConfiguration.Credential;
 import org.apache.james.backends.es.ElasticSearchConfiguration.HostScheme;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration;
-import org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLTrustStore;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration;
+import org.apache.james.backends.es.ElasticSearchConfiguration.SSLConfiguration.SSLTrustStore;
 import org.apache.james.util.Host;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -62,7 +62,7 @@ class ElasticSearchConfigurationTest {
     }
 
     @Nested
-    class SSLTrustConfigurationTest {
+    class SSLConfigurationTest {
 
         @Test
         void sslTrustStoreShouldMatchBeanContact() {
@@ -72,45 +72,112 @@ class ElasticSearchConfigurationTest {
 
         @Test
         void shouldMatchBeanContact() {
-            EqualsVerifier.forClass(SSLTrustConfiguration.class)
+            EqualsVerifier.forClass(SSLConfiguration.class)
                 .verify();
         }
 
+        @Test
+        void getSSLConfigurationShouldReturnDefaultValueWhenEmpty() throws Exception {
+            PropertiesConfiguration configuration = new PropertiesConfiguration();
+            configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+            assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                    .getSslConfiguration())
+                .isEqualTo(SSLConfiguration.defaultBehavior());
+        }
+
+        @Test
+        void getSSLConfigurationShouldReturnConfiguredValue() throws Exception {
+            PropertiesConfiguration configuration = new PropertiesConfiguration();
+            configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+            String trustStorePath = "src/test/resources/auth-es/server.jks";
+            String trustStorePassword = "secret";
+
+            configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
+            configuration.addProperty("elasticsearch.hostScheme.https.trustStorePath", trustStorePath);
+            configuration.addProperty("elasticsearch.hostScheme.https.trustStorePassword", trustStorePassword);
+            configuration.addProperty("elasticsearch.hostScheme.https.hostNameVerifier", "default");
+
+            assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                    .getSslConfiguration())
+                .isEqualTo(SSLConfiguration.builder()
+                    .strategyOverride(SSLTrustStore.of(trustStorePath, trustStorePassword))
+                    .defaultHostNameVerifier()
+                    .build());
+        }
+
         @Nested
         class WithSSLValidationStrategy {
 
             @Test
-            void getSSLConfigurationShouldReturnDefaultValueWhenEmpty() throws Exception {
+            void getSSLConfigurationShouldAcceptCaseInsensitiveStrategy() throws Exception {
                 PropertiesConfiguration configuration = new PropertiesConfiguration();
                 configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
 
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "DEfault");
+
                 assertThat(ElasticSearchConfiguration.fromProperties(configuration)
-                        .getSslTrustConfiguration())
-                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.defaultBehavior());
             }
 
             @Test
-            void getSSLConfigurationShouldAcceptCaseInsensitiveStrategy() throws Exception {
+            void fromPropertiesShouldThrowWhenInvalidStrategy() throws Exception {
                 PropertiesConfiguration configuration = new PropertiesConfiguration();
                 configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
 
-                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "DEfault");
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "invalid");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("invalid strategy 'invalid'");
+            }
+        }
+
+        @Nested
+        class WithHostNameVerifier {
+
+            @Test
+            void getSSLConfigurationShouldReturnConfiguredValue() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.hostNameVerifier", "DEFAULT");
 
                 assertThat(ElasticSearchConfiguration.fromProperties(configuration)
-                        .getSslTrustConfiguration())
-                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.builder()
+                        .strategyDefault()
+                        .defaultHostNameVerifier()
+                        .build());
             }
 
             @Test
-            void fromPropertiesShouldThrowWhenInvalidStrategy() throws Exception {
+            void getSSLConfigurationShouldAcceptCaseInsensitiveVerifier() throws Exception {
                 PropertiesConfiguration configuration = new PropertiesConfiguration();
                 configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
 
-                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "invalid");
+                configuration.addProperty("elasticsearch.hostScheme.https.hostNameVerifier", "Accept_Any_Hostname");
+
+                assertThat(ElasticSearchConfiguration.fromProperties(configuration)
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.builder()
+                        .strategyDefault()
+                        .acceptAnyHostNameVerifier()
+                        .build());
+            }
+
+            @Test
+            void fromPropertiesShouldThrowWhenInvalidVerifier() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.hostNameVerifier", "invalid");
 
                 assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
                     .isInstanceOf(IllegalArgumentException.class)
-                    .hasMessage("invalid strategy 'invalid'");
+                    .hasMessage("invalid HostNameVerifier 'invalid'");
             }
         }
 
@@ -125,8 +192,8 @@ class ElasticSearchConfigurationTest {
                 configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "default");
 
                 assertThat(ElasticSearchConfiguration.fromProperties(configuration)
-                        .getSslTrustConfiguration())
-                    .isEqualTo(SSLTrustConfiguration.defaultBehavior());
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.defaultBehavior());
             }
         }
 
@@ -141,8 +208,11 @@ class ElasticSearchConfigurationTest {
                 configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "ignore");
 
                 assertThat(ElasticSearchConfiguration.fromProperties(configuration)
-                        .getSslTrustConfiguration())
-                    .isEqualTo(SSLTrustConfiguration.ignore());
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.builder()
+                        .strategyIgnore()
+                        .defaultHostNameVerifier()
+                        .build());
             }
         }
 
@@ -206,18 +276,20 @@ class ElasticSearchConfigurationTest {
                 PropertiesConfiguration configuration = new PropertiesConfiguration();
                 configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
 
-                String strategy = "override";
                 String trustStorePath = "src/test/resources/auth-es/server.jks";
                 String trustStorePassword = "secret";
 
-                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", strategy);
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
                 configuration.addProperty("elasticsearch.hostScheme.https.trustStorePath", trustStorePath);
                 configuration.addProperty("elasticsearch.hostScheme.https.trustStorePassword", trustStorePassword);
+                configuration.addProperty("elasticsearch.hostScheme.https.hostNameVerifier", "default");
 
                 assertThat(ElasticSearchConfiguration.fromProperties(configuration)
-                        .getSslTrustConfiguration())
-                    .isEqualTo(SSLTrustConfiguration.override(
-                        SSLTrustStore.of(trustStorePath, trustStorePassword)));
+                        .getSslConfiguration())
+                    .isEqualTo(SSLConfiguration.builder()
+                        .strategyOverride(SSLTrustStore.of(trustStorePath, trustStorePassword))
+                        .defaultHostNameVerifier()
+                        .build());
             }
         }
     }
diff --git a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
index b2eefc5..74e5a3d 100644
--- a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
@@ -45,6 +45,12 @@ elasticsearch.port=9200
 # You need to specify both trustStorePath and trustStorePassword
 # elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
+# Optional. default is `default`
+# Configure Elasticsearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any hostname (not recommended).
+# elasticsearch.hostScheme.https.hostNameVerifier=default
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
index eecf449..0ea86db 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
@@ -44,6 +44,12 @@ elasticsearch.port=9200
 # You need to specify both trustStorePath and trustStorePassword
 # elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
+# Optional. default is `default`
+# Configure Elasticsearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any hostname (not recommended).
+# elasticsearch.hostScheme.https.hostNameVerifier=default
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
index eecf449..16ea216 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
@@ -44,6 +44,12 @@ elasticsearch.port=9200
 # You need to specify both trustStorePath and trustStorePassword
 # elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
+# Optional. default is `default`
+# Configure Elasticsearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any host (not recommended).
+# elasticsearch.hostScheme.https.hostNameVerifier=default
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
index b2eefc5..74e5a3d 100644
--- a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
@@ -45,6 +45,12 @@ elasticsearch.port=9200
 # You need to specify both trustStorePath and trustStorePassword
 # elasticsearch.hostScheme.https.trustStorePassword=myJKSPassword
 
+# Optional. default is `default`
+# Configure Elasticsearch rest client to use host name verifier during SSL handshake
+# default: using the default hostname verifier provided by apache http client.
+# accept_any_hostname: accept any hostname (not recommended).
+# elasticsearch.hostScheme.https.hostNameVerifier=default
+
 # Optional.
 # Basic auth username to access elasticsearch.
 # Ignore elasticsearch.user and elasticsearch.password to not be using authentication (default behaviour).
diff --git a/src/site/xdoc/server/config-elasticsearch.xml b/src/site/xdoc/server/config-elasticsearch.xml
index f763272..789f044 100644
--- a/src/site/xdoc/server/config-elasticsearch.xml
+++ b/src/site/xdoc/server/config-elasticsearch.xml
@@ -252,6 +252,21 @@
               Once you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
             </dd>
         </dl>
+
+        <p>
+            During SSL handshaking, the client can determine whether accept or reject connecting to a remote server by its hostname.
+            You can configure to use which HostNameVerifier in the client.
+        </p>
+        <dl>
+            <dt><strong>elasticsearch.hostScheme.https.hostNameVerifier</strong></dt>
+            <dd>
+              Optional. Default is <strong>default</strong>.
+            </dd>
+            <dd>
+              default: using the default hostname verifier provided by apache http client.
+              accept_any_hostname: accept any host (not recommended).
+            </dd>
+        </dl>
     </section>
 </body>
 


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 06/07: JAMES-2905 ElasticSearch configuration various improvements

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 8b46a4b68145334927655b8e10800be0aeb8ab0d
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Wed Nov 13 10:21:40 2019 +0700

    JAMES-2905 ElasticSearch configuration various improvements
    
     - Using a written HostNameVerifier
     - Using File + char [] in TrustStore
     - Correct documentations
     ...
---
 .../apache/james/backends/es/ClientProvider.java   | 10 ++--
 .../backends/es/ElasticSearchConfiguration.java    | 53 +++++++++++++---------
 .../james/backends/es/DockerElasticSearch.java     |  4 +-
 .../backends/es/ElasticSearchClusterExtension.java |  4 +-
 .../es/ElasticSearchConfigurationTest.java         | 32 +++++++++++--
 .../src/test/resources/auth-es/README.md           |  2 +-
 .../destination/conf/elasticsearch.properties      |  5 +-
 .../destination/conf/elasticsearch.properties      |  2 +-
 .../destination/conf/elasticsearch.properties      |  5 +-
 .../destination/conf/elasticsearch.properties      |  2 +-
 src/site/xdoc/server/config-elasticsearch.xml      | 23 ++++++----
 11 files changed, 94 insertions(+), 48 deletions(-)

diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
index 923a820..50a51e7 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ClientProvider.java
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.james.backends.es;
 
-import java.io.File;
 import java.io.IOException;
 import java.security.KeyManagementException;
 import java.security.KeyStoreException;
@@ -30,6 +29,7 @@ import java.time.LocalDateTime;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Provider;
+import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 
 import org.apache.commons.lang3.NotImplementedException;
@@ -38,7 +38,6 @@ import org.apache.http.HttpHost;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.CredentialsProvider;
-import org.apache.http.conn.ssl.NoopHostnameVerifier;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
 import org.apache.http.ssl.SSLContextBuilder;
@@ -61,6 +60,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
     private static class HttpAsyncClientConfigurer {
 
         private static final TrustStrategy TRUST_ALL = (x509Certificates, authType) -> true;
+        private static final HostnameVerifier ACCEPT_ANY_HOST = (hostname, sslSession) -> true;
 
         private final ElasticSearchConfiguration configuration;
 
@@ -94,7 +94,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
             try {
                 builder
                     .setSSLContext(sslContext())
-                    .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
+                    .setSSLHostnameVerifier(ACCEPT_ANY_HOST);
             } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException e) {
                 throw new RuntimeException("Cannot set SSL options to the builder", e);
             }
@@ -131,7 +131,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
                 .orElseThrow(() -> new IllegalStateException("SSLTrustStore cannot to be empty"));
 
             return sslContextBuilder
-                .loadTrustMaterial(new File(trustStore.getFilePath()), trustStore.getPassword().toCharArray());
+                .loadTrustMaterial(trustStore.getFile(), trustStore.getPassword());
         }
 
         private void configureAuthentication(HttpAsyncClientBuilder builder) {
@@ -139,7 +139,7 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
                 .ifPresent(credential -> {
                     CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
                     credentialsProvider.setCredentials(AuthScope.ANY,
-                        new UsernamePasswordCredentials(credential.getUsername(), credential.getPassword()));
+                        new UsernamePasswordCredentials(credential.getUsername(), String.valueOf(credential.getPassword())));
                     builder.setDefaultCredentialsProvider(credentialsProvider);
                 });
         }
diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
index 82ce0d6..47681a5 100644
--- a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
+++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchConfiguration.java
@@ -21,10 +21,14 @@ package org.apache.james.backends.es;
 
 import static org.apache.james.backends.es.ElasticSearchConfiguration.SSLTrustConfiguration.SSLValidationStrategy.OVERRIDE;
 
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -47,8 +51,11 @@ public class ElasticSearchConfiguration {
         HTTPS("https");
 
         public static HostScheme of(String schemeValue) {
+            Preconditions.checkNotNull(schemeValue);
+
             return Arrays.stream(values())
-                .filter(hostScheme -> hostScheme.value.equalsIgnoreCase(schemeValue))
+                .filter(hostScheme -> hostScheme.value.toLowerCase(Locale.US)
+                    .equals(schemeValue.toLowerCase(Locale.US)))
                 .findFirst()
                 .orElseThrow(() -> new IllegalArgumentException(
                     String.format("Unknown HostScheme '%s'", schemeValue)));
@@ -68,21 +75,21 @@ public class ElasticSearchConfiguration {
         }
 
         private final String username;
-        private final String password;
+        private final char[] password;
 
         private Credential(String username, String password) {
             Preconditions.checkNotNull(username, "username cannot be null when password is specified");
             Preconditions.checkNotNull(password, "password cannot be null when username is specified");
 
             this.username = username;
-            this.password = password;
+            this.password = password.toCharArray();
         }
 
         public String getUsername() {
             return username;
         }
 
-        public String getPassword() {
+        public char[] getPassword() {
             return password;
         }
 
@@ -92,14 +99,14 @@ public class ElasticSearchConfiguration {
                 Credential that = (Credential) o;
 
                 return Objects.equals(this.username, that.username)
-                    && Objects.equals(this.password, that.password);
+                    && Arrays.equals(this.password, that.password);
             }
             return false;
         }
 
         @Override
         public final int hashCode() {
-            return Objects.hash(username, password);
+            return Objects.hash(username, Arrays.hashCode(password));
         }
     }
 
@@ -127,22 +134,26 @@ public class ElasticSearchConfiguration {
                 return new SSLTrustStore(filePath, password);
             }
 
-            private final String filePath;
-            private final String password;
+            private final File file;
+            private final char[] password;
 
             private SSLTrustStore(String filePath, String password) {
-                Preconditions.checkNotNull(filePath, ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " is specified");
-                Preconditions.checkNotNull(password, ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " is specified");
-
-                this.filePath = filePath;
-                this.password = password;
+                Preconditions.checkNotNull(filePath,
+                    ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " is specified");
+                Preconditions.checkNotNull(password,
+                    ELASTICSEARCH_HTTPS_TRUST_STORE_PASSWORD + " cannot be null when " + ELASTICSEARCH_HTTPS_TRUST_STORE_PATH + " is specified");
+                Preconditions.checkArgument(Files.exists(Paths.get(filePath)),
+                     String.format("the file '%s' from property '%s' doesn't exist", filePath, ELASTICSEARCH_HTTPS_TRUST_STORE_PATH));
+
+                this.file = new File(filePath);
+                this.password = password.toCharArray();
             }
 
-            public String getFilePath() {
-                return filePath;
+            public File getFile() {
+                return file;
             }
 
-            public String getPassword() {
+            public char[] getPassword() {
                 return password;
             }
 
@@ -151,15 +162,15 @@ public class ElasticSearchConfiguration {
                 if (o instanceof SSLTrustStore) {
                     SSLTrustStore that = (SSLTrustStore) o;
 
-                    return Objects.equals(this.filePath, that.filePath)
-                        && Objects.equals(this.password, that.password);
+                    return Objects.equals(this.file, that.file)
+                        && Arrays.equals(this.password, that.password);
                 }
                 return false;
             }
 
             @Override
             public final int hashCode() {
-                return Objects.hash(filePath, password);
+                return Objects.hash(file, Arrays.hashCode(password));
             }
         }
 
@@ -181,7 +192,7 @@ public class ElasticSearchConfiguration {
         private SSLTrustConfiguration(SSLValidationStrategy strategy, Optional<SSLTrustStore> trustStore) {
             Preconditions.checkNotNull(strategy);
             Preconditions.checkNotNull(trustStore);
-            Preconditions.checkArgument(strategy != OVERRIDE || trustStore.isPresent(), "OVERRIDE strategy requires trustStore to be present");
+            Preconditions.checkArgument(strategy != OVERRIDE || trustStore.isPresent(), OVERRIDE.name() + " strategy requires trustStore to be present");
 
             this.strategy = strategy;
             this.trustStore = trustStore;
@@ -292,7 +303,7 @@ public class ElasticSearchConfiguration {
         }
 
         public Builder sslTrustConfiguration(SSLTrustConfiguration sslTrustConfiguration) {
-            this.sslTrustConfiguration = Optional.ofNullable(sslTrustConfiguration);
+            this.sslTrustConfiguration = Optional.of(sslTrustConfiguration);
             return this;
         }
 
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
index 2424477..03fda7f 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java
@@ -62,7 +62,7 @@ public interface DockerElasticSearch {
     interface ElasticSearchAPI {
 
         class Builder {
-            private static final HostnameVerifier ACCEPT_ANY_HOST = (hostname1, sslSession) -> true;
+            private static final HostnameVerifier ACCEPT_ANY_HOST = (hostname, sslSession) -> true;
             private static final TrustManager[] TRUST_ALL = new TrustManager[] {
                 new X509TrustManager() {
 
@@ -88,7 +88,7 @@ public interface DockerElasticSearch {
 
             public Builder credential(Credential credential) {
                 requestBuilder.requestInterceptor(
-                    new BasicAuthRequestInterceptor(credential.getUsername(), credential.getPassword()));
+                    new BasicAuthRequestInterceptor(credential.getUsername(), String.valueOf(credential.getPassword())));
                 return this;
             }
 
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
index 3e73fe0..41660b3 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchClusterExtension.java
@@ -58,8 +58,8 @@ class ElasticSearchClusterExtension implements AfterAllCallback, BeforeAllCallba
                         es1.cleanUpData();
                 }},
                 () -> {
-                    if (es1.isRunning()) {
-                        es1.cleanUpData();
+                    if (es2.isRunning()) {
+                        es2.cleanUpData();
                 }});
         }
 
diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
index 6cb1867..cf50942 100644
--- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
+++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchConfigurationTest.java
@@ -159,7 +159,7 @@ class ElasticSearchConfigurationTest {
 
                 assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
                     .isInstanceOf(NullPointerException.class)
-                    .hasMessage("password cannot be null when filePath is specified");
+                    .hasMessage("elasticsearch.hostScheme.https.trustStorePassword cannot be null when elasticsearch.hostScheme.https.trustStorePath is specified");
             }
 
             @Test
@@ -172,7 +172,33 @@ class ElasticSearchConfigurationTest {
 
                 assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
                     .isInstanceOf(NullPointerException.class)
-                    .hasMessage("filePath cannot be null when password is specified");
+                    .hasMessage("elasticsearch.hostScheme.https.trustStorePath cannot be null when elasticsearch.hostScheme.https.trustStorePassword is specified");
+            }
+
+            @Test
+            void fromPropertiesShouldThrowWhenTrustStoreIsNotProvided() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("OVERRIDE strategy requires trustStore to be present");
+            }
+
+            @Test
+            void fromPropertiesShouldThrowWhenTrustStorePathDoesntExist() throws Exception {
+                PropertiesConfiguration configuration = new PropertiesConfiguration();
+                configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+                configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", "override");
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePath", "/home/james/ServerTrustStore.jks");
+                configuration.addProperty("elasticsearch.hostScheme.https.trustStorePassword", "password");
+
+                assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+                    .isInstanceOf(IllegalArgumentException.class)
+                    .hasMessage("the file '/home/james/ServerTrustStore.jks' from property 'elasticsearch.hostScheme.https.trustStorePath' doesn't exist");
             }
 
             @Test
@@ -181,7 +207,7 @@ class ElasticSearchConfigurationTest {
                 configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
 
                 String strategy = "override";
-                String trustStorePath = "/home/james/ServerTrustStore.jks";
+                String trustStorePath = "src/test/resources/auth-es/server.jks";
                 String trustStorePassword = "secret";
 
                 configuration.addProperty("elasticsearch.hostScheme.https.sslValidationStrategy", strategy);
diff --git a/backends-common/elasticsearch/src/test/resources/auth-es/README.md b/backends-common/elasticsearch/src/test/resources/auth-es/README.md
index b7d945e..5dd42de 100644
--- a/backends-common/elasticsearch/src/test/resources/auth-es/README.md
+++ b/backends-common/elasticsearch/src/test/resources/auth-es/README.md
@@ -4,7 +4,7 @@
 
 Contains nginx configuration files:
  - reverse_elasticsearch.conf: allow nginx to be the proxy connecting to ElasticSearch
- - passwd: Nginx credentials file store, each record follow the format: `username:encrypted-password`
+ - passwd: Nginx credentials file store, each record follows the format: `username:encrypted-password`
 
 ### default.crt & default.key
 
diff --git a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
index f28c38a..b2eefc5 100644
--- a/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-ldap/destination/conf/elasticsearch.properties
@@ -25,11 +25,14 @@
 elasticsearch.masterHost=elasticsearch
 elasticsearch.port=9200
 
+# Optional. Only http or https are accepted, default is http
+# elasticsearch.hostScheme=http
+
 # Optional, default is `default`
 # Choosing the SSL check strategy when using https scheme
 # default: Use the default SSL TrustStore of the system.
 # ignore: Ignore SSL Validation check (not recommended).
-# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
 # elasticsearch.hostScheme.https.sslValidationStrategy=default
 
 # Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
index 3490e61..eecf449 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq-ldap/destination/conf/elasticsearch.properties
@@ -31,7 +31,7 @@ elasticsearch.port=9200
 # Choosing the SSL check strategy when using https scheme
 # default: Use the default SSL TrustStore of the system.
 # ignore: Ignore SSL Validation check (not recommended).
-# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
 # elasticsearch.hostScheme.https.sslValidationStrategy=default
 
 # Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
diff --git a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
index 7c23c72..eecf449 100644
--- a/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra-rabbitmq/destination/conf/elasticsearch.properties
@@ -24,11 +24,14 @@
 elasticsearch.masterHost=elasticsearch
 elasticsearch.port=9200
 
+# Optional. Only http or https are accepted, default is http
+# elasticsearch.hostScheme=http
+
 # Optional, default is `default`
 # Choosing the SSL check strategy when using https scheme
 # default: Use the default SSL TrustStore of the system.
 # ignore: Ignore SSL Validation check (not recommended).
-# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
 # elasticsearch.hostScheme.https.sslValidationStrategy=default
 
 # Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
diff --git a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
index 077e76c..b2eefc5 100644
--- a/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
+++ b/dockerfiles/run/guice/cassandra/destination/conf/elasticsearch.properties
@@ -32,7 +32,7 @@ elasticsearch.port=9200
 # Choosing the SSL check strategy when using https scheme
 # default: Use the default SSL TrustStore of the system.
 # ignore: Ignore SSL Validation check (not recommended).
-# override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+# override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
 # elasticsearch.hostScheme.https.sslValidationStrategy=default
 
 # Optional. Required when using 'https' scheme and 'override' sslValidationStrategy
diff --git a/src/site/xdoc/server/config-elasticsearch.xml b/src/site/xdoc/server/config-elasticsearch.xml
index 77f0fc4..f763272 100644
--- a/src/site/xdoc/server/config-elasticsearch.xml
+++ b/src/site/xdoc/server/config-elasticsearch.xml
@@ -205,21 +205,24 @@
             <dd>
                 default: Use the default SSL TrustStore of the system.
                 ignore: Ignore SSL Validation check (not recommended).
-                override: Override the SSL Context to use a custome TrustStore containing ES server's certificate.
+                override: Override the SSL Context to use a custom TrustStore containing ES server's certificate.
             </dd>
         </dl>
 
         <p>
-            In some cases, you want to secure ES to protect it from unauthorized requests,
-            assuming with the ES is using <strong>https</strong> with a self signed certificate.
-            Which means you should configure the ES RestHighLevelClient to trust your self signed certificate.
-
-            There are two ways on client side: ignoring SSL check or configure to trust the server's certificate.
+            In some cases, you want to secure the connection from clients to ES by setting up a <strong>https</strong> protocol
+            with a self signed certificate. And you prefer to left the system ca-certificates un touch.
+            There are possible solutions to let the ES RestHighLevelClient to trust your self signed certificate.
+        </p>
+        <p>
+            First solution: ignoring SSL check.
             In case you want to ignore the SSL check, simply, just don't specify below options. Otherwise, configuring the trust
             requires some prerequisites and they are explained in below block.
-
+        </p>
+        <p>
+            Second solution: importing a TrustStore containing the certificate into SSL context.
             A certificate normally contains two parts: a public part in .crt file, another private part in .key file.
-            To trust the server, the client need to be acknowledged that the server's certificate is in the list of
+            To trust the server, the client needs to be acknowledged that the server's certificate is in the list of
             client's TrustStore. Basically, you can create a local TrustStore file containing the public part of a remote server
             by execute this command:
         </p>
@@ -239,14 +242,14 @@
             <dd>
               Optional. Use it when https is configured in elasticsearch.hostScheme, and sslValidationStrategy is <strong>override</strong>
               Configure Elasticsearch rest client to use this trustStore file to recognize nginx's ssl certificate.
-              Once, you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
+              Once you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
             </dd>
 
             <dt><strong>elasticsearch.hostScheme.https.trustStorePassword</strong></dt>
             <dd>
               Optional. Use it when https is configured in elasticsearch.hostScheme, and sslValidationStrategy is <strong>override</strong>
               Configure Elasticsearch rest client to use this trustStore file with the specified password.
-              Once, you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
+              Once you chose <strong>override</strong>, you need to specify both trustStorePath and trustStorePassword.
             </dd>
         </dl>
     </section>


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org