2

I'm working on a new project which involves fuzzy search so was working on elasticsearch (v6.4.3) with spring boot (v2.1.5). I am unable to make connection between spring boot to elasticsearch since I have to pass username, password, ca_certificate_base64 from spring boot to elasticsearch to make connection. Could you please let me know how do I make connection and what elasticsearch client is correct option? A sample or link would be helpful.

Locally (my laptop), I did install elasticsearch 6 and spring boot 2.1.5. I was able to make connection (using spring data) since there was no 'https' connection required or pass username and password since most of the examples in internet talks about localhost:9200.

   public Client client() {
     Settings elasticsearchSettings = Settings.builder()
           .put("client.transport.sniff", true)
           .put("uri", "https://usertemp:ABCSAD@alp-usba-north-3-portal.11.db2lay.com:32117/")
           .put("uri_direct_1", "https://usertemp:ABCSAD@alp-usba-north-3-portal.11.db2lay.com:32117/")
           .put("cluster.name", clusterName).build();
      TransportClient client = new PreBuiltTransportClient(elasticsearchSettings);
}


I also tried to update application.properties file
'''
spring.data.elasticsearch.cluster-name=ee842f-93042
spring.data.elasticsearch.cluster-nodes=alp-usba-north-3-portal.11.db2lay.com:32117
spring.data.elasticsearch.properties.username=usertemp
spring.data.elasticsearch.properties.password=ABCSAD
spring.data.elasticsearch.properties.ca_certificate_base64=SUZJQ0FURS0tLS0tCk1JSURvekNDQW91Z0F3SUJBZ0lFWFA5Sjl6QU5CZ2

I was expecting the connection with spring boot and elasticsearch. But, getting below error when I start the application server.

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.transport.TransportClient]: Factory method 'elasticsearchClient' threw exception; nested exception is java.lang.IllegalArgumentException: unknown setting [password] please check that any required plugins are installed, or check the breaking changes documentation for removed settings
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    ... 96 common frames omitted
Caused by: java.lang.IllegalArgumentException: unknown setting [password] please check that any required plugins are installed, or check the breaking changes documentation for removed settings
    at org.elasticsearch.common.settings.AbstractScopedSettings.validate(AbstractScopedSettings.java:393) ~[elasticsearch-6.4.3.jar:6.4.3] 
P.J.Meisch
  • 18,013
  • 6
  • 50
  • 66
  • The TransportClient that you configure uses a native protocol (not HTTP/HTTP) and is normally running on port 9300. HTTP(S) is used when accessing a cluster with the REST client, that is normally using port 9200. So check that you are using the right client and how your server is set up. – P.J.Meisch Jun 15 '19 at 18:23
  • @Meisch, Thanks yes, I did check cloud environment (compose for elasticsearch) service, where I find credential and certificate information. Now, I need to pass credential and certificate from Spring boot application to elasticsearch for getting connection and for further communication. Could you please let me know which client is the right option since I am new to both spring boot data and elasticsearch? And they have provided a sample on how to connect to elastic search with Node JS. https://github.com/IBM-Cloud/compose-elasticsearch-helloworld-nodejs/blob/master/server.js – Chetan Ramaiah Jun 16 '19 at 05:16
  • Never worked with IBM Compse for Elastic, the documentation on their site is pretty nonexistent. Looking at the Javascript example it seems that you need to use the client certificate to access it, not a user/password. I'll try if I can set up something like that locally to test how to connect – P.J.Meisch Jun 16 '19 at 06:22
  • btw, which ES version do you use? The topic of your question mentions 6.6, but in the post you are talking about 6.4.3 - And been thinking about the user/password: you probably need that for ES and the certificate for the connection – P.J.Meisch Jun 16 '19 at 06:40
  • @Meisch, ES version at Cloud is 6.6.2. And in my local, I tried with ES 6.4.3 which comes with Spring boot 2.1.5. Thanks in advance. – Chetan Ramaiah Jun 16 '19 at 09:23
  • I tried with Jest client but getting SSL handshake issue. JestClientFactory factory = new JestClientFactory(); factory.setHttpClientConfig(new HttpClientConfig .Builder(Arrays.asList("https://usertemp:ABCSAD@alp-usba-north-3-portal.11.db2lay.com:32117/", "https://usertemp:ABCSAD@alp-usba-north-3-portal.12.db2lay.com:32117/")).multiThreaded(true).build()); JestClient client = factory.getObject(); _javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake, Caused by: java.io.EOFException: SSL peer shut down incorrectly_ – Chetan Ramaiah Jun 17 '19 at 11:01
  • From cloud environment, they have provided ca_certificate_base64 in string format. Should I decrypt the certificate and pass it to HttpClientConfig.Builder(Array.asList("user..". "user..", decryptedCertificate))? But in cloud, they have mentioned as **optional**. Please advice. – Chetan Ramaiah Jun 17 '19 at 11:14
  • I don't know how IBM Compose for elastic exactly authorizes. I am currently setting up a local ES 6.6.2, where I put an nginx in front that does basic authenitcation and SSL encryption with client certificates. I hope that I will get a working solution for that until tomorrow afternoon (CEST). I can't guarantee that it will work with your setup. Anyway, to use the Rest Client in Spring Data Elasticsearch you will have to use 3.2.0M4 (Boot 2.1.5 pulls in 3.1.8, that will not work). But it's possible to do that, I still need some more time to get this running. – P.J.Meisch Jun 17 '19 at 12:53
  • @Meisch, I will try to use 3.2.0M4 and let you know what I find. Thanks in advance. – Chetan Ramaiah Jun 17 '19 at 15:14

1 Answers1

2

As I already mentioned in the comments, I don't know how exactly IBM Compose for Elasticsearch sets up the connection security; my setup for this scenario is as follows:

Elasticsearch

  • Elasticsearch 6.6.2 running on my machine
  • an NGINX running as proxy in front of Elasticsearch with
    • Basic Authentication activated
    • SSL only
    • SSL client certificates needed

maven and versions

In order to be able to communicate via HTTPS you need to use the RestClient which is available in Spring Data Elasticsearch as of version 3.2 (which currently is available in version 3.2.0.M4). Spring Boot 2.1.5 pulls in Spring Data Elasticsearch 3.1.8, so you need to override the version. You also need to specify the Elasticsearch version to match Spring Boot Data 3.2.0.M4, so your pom.xml needs the following entries:

<properties>
    <java.version>1.8</java.version>
    <elasticsearch.version>6.7.2</elasticsearch.version>
    <spring-data-elasticsearch>3.2.0.M4</spring-data-elasticsearch>
</properties>

<repositories>
    <repository>
        <id>spring-libs-snapshot</id>
        <name>Spring Snapshot Repository</name>
        <url>https://repo.spring.io/libs-milestones</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-elasticsearch</artifactId>
        <version>${spring-data-elasticsearch}</version>
    </dependency>
</dependencies>

Bean configuration

In your program you can configure the Beans for Spring Data Elasticsearch with an implementation of AbstractElasticsearchConfiguration:

@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {

    private static final Logger LOG = LoggerFactory.getLogger(RestClientConfig.class);

    private static final String CERT_FILE = "client.p12";
    private static final String CERT_PASSWORD = "topsecret";
    private static final String USER_NAME = "user";
    private static final String USER_PASS = "password";

    @Override
    public RestHighLevelClient elasticsearchClient() {

        final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
                .connectedTo("localhost:443")  // set the address of the Elasticsearch cluster
                .usingSsl(createSSLContext())  // use the SSLContext with the client cert
                .withBasicAuth(USER_NAME, USER_PASS)   // use the headers for authentication
                .build();

        return RestClients.create(clientConfiguration).rest();
    }

    private SSLContext createSSLContext() {
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");

            KeyManager[] keyManagers = getKeyManagers();

            sslContext.init(keyManagers, null, null);

            return sslContext;
        } catch (Exception e) {
            LOG.error("cannot create SSLContext", e);
        }
        return null;
    }

    private KeyManager[] getKeyManagers()
            throws KeyStoreException, NoSuchAlgorithmException, IOException, CertificateException, UnrecoverableKeyException {
        try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(CERT_FILE)) {
            KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
            clientKeyStore.load(inputStream, CERT_PASSWORD.toCharArray());

            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            kmf.init(clientKeyStore, CERT_PASSWORD.toCharArray());
            return kmf.getKeyManagers();
        }
    }

    @Bean
    @Primary
    public ElasticsearchOperations elasticsearchTemplate() {
        return elasticsearchOperations();
    }
}

With this setup I can run my Spring Boot application against the Elasticsearch cluster using ElasticsearchRestTemplate and ElasticsearchRepository.

You probably need to adjust the code in the getKeyManagers() method to match what you have from Compose, but this should give you some point from where to start.

lauksas
  • 533
  • 7
  • 14
P.J.Meisch
  • 18,013
  • 6
  • 50
  • 66
  • @Meisch, thank you very much. From cloud, I am not getting certificate file instead they are providing trusted cert available as Base64 encoded data. So, I did write code which decodes the certificate and then add it to keystore. But, unable to figure out on passing sslcontext to ClientConfiguration.build(). Here, I am able to input code snippet due to number of characters allowed. – Chetan Ramaiah Jun 20 '19 at 10:44
  • after building a `SSLContext` you add it to the `ClientConfiguration` builder as argument to the `usingSsl()` method, like in my example. – P.J.Meisch Jun 20 '19 at 14:20
  • @Meisch, As per your sample code, I did try to implement RestClientConfig class but getting exception when I try to use ElasticsearchRepository so removed ElasticsearchRepository instead tried to ping the cluster rhlc.ping(RequestOptions.DEFAULT) exception is org.apache.http.ConnectionClosedException: Connection is closed [err] at org.elasticsearch.client.RestClient$SyncResponseListener.get(RestClient.java:942) Caused by: org.apache.http.ConnectionClosedException: Connection is closed [err] at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.endOfInput(HttpAsyncRequestExecutor.java:356) – Chetan Ramaiah Jun 23 '19 at 06:01
  • @Meisch, Thank you very much for your help and guidance. I did try to google for the exception but did not get much help. I am passing correct data to ClientConfiguration.builder().connectedTo(alp-usba-north-3-portal.11.db2lay.com:32117).usingSsl(sslcontext).withDefaultHeaders(header).build(); Now,header contains Basic authentication information. Could the issue be sslContext. If yes, could you please let me know any approach to get to the root cause? – Chetan Ramaiah Jun 23 '19 at 06:17
  • Below code snippet gets the sslContext from SslcontextConfig.init() and invoked at .usingSsl() method `private SSLContext createSSLContext() { try { SSLContext sslContext = null; ConfigurableBeanFactory configBeanFactory = sslcontectConfig.getConfigurableBeanFactory(); sslContext = (SSLContext) configBeanFactory.getBean("ca_certificate_base64"); return sslContext; } catch (Exception e) { LOG.error("cannot create SSLContext", e); } return null; }` I did try to google the exception but did not get much help. Could you please guide where am I going wrong? – Chetan Ramaiah Jun 23 '19 at 07:09
  • `SslcontextConfig.init() { try { SSLContext ctx = SSLContext.getInstance("TLSv1.2"); Base64TrustingTrustManager tm = new Base64TrustingTrustManager(e.getValue().getTrustedCert()); ctx.init(null, new TrustManager[] { tm }, null); beanFactory.registerSingleton(e.getKey(), ctx); beanFactory.registerSingleton(e.getKey() + ".factory", ctx.getSocketFactory()); } catch (Exception ex) { }` Base64TrustingTrustManager adds the certificate to keystore. – Chetan Ramaiah Jun 23 '19 at 07:14
  • I dont't know why you expose register the soketfactory in a beanfactory. To use the SSLContext with spring-data-elasticsearch you add it to a ClientConfiguration and pass that into the RestClient builder. After the you just inject a `RestHighLevelClient` into your app to acess Elasticsearch with it. – P.J.Meisch Jun 23 '19 at 15:25
  • @Meisch, I did try `private SSLContext ctx = null; SslcontextConfig.init() { try { ctx = SSLContext.getInstance("TLS"); Base64TrustingTrustManager tm = new Base64TrustingTrustManager(e.getValue().getTrustedCert()); ctx.init(null, new TrustManager[] { tm }, null); } catch (Exception ex) { }` but getting same exception i.e. _italic_org.apache.http.ConnectionClosedException: Connection is closed. Caused by: [err] org.apache.http.ConnectionClosedException: Connection is closed._italic_ – Chetan Ramaiah Jun 23 '19 at 17:31
  • I also tried `try { sslContext = SSLContext.getInstance(**"TLSv1.2"**); Base64TrustingTrustManager tm = new Base64TrustingTrustManager(e.getValue().getTrustedCert()); sslContext.init(null, new TrustManager[] { tm }, null);` but getting different exception. – Chetan Ramaiah Jun 23 '19 at 17:38
  • please find the exception _ javax.net.ssl.SSLHandshakeException: General SSLEngine problem [err] at org.elasticsearch.client.RestClient$SyncResponseListener.get(RestClient.java:947) Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem. Caused by: com.ibm.jsse2.util.h: PKIX path building failed: java.security.cert.CertPathBuilderException: PKIXCertPathBuilderImpl could not build a valid CertPath.; internal cause is: java.security.cert.CertPathValidatorException: The certificate issued by CN=DST Root CA X3, O=Digital SignatureTrustCo. is not trusted;Certificate chaining err_ – Chetan Ramaiah Jun 23 '19 at 17:42
  • You need a KeyManager to provide your certificate to the server and not a TrustManager which you would use to verify the server's certificate. – P.J.Meisch Jun 24 '19 at 19:07
  • @Meisch, let me try. Thanks – Chetan Ramaiah Jun 26 '19 at 06:47
  • I am able to connect to elasticsearch using '''RestClientBuilder builder = RestClient.builder(new HttpHost("", , "https")).setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);} }); RestHighLevelClient client = new RestHighLevelClient(builder); ''' I am able to ping elasticsearch service but not able to create index. – Chetan Ramaiah Jun 27 '19 at 09:44
  • @Meisch, I think we need to implement ElasticsearchRestTemplate instead of ElasticsearchRepository. Is that correct? Since I tried ElasticsearchRepository and got error message __java.lang.NoSuchMethodError: org/elasticsearch/client/IndicesClient.create_ – Chetan Ramaiah Jun 27 '19 at 09:55
  • _java.lang.NoSuchMethodError: org/elasticsearch/client/IndicesClient.create(Lorg/elasticsearch/action/admin/indices/create/CreateIndexRequest;[Lorg/apache/http/Header;)Lorg/elasticsearch/client/indices/CreateIndexResponse; (loaded from file:/.m2/repository/org/elasticsearch/client/elasticsearch-rest-high-level-client/6.6.2/elasticsearch-rest-high-level-client-6.6.2.jar) called from class org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate (loaded from file:/.m2/repository/org/springframework/data/spring-data-elasticsearch/3.2.0.M4/spring-data-elasticsearch-3.2.0.M4.jar)._ – Chetan Ramaiah Jun 27 '19 at 09:58
  • you need the 6.7.2 client libs, they work with the 6.6.2 server, check the `elasticsearch.version` property in my example. 3.2.0M4 ist built against 6.7.2 ES client – P.J.Meisch Jun 27 '19 at 19:45
  • @Meisch, thank you very much. Let me try to build the application using ES v6.7.2 and keep you posted. – Chetan Ramaiah Jun 28 '19 at 09:26
  • I tried following the example in documentation from spring website, it didn't worked, followed your code but with no SSL with a previously imported certificate (using default context) and it worked at first. Thanks for that. – lauksas Dec 30 '19 at 16:57
  • @lauksas thank you for updating the example. When I wrote this answer, the `withBasicAuth()` method was not yet available. I implemented that in July. – P.J.Meisch Dec 31 '19 at 11:18