Skip to content

Commit

Permalink
Merge pull request #4089 from Agnul97/fix-SSL_Es
Browse files Browse the repository at this point in the history
FIX - SSL configuration on Elasticsearch clients
  • Loading branch information
Coduz authored Sep 2, 2024
2 parents 6b14b76 + e8db972 commit 4e47529
Show file tree
Hide file tree
Showing 11 changed files with 112 additions and 140 deletions.
1 change: 0 additions & 1 deletion assembly/consumer/telemetry/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ ENV JAVA_OPTS "-Dcommons.db.schema.update=true \
-Dcommons.eventbus.url=\${SERVICE_BROKER_ADDR} \
-Dconsumer.jaxb_context_class_name=org.eclipse.kapua.consumer.telemetry.TelemetryJAXBContextProvider \
-Ddatastore.elasticsearch.nodes=\${DATASTORE_ADDR} \
-Ddatastore.elasticsearch.port=\${DATASTORE_PORT} \
-Ddatastore.client.class=\${DATASTORE_CLIENT} \
-Dbroker.host=\${BROKER_HOST} \
-Dbroker.port=\${BROKER_PORT} \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,6 @@ private void logParameters() {
logger.info("{}: {}", SystemSettingKey.DB_JDBC_DRIVER.key(), SystemSetting.getInstance().getString(SystemSettingKey.DB_JDBC_DRIVER));
logger.info("{}: {}", SystemSettingKey.DB_CONNECTION_SCHEME.key(), SystemSetting.getInstance().getString(SystemSettingKey.DB_CONNECTION_SCHEME));
logger.info("Rest: datastore.elasticsearch.node: {}", DatastoreElasticsearchClientSettings.getInstance().getString(DatastoreElasticsearchClientSettingsKey.NODES));
logger.info("Rest: datastore.elasticsearch.port: {}", DatastoreElasticsearchClientSettings.getInstance().getString(DatastoreElasticsearchClientSettingsKey.PORT));
logger.info("===============================================");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ public class MessageStoreServiceSslTest extends AbstractMessageStoreServiceTest
@Ignore
public void connectNoSsl() throws InterruptedException, KapuaException, ParseException {
// datastore.elasticsearch.ssl.enabled=false
// datastore.elasticsearch.ssl.trust_server_certificate=false
// datastore.elasticsearch.ssl.keystore.path=
// datastore.elasticsearch.ssl.keystore.password=
// datastore.elasticsearch.ssl.keystore.type=jks
Expand All @@ -117,7 +116,6 @@ public void connectNoSsl() throws InterruptedException, KapuaException, ParseExc
@Ignore
public void connectSsl() throws InterruptedException, KapuaException, ParseException {
// datastore.elasticsearch.ssl.enabled=true
// datastore.elasticsearch.ssl.trust_server_certificate=false
// datastore.elasticsearch.ssl.keystore.path=
// datastore.elasticsearch.ssl.keystore.password=
// datastore.elasticsearch.ssl.keystore.type=jks
Expand All @@ -137,7 +135,6 @@ public void connectSsl() throws InterruptedException, KapuaException, ParseExcep
@Ignore
public void connectSslTrustServerNoTrustStoreSet() throws InterruptedException, KapuaException, ParseException {
// datastore.elasticsearch.ssl.enabled=true
// datastore.elasticsearch.ssl.trust_server_certificate=true
// datastore.elasticsearch.ssl.keystore.path=
// datastore.elasticsearch.ssl.keystore.password=
// datastore.elasticsearch.ssl.keystore.type=jks
Expand All @@ -157,7 +154,6 @@ public void connectSslTrustServerNoTrustStoreSet() throws InterruptedException,
@Ignore
public void connectSslTrustServerTrustStoreSet() throws InterruptedException, KapuaException, ParseException {
// datastore.elasticsearch.ssl.enabled=false
// datastore.elasticsearch.ssl.trust_server_certificate=false
// datastore.elasticsearch.ssl.keystore.path=
// datastore.elasticsearch.ssl.keystore.password=
// datastore.elasticsearch.ssl.keystore.type=jks
Expand All @@ -177,7 +173,6 @@ public void connectSslTrustServerTrustStoreSet() throws InterruptedException, Ka
@Ignore
public void connectSslTrustServerSelfSignedTrustStore() throws InterruptedException, KapuaException, ParseException {
// datastore.elasticsearch.ssl.enabled=false
// datastore.elasticsearch.ssl.trust_server_certificate=false
// datastore.elasticsearch.ssl.keystore.path=
// datastore.elasticsearch.ssl.keystore.password=
// datastore.elasticsearch.ssl.keystore.type=jks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ datastore.elasticsearch.request.socket.timeout.millis=-1
#
# SSL
datastore.elasticsearch.ssl.enabled=false
datastore.elasticsearch.ssl.trust_server_certificate=false
datastore.elasticsearch.ssl.keystore.path=
datastore.elasticsearch.ssl.keystore.password=
datastore.elasticsearch.ssl.keystore.type=jks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
public class ElasticsearchClientSslConfiguration {

private boolean enabled;
private boolean trustServiceCertificate;
private String keyStorePath;
private String keyStorePassword;
private String keyStoreType;
Expand Down Expand Up @@ -72,28 +71,6 @@ public ElasticsearchClientSslConfiguration setEnabled(boolean enabled) {
return this;
}

/**
* Gets whether or not to trust the server certificate.
*
* @return {@code true} if needs to be trusted, {@code false} otherwise.
* @since 1.3.0
*/
public boolean isTrustServiceCertificate() {
return trustServiceCertificate;
}

/**
* Sets whether or not to trust the server certificate.
*
* @param trustServiceCertificate {@code true} if needs to be trusted, {@code false} otherwise.
* @return This {@link ElasticsearchClientSslConfiguration} to chain method invocation.
* @since 1.3.0
*/
public ElasticsearchClientSslConfiguration setTrustServiceCertificate(boolean trustServiceCertificate) {
this.trustServiceCertificate = trustServiceCertificate;
return this;
}

/**
* Gets the {@link java.security.KeyStore} path.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,21 @@
*******************************************************************************/
package org.eclipse.kapua.service.elasticsearch.client.rest;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.net.ssl.SSLContext;

import com.google.common.base.Strings;
import com.google.inject.Inject;
import org.apache.commons.lang3.StringUtils;
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.config.RegistryBuilder;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.nio.reactor.IOReactorExceptionHandler;
Expand All @@ -59,15 +45,30 @@
import org.eclipse.kapua.service.elasticsearch.client.exception.ClientInitializationException;
import org.eclipse.kapua.service.elasticsearch.client.exception.ClientProviderInitException;
import org.eclipse.kapua.service.elasticsearch.client.exception.ClientUnavailableException;
import org.eclipse.kapua.service.elasticsearch.client.rest.ssl.SkipCertificateCheckTrustStrategy;
import org.eclipse.kapua.service.elasticsearch.client.utils.InetAddressParser;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;
import com.google.inject.Inject;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* {@link ElasticsearchClientProvider} REST implementation.
Expand Down Expand Up @@ -151,7 +152,6 @@ public RestElasticsearchClientProvider init() throws ClientProviderInitException
.addParameter("Key Store Path", getClientSslConfiguration().getKeyStorePath())
.addParameter("Key Store Type", getClientSslConfiguration().getKeyStoreType())
.addParameter("Key Store Password", Strings.isNullOrEmpty(getClientSslConfiguration().getKeyStorePassword()) ? "No" : "Yes")
.addParameter("Trust Server Certificate", getClientSslConfiguration().isTrustServiceCertificate())
.addParameter("Trust Store Path", getClientSslConfiguration().getTrustStorePath())
.addParameter("Trust Store Password", Strings.isNullOrEmpty(getClientSslConfiguration().getTrustStorePassword()) ? "No" : "Yes");
}
Expand Down Expand Up @@ -371,44 +371,20 @@ private RestClient initClient() throws ClientInitializationException {

private HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder, SSLContext sslContext, CredentialsProvider credentialsProvider) {
try {
if (sslContext != null) {
httpClientBuilder.setSSLContext(sslContext);
}

if (credentialsProvider != null) {
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
final DefaultConnectingIOReactor ioReactor;
final Optional<Integer> numberOfIOThreads = getClientConfiguration().getNumberOfIOThreads();
if (numberOfIOThreads.isPresent()) {
ioReactor = new DefaultConnectingIOReactor(
IOReactorConfig.custom().setIoThreadCount(
numberOfIOThreads.get()
).build()
);

DefaultConnectingIOReactor ioReactor = getDefaultConnectingIOReactor();
if (sslContext != null) {
//we need to set SSL context inside the connectionManager because it hides SSL settings that are set directly to the builder
SSLIOSessionStrategy s = new SSLIOSessionStrategy(sslContext);
RegistryBuilder<SchemeIOSessionStrategy> rb = RegistryBuilder.create();
rb.register("https", s).register("http", NoopIOSessionStrategy.INSTANCE);
httpClientBuilder.setConnectionManager(new PoolingNHttpClientConnectionManager(ioReactor, rb.build()));
} else {
ioReactor = new DefaultConnectingIOReactor();
httpClientBuilder.setConnectionManager(new PoolingNHttpClientConnectionManager(ioReactor));
}
ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {

@Override
public boolean handle(IOException e) {
metrics.getException().inc();
LOG.warn("IOReactor encountered a checked exception: {}", e.getMessage(), e);
//return true to note this exception as handled, it will not be re-thrown
return true;
}

@Override
public boolean handle(RuntimeException e) {
metrics.getRuntimeException().inc();
LOG.warn("IOReactor encountered a runtime exception: {}", e.getMessage(), e);
//return true to note this exception as handled, it will not be re-thrown
return true;
}
});

httpClientBuilder.setConnectionManager(new PoolingNHttpClientConnectionManager(ioReactor));
} catch (IOReactorException e) {
throw new RuntimeException(e);
}
Expand Down Expand Up @@ -445,6 +421,46 @@ public RestElasticsearchClient getElasticsearchClient() throws ClientUnavailable
}
// Private methods

/**
* Gets the {@link DefaultConnectingIOReactor}.
* This thing is motivated by this https://github.com/eclipse/kapua/pull/3564 (specifically, this issue: https://github.com/elastic/elasticsearch/issues/49124)
* Maybe in the future, with future ES versions, the problem will be fixed and this won't be needed anymore
* @return The {@link DefaultConnectingIOReactor}.
* @since 1.3.0
*/
private DefaultConnectingIOReactor getDefaultConnectingIOReactor() throws IOReactorException {
final DefaultConnectingIOReactor ioReactor;
final Optional<Integer> numberOfIOThreads = getClientConfiguration().getNumberOfIOThreads();
if (numberOfIOThreads.isPresent()) {
ioReactor = new DefaultConnectingIOReactor(
IOReactorConfig.custom().setIoThreadCount(
numberOfIOThreads.get()
).build()
);
} else {
ioReactor = new DefaultConnectingIOReactor();
}
ioReactor.setExceptionHandler(new IOReactorExceptionHandler() {

@Override
public boolean handle(IOException e) {
metrics.getException().inc();
LOG.warn("IOReactor encountered a checked exception: {}", e.getMessage(), e);
//return true to note this exception as handled, it will not be re-thrown
return true;
}

@Override
public boolean handle(RuntimeException e) {
metrics.getRuntimeException().inc();
LOG.warn("IOReactor encountered a runtime exception: {}", e.getMessage(), e);
//return true to note this exception as handled, it will not be re-thrown
return true;
}
});
return ioReactor;
}

/**
* Gets the {@link ElasticsearchClientConfiguration}.
*
Expand Down Expand Up @@ -504,63 +520,58 @@ private void initKeyStore(SSLContextBuilder sslBuilder) throws ClientInitializat
}
}

/**
* Loads the {@link KeyStore} as per {@link ElasticsearchClientSslConfiguration}.
*
* @param keystorePath
* The {@link KeyStore} path.
* @param keystorePassword
* The {@link KeyStore} password.
* @return The initialized {@link KeyStore}.
* @throws ClientInitializationException
* if {@link KeyStore} cannot be loaded.
* @since 1.0.0
*/
private KeyStore loadKeyStore(String keystorePath, String keystorePassword) throws ClientInitializationException {
try (InputStream is = Files.newInputStream(new File(keystorePath).toPath())) {
KeyStore keystore = KeyStore.getInstance(getClientConfiguration().getSslConfiguration().getKeyStoreType());
keystore.load(is, keystorePassword.toCharArray());
return keystore;
} catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) {
throw new ClientInitializationException(e, "Failed to load KeyStore");
}
}

/**
* Initializes the {@link TrustStrategy} as per {@link ElasticsearchClientSslConfiguration} with the given {@link SSLContextBuilder}
* <p>
* Truststore 3 available configurations:
* Truststore 2 available configurations:
* <ol>
* <li>No {@link javax.net.ssl.TrustManager}: if {@link ElasticsearchClientSslConfiguration#isTrustServiceCertificate()} is {@code false}</li>
* <li>Set the custom trust manager: if {@link ElasticsearchClientSslConfiguration#getTrustStorePath()} is defined</li>
* <li>Use the JVM default truststore: as fallback option</li>
* </ol>
*
* @param sslBuilder
* The {@link SSLContextBuilder} to use.
* @throws ClientInitializationException
* if {@link KeyStore} cannot be initialized.
* @param sslBuilder
* The {@link SSLContextBuilder} to use.
* @throws ClientInitializationException
* if {@link KeyStore} cannot be initialized.
* @since 1.0.0
*/
private void initTrustStore(SSLContextBuilder sslBuilder) throws ClientInitializationException {
ElasticsearchClientSslConfiguration sslConfiguration = getClientSslConfiguration();

boolean trustServerCertificate = sslConfiguration.isTrustServiceCertificate();
String truststorePath = sslConfiguration.getKeyStorePath();
String truststorePath = sslConfiguration.getTrustStorePath();
String truststorePassword = sslConfiguration.getTrustStorePassword();
LOG.info("ES Rest Client - SSL trust server certificate: {}", (trustServerCertificate ? "Enabled" : "Disabled"));
LOG.info("ES Rest Client - Truststore path: {}", StringUtils.isNotBlank(truststorePath) ? truststorePath : "None");

try {
if (!trustServerCertificate) {
sslBuilder.loadTrustMaterial(null, new SkipCertificateCheckTrustStrategy());
} else if (StringUtils.isNotBlank(truststorePath)) {
if (StringUtils.isNotBlank(truststorePath)) {
sslBuilder.loadTrustMaterial(loadKeyStore(truststorePath, truststorePassword), null);
} else {
sslBuilder.loadTrustMaterial((TrustStrategy) null);
sslBuilder.loadTrustMaterial((TrustStrategy) null); //This will load JVM default truststore (with common root certificates in it). Useful for usual production deployment
}
} catch (NoSuchAlgorithmException | KeyStoreException ltme) {
throw new ClientInitializationException(ltme, "Failed to init TrustStore");
}
}

/**
* Loads the {@link KeyStore} as per {@link ElasticsearchClientSslConfiguration}.
*
* @param keystorePath
* The {@link KeyStore} path.
* @param keystorePassword
* The {@link KeyStore} password.
* @return The initialized {@link KeyStore}.
* @throws ClientInitializationException
* if {@link KeyStore} cannot be loaded.
* @since 1.0.0
*/
private KeyStore loadKeyStore(String keystorePath, String keystorePassword) throws ClientInitializationException {
try (InputStream is = Files.newInputStream(new File(keystorePath).toPath())) {
KeyStore keystore = KeyStore.getInstance(getClientConfiguration().getSslConfiguration().getKeyStoreType());
keystore.load(is, keystorePassword.toCharArray());
return keystore;
} catch (IOException | KeyStoreException | CertificateException | NoSuchAlgorithmException e) {
throw new ClientInitializationException(e, "Failed to load KeyStore");
}
}
}
Loading

0 comments on commit 4e47529

Please sign in to comment.