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:21 UTC
[james-project] 01/07: JAMES-2905 ElasticSearch authentication
configuration
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