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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
