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 bt...@apache.org on 2019/11/13 03:07:05 UTC
[james-project] 16/21: JAMES-2905 ES authentication configuration
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit acc801ac7a6275a6723183a71079f3f918052269
Author: Antoine DUPRAT <ad...@linagora.com>
AuthorDate: Mon Oct 28 11:55:24 2019 +0100
JAMES-2905 ES authentication configuration
---
.../apache/james/backends/es/ClientProvider.java | 27 ++++-
.../backends/es/ElasticSearchConfiguration.java | 122 +++++++++++++++++++-
.../es/ElasticSearchConfigurationTest.java | 123 +++++++++++++++++++++
.../modules/mailbox/ElasticSearchStartUpCheck.java | 3 +-
4 files changed, 267 insertions(+), 8 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 f6deb48..5412b09 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
@@ -21,6 +21,7 @@ package org.apache.james.backends.es;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
+import java.util.Optional;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
@@ -28,7 +29,12 @@ import javax.inject.Provider;
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.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,7 +47,6 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
private static final Logger LOGGER = LoggerFactory.getLogger(ClientProvider.class);
- private static final String HTTP_HOST_SCHEME = "http";
private final ElasticSearchConfiguration configuration;
private final RestHighLevelClient client;
@@ -67,14 +72,30 @@ public class ClientProvider implements Provider<RestHighLevelClient> {
private RestHighLevelClient connectToCluster(ElasticSearchConfiguration configuration) throws IOException {
LOGGER.info("Trying to connect to ElasticSearch service at {}", LocalDateTime.now());
+ Optional<CredentialsProvider> credentials = credentials(configuration);
+ RestClientBuilder restClientBuilder = RestClient.builder(hostsToHttpHosts());
+ credentials.ifPresent(provider -> restClientBuilder
+ .setHttpClientConfigCallback(httpClientBuilder -> {
+ return httpClientBuilder.setDefaultCredentialsProvider(provider);
+ }));
+
return new RestHighLevelClient(
- RestClient.builder(hostsToHttpHosts())
+ restClientBuilder
.setMaxRetryTimeoutMillis(Math.toIntExact(configuration.getRequestTimeout().toMillis())));
}
+ private Optional<CredentialsProvider> credentials(ElasticSearchConfiguration configuration) {
+ if (configuration.getUser().isPresent() && configuration.getPassword().isPresent()) {
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(configuration.getUser().get(), configuration.getPassword().get()));
+ return Optional.of(credentialsProvider);
+ }
+ return Optional.empty();
+ }
+
private HttpHost[] hostsToHttpHosts() {
return configuration.getHosts().stream()
- .map(host -> new HttpHost(host.getHostName(), host.getPort(), HTTP_HOST_SCHEME))
+ .map(host -> new HttpHost(host.getHostName(), host.getPort(), configuration.getHostScheme().name()))
.toArray(HttpHost[]::new);
}
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 f50200f..875bf01 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
@@ -37,6 +37,67 @@ import com.google.common.collect.ImmutableList;
public class ElasticSearchConfiguration {
+ public enum HostScheme {
+ HTTP("http"),
+ HTTPS("https");
+
+ public static HostScheme of(String schemeValue) {
+ return Arrays.stream(values())
+ .filter(hostScheme -> hostScheme.value.equalsIgnoreCase(schemeValue))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(
+ String.format("Unknown HostScheme '%s'", schemeValue)));
+ }
+
+ private final String value;
+
+ HostScheme(String value) {
+ this.value = value;
+ }
+ }
+
+ public static class Credential {
+
+ public static Credential of(String username, String password) {
+ return new Credential(username, password);
+ }
+
+ private final String username;
+ private final String 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;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof Credential) {
+ Credential that = (Credential) o;
+
+ return Objects.equals(this.username, that.username)
+ && Objects.equals(this.password, that.password);
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(username, password);
+ }
+ }
+
public static class Builder {
private final ImmutableList.Builder<Host> hosts;
@@ -46,6 +107,8 @@ public class ElasticSearchConfiguration {
private Optional<Integer> minDelay;
private Optional<Integer> maxRetries;
private Optional<Duration> requestTimeout;
+ private Optional<HostScheme> hostScheme;
+ private Optional<Credential> credential;
public Builder() {
hosts = ImmutableList.builder();
@@ -55,6 +118,8 @@ public class ElasticSearchConfiguration {
minDelay = Optional.empty();
maxRetries = Optional.empty();
requestTimeout = Optional.empty();
+ hostScheme = Optional.empty();
+ credential = Optional.empty();
}
public Builder addHost(Host host) {
@@ -100,6 +165,16 @@ public class ElasticSearchConfiguration {
return this;
}
+ public Builder hostScheme(Optional<HostScheme> hostScheme) {
+ this.hostScheme = hostScheme;
+ return this;
+ }
+
+ public Builder credential(Optional<Credential> credential) {
+ this.credential = credential;
+ return this;
+ }
+
public ElasticSearchConfiguration build() {
ImmutableList<Host> hosts = this.hosts.build();
Preconditions.checkState(!hosts.isEmpty(), "You need to specify ElasticSearch host");
@@ -110,7 +185,9 @@ public class ElasticSearchConfiguration {
waitForActiveShards.orElse(DEFAULT_WAIT_FOR_ACTIVE_SHARDS),
minDelay.orElse(DEFAULT_CONNECTION_MIN_DELAY),
maxRetries.orElse(DEFAULT_CONNECTION_MAX_RETRIES),
- requestTimeout.orElse(DEFAULT_REQUEST_TIMEOUT));
+ requestTimeout.orElse(DEFAULT_REQUEST_TIMEOUT),
+ hostScheme.orElse(DEFAULT_SCHEME),
+ credential);
}
}
@@ -121,6 +198,9 @@ public class ElasticSearchConfiguration {
public static final String ELASTICSEARCH_HOSTS = "elasticsearch.hosts";
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_USER = "elasticsearch.user";
+ public static final String ELASTICSEARCH_PASSWORD = "elasticsearch.password";
public static final String ELASTICSEARCH_NB_REPLICA = "elasticsearch.nb.replica";
public static final String WAIT_FOR_ACTIVE_SHARDS = "elasticsearch.index.waitForActiveShards";
public static final String ELASTICSEARCH_NB_SHARDS = "elasticsearch.nb.shards";
@@ -136,6 +216,7 @@ public class ElasticSearchConfiguration {
public static final int DEFAULT_PORT = 9200;
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 ElasticSearchConfiguration DEFAULT_CONFIGURATION = builder()
.addHost(Host.from(LOCALHOST, DEFAULT_PORT))
@@ -144,6 +225,8 @@ public class ElasticSearchConfiguration {
public static ElasticSearchConfiguration fromProperties(Configuration configuration) throws ConfigurationException {
return builder()
.addHosts(getHosts(configuration))
+ .hostScheme(getHostScheme(configuration))
+ .credential(getCredential(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))
@@ -152,6 +235,22 @@ public class ElasticSearchConfiguration {
.build();
}
+ private static Optional<HostScheme> getHostScheme(Configuration configuration) {
+ return Optional.ofNullable(configuration.getString(ELASTICSEARCH_HOST_SCHEME))
+ .map(HostScheme::of);
+ }
+
+ private static Optional<Credential> getCredential(Configuration configuration) {
+ String username = configuration.getString(ELASTICSEARCH_USER);
+ String password = configuration.getString(ELASTICSEARCH_PASSWORD);
+
+ if (username == null && password == null) {
+ return Optional.empty();
+ }
+
+ return Optional.of(Credential.of(username, password));
+ }
+
private static ImmutableList<Host> getHosts(Configuration propertiesReader) throws ConfigurationException {
Optional<String> masterHost = Optional.ofNullable(
propertiesReader.getString(ELASTICSEARCH_MASTER_HOST, null));
@@ -194,8 +293,11 @@ public class ElasticSearchConfiguration {
private final int minDelay;
private final int maxRetries;
private final Duration requestTimeout;
+ private final HostScheme hostScheme;
+ private final Optional<Credential> credential;
- private ElasticSearchConfiguration(ImmutableList<Host> hosts, int nbShards, int nbReplica, int waitForActiveShards, int minDelay, int maxRetries, Duration requestTimeout) {
+ private ElasticSearchConfiguration(ImmutableList<Host> hosts, int nbShards, int nbReplica, int waitForActiveShards, int minDelay, int maxRetries, Duration requestTimeout,
+ HostScheme hostScheme, Optional<Credential> credential) {
this.hosts = hosts;
this.nbShards = nbShards;
this.nbReplica = nbReplica;
@@ -203,6 +305,8 @@ public class ElasticSearchConfiguration {
this.minDelay = minDelay;
this.maxRetries = maxRetries;
this.requestTimeout = requestTimeout;
+ this.hostScheme = hostScheme;
+ this.credential = credential;
}
public ImmutableList<Host> getHosts() {
@@ -233,6 +337,14 @@ public class ElasticSearchConfiguration {
return requestTimeout;
}
+ public HostScheme getHostScheme() {
+ return hostScheme;
+ }
+
+ public Optional<Credential> getCredential() {
+ return credential;
+ }
+
@Override
public final boolean equals(Object o) {
if (o instanceof ElasticSearchConfiguration) {
@@ -244,13 +356,15 @@ public class ElasticSearchConfiguration {
&& Objects.equals(this.minDelay, that.minDelay)
&& Objects.equals(this.maxRetries, that.maxRetries)
&& Objects.equals(this.hosts, that.hosts)
- && Objects.equals(this.requestTimeout, that.requestTimeout);
+ && Objects.equals(this.requestTimeout, that.requestTimeout)
+ && Objects.equals(this.hostScheme, that.hostScheme)
+ && Objects.equals(this.credential, that.credential);
}
return false;
}
@Override
public final int hashCode() {
- return Objects.hash(hosts, nbShards, nbReplica, waitForActiveShards, minDelay, maxRetries, requestTimeout);
+ return Objects.hash(hosts, nbShards, nbReplica, waitForActiveShards, minDelay, maxRetries, requestTimeout, hostScheme, credential);
}
}
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 befa774..5013204 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
@@ -27,7 +27,10 @@ import java.util.Optional;
import org.apache.commons.configuration2.PropertiesConfiguration;
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.util.Host;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import com.google.common.collect.ImmutableList;
@@ -36,6 +39,26 @@ import nl.jqno.equalsverifier.EqualsVerifier;
class ElasticSearchConfigurationTest {
+ @Nested
+ class HostSchemeTest {
+
+ @Test
+ void shouldMatchBeanContact() {
+ EqualsVerifier.forClass(HostScheme.class)
+ .verify();
+ }
+ }
+
+ @Nested
+ class CredentialTest {
+
+ @Test
+ void shouldMatchBeanContact() {
+ EqualsVerifier.forClass(Credential.class)
+ .verify();
+ }
+ }
+
@Test
void elasticSearchConfigurationShouldRespectBeanContract() {
EqualsVerifier.forClass(ElasticSearchConfiguration.class)
@@ -326,4 +349,104 @@ class ElasticSearchConfigurationTest {
.nbShards(0))
.isInstanceOf(IllegalArgumentException.class);
}
+
+ @Test
+ void getCredentialShouldReturnConfiguredValue() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ String user = "johndoe";
+ String password = "secret";
+ configuration.addProperty("elasticsearch.user", user);
+ configuration.addProperty("elasticsearch.password", password);
+
+ ElasticSearchConfiguration elasticSearchConfiguration = ElasticSearchConfiguration.fromProperties(configuration);
+
+ assertThat(elasticSearchConfiguration.getCredential())
+ .contains(Credential.of(user, password));
+ }
+
+ @Test
+ void getCredentialShouldReturnEmptyWhenNotConfigured() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ ElasticSearchConfiguration elasticSearchConfiguration = ElasticSearchConfiguration.fromProperties(configuration);
+
+ assertThat(elasticSearchConfiguration.getCredential())
+ .isEmpty();
+ }
+
+ @Test
+ void fromPropertiesShouldThrowWhenOnlyUsername() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ configuration.addProperty("elasticsearch.user", "username");
+
+ assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("password cannot be null when username is specified");
+ }
+
+ @Test
+ void fromPropertiesShouldThrowWhenOnlyPassword() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ configuration.addProperty("elasticsearch.password", "password");
+
+ assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("username cannot be null when password is specified");
+ }
+
+ @Test
+ void getHostSchemeShouldReturnConfiguredValue() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ configuration.addProperty("elasticsearch.hostScheme", "https");
+
+ ElasticSearchConfiguration elasticSearchConfiguration = ElasticSearchConfiguration.fromProperties(configuration);
+
+ assertThat(elasticSearchConfiguration.getHostScheme())
+ .isEqualTo(HostScheme.HTTPS);
+ }
+
+ @Test
+ void getHostSchemeShouldBeCaseInsensitive() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ configuration.addProperty("elasticsearch.hostScheme", "HTTPs");
+
+ ElasticSearchConfiguration elasticSearchConfiguration = ElasticSearchConfiguration.fromProperties(configuration);
+
+ assertThat(elasticSearchConfiguration.getHostScheme())
+ .isEqualTo(HostScheme.HTTPS);
+ }
+
+ @Test
+ void getHostSchemeShouldReturnHttpWhenNotConfigured() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ ElasticSearchConfiguration elasticSearchConfiguration = ElasticSearchConfiguration.fromProperties(configuration);
+
+ assertThat(elasticSearchConfiguration.getHostScheme())
+ .isEqualTo(HostScheme.HTTP);
+ }
+
+ @Test
+ void fromPropertiesShouldThrowWhenInvalidValue() throws Exception {
+ PropertiesConfiguration configuration = new PropertiesConfiguration();
+ configuration.addProperty("elasticsearch.hosts", "127.0.0.1");
+
+ configuration.addProperty("elasticsearch.hostScheme", "invalid-protocol");
+
+ assertThatThrownBy(() -> ElasticSearchConfiguration.fromProperties(configuration))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Unknown HostScheme 'invalid-protocol'");
+ }
}
diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchStartUpCheck.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchStartUpCheck.java
index e8eb258..4bf89d7 100644
--- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchStartUpCheck.java
+++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchStartUpCheck.java
@@ -26,6 +26,7 @@ import javax.inject.Inject;
import org.apache.james.backends.es.ElasticSearchConfiguration;
import org.apache.james.lifecycle.api.StartUpCheck;
import org.elasticsearch.Version;
+import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,7 +50,7 @@ public class ElasticSearchStartUpCheck implements StartUpCheck {
@Override
public CheckResult check() {
try {
- Version esVersion = client.info()
+ Version esVersion = client.info(RequestOptions.DEFAULT)
.getVersion();
if (esVersion.isCompatible(RECOMMENDED_ES_VERSION)) {
return CheckResult.builder()
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org