title: Connection to AWS Elasticsearch service breaks when setting truststore through system.properties
body: <p>I'm building an application that uses both <a href="https://aws.amazon.com/elasticsearch-service/" rel="nofollow noreferrer">AWS Elasticsearch service</a> and <a href="https://aws.amazon.com/documentdb/" rel="nofollow noreferrer">AWS DocumentDB</a> (with mongoDB compatibility). </p>
  
  <p>In order to connect to Elasticsearch service I've replicated the sample project provided by awsdocs: <a href="https://github.com/awsdocs/amazon-elasticsearch-service-developer-guide/blob/master/sample_code/java/amazon-es-docs-sample-client.zip" rel="nofollow noreferrer">https://github.com/awsdocs/amazon-elasticsearch-service-developer-guide/blob/master/sample_code/java/amazon-es-docs-sample-client.zip</a></p>
  
  <p>The sample project contains two files <strong>AWSRequestSigningApacheInterceptor.java</strong>, which acts as a proxy for the normal ElasticSearch RestClient, and a sample test called <strong>AmazonElasticsearchServiceClient.java</strong>. These are posted at the bottom. I've tried it out and it works fine.</p>
  
  <p>Now to the problem:</p>
  
  <p>In order to connect to DocumentDB by SSL I need to fiddle with System.setProperties(), which seems to break down the Elasticsearch service connection. Similar to the process described by AWS for <a href="https://docs.aws.amazon.com/documentdb/latest/developerguide/connect.html" rel="nofollow noreferrer">connecting to a DocumentDB cluster programmatically</a> I'm using an SSL context helper in order to set keystore and put the pem file required for connection to DocumentDB in it. Running the file <strong>SSLContextHelper.java</strong> breaks the ElasticSearch connection producing the following errors:</p>
  
  <blockquote>
    <p>javax.net.ssl.SSLHandshakeException: General SSLEngine problem Caused
    by: sun.security.validator.ValidatorException: PKIX path building
    failed: sun.security.provider.certpath.SunCertPathBuilderException:
    unable to find valid certification path to requested target Caused by:
    sun.security.provider.certpath.SunCertPathBuilderException: unable to
    find valid certification path to requested target</p>
  </blockquote>
  
  <p>I'm thinking that this issue might be because the AWSRequestSigningApacheInterceptor.java utilizes System properties related to the truststore. Though I don't know how to work around it. Any suggestions?</p>
  
  <p><strong>AWSRequestSigningApacheInterceptor.java</strong></p>
  
  <pre><code>/*
   * Copyright 2012-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
   * the License. A copy of the License is located at
   *
   * http://aws.amazon.com/apache2.0
   *
   * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
   * and limitations under the License.
   */
  package com.amazonaws.http;
  
  import com.amazonaws.DefaultRequest;
  import com.amazonaws.auth.AWSCredentialsProvider;
  import com.amazonaws.auth.Signer;
  import org.apache.http.Header;
  import org.apache.http.HttpEntityEnclosingRequest;
  import org.apache.http.HttpException;
  import org.apache.http.HttpHost;
  import org.apache.http.HttpRequest;
  import org.apache.http.HttpRequestInterceptor;
  import org.apache.http.NameValuePair;
  import org.apache.http.client.utils.URIBuilder;
  import org.apache.http.entity.BasicHttpEntity;
  import org.apache.http.message.BasicHeader;
  import org.apache.http.protocol.HttpContext;
  
  import java.io.IOException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
  import java.util.TreeMap;
  
  import static org.apache.http.protocol.HttpCoreContext.HTTP_TARGET_HOST;
  
  /**
   * An {@link HttpRequestInterceptor} that signs requests using any AWS {@link Signer}
   * and {@link AWSCredentialsProvider}.
   */
  public class AWSRequestSigningApacheInterceptor implements HttpRequestInterceptor {
      /**
       * The service that we're connecting to. Technically not necessary.
       * Could be used by a future Signer, though.
       */
      private final String service;
  
      /**
       * The particular signer implementation.
       */
      private final Signer signer;
  
      /**
       * The source of AWS credentials for signing.
       */
      private final AWSCredentialsProvider awsCredentialsProvider;
  
      /**
       *
       * @param service service that we're connecting to
       * @param signer particular signer implementation
       * @param awsCredentialsProvider source of AWS credentials for signing
       */
      public AWSRequestSigningApacheInterceptor(final String service,
                                  final Signer signer,
                                  final AWSCredentialsProvider awsCredentialsProvider) {
          this.service = service;
          this.signer = signer;
          this.awsCredentialsProvider = awsCredentialsProvider;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public void process(final HttpRequest request, final HttpContext context)
              throws HttpException, IOException {
          URIBuilder uriBuilder;
          try {
              uriBuilder = new URIBuilder(request.getRequestLine().getUri());
          } catch (URISyntaxException e) {
              throw new IOException("Invalid URI" , e);
          }
  
          // Copy Apache HttpRequest to AWS DefaultRequest
          DefaultRequest&lt;?&gt; signableRequest = new DefaultRequest&lt;&gt;(service);
  
          HttpHost host = (HttpHost) context.getAttribute(HTTP_TARGET_HOST);
          if (host != null) {
              signableRequest.setEndpoint(URI.create(host.toURI()));
          }
          final HttpMethodName httpMethod =
                  HttpMethodName.fromValue(request.getRequestLine().getMethod());
          signableRequest.setHttpMethod(httpMethod);
          try {
              signableRequest.setResourcePath(uriBuilder.build().getRawPath());
          } catch (URISyntaxException e) {
              throw new IOException("Invalid URI" , e);
          }
  
          if (request instanceof HttpEntityEnclosingRequest) {
              HttpEntityEnclosingRequest httpEntityEnclosingRequest =
                      (HttpEntityEnclosingRequest) request;
              if (httpEntityEnclosingRequest.getEntity() != null) {
                  signableRequest.setContent(httpEntityEnclosingRequest.getEntity().getContent());
              }
          }
          signableRequest.setParameters(nvpToMapParams(uriBuilder.getQueryParams()));
          signableRequest.setHeaders(headerArrayToMap(request.getAllHeaders()));
  
          // Sign it
          signer.sign(signableRequest, awsCredentialsProvider.getCredentials());
  
          // Now copy everything back
          request.setHeaders(mapToHeaderArray(signableRequest.getHeaders()));
          if (request instanceof HttpEntityEnclosingRequest) {
              HttpEntityEnclosingRequest httpEntityEnclosingRequest =
                      (HttpEntityEnclosingRequest) request;
              if (httpEntityEnclosingRequest.getEntity() != null) {
                  BasicHttpEntity basicHttpEntity = new BasicHttpEntity();
                  basicHttpEntity.setContent(signableRequest.getContent());
                  httpEntityEnclosingRequest.setEntity(basicHttpEntity);
              }
          }
      }
  
      /**
       *
       * @param params list of HTTP query params as NameValuePairs
       * @return a multimap of HTTP query params
       */
      private static Map&lt;String, List&lt;String&gt;&gt; nvpToMapParams(final List&lt;NameValuePair&gt; params) {
          Map&lt;String, List&lt;String&gt;&gt; parameterMap = new TreeMap&lt;&gt;(String.CASE_INSENSITIVE_ORDER);
          for (NameValuePair nvp : params) {
              List&lt;String&gt; argsList =
                      parameterMap.computeIfAbsent(nvp.getName(), k -&gt; new ArrayList&lt;&gt;());
              argsList.add(nvp.getValue());
          }
          return parameterMap;
      }
  
      /**
       * @param headers modeled Header objects
       * @return a Map of header entries
       */
      private static Map&lt;String, String&gt; headerArrayToMap(final Header[] headers) {
          Map&lt;String, String&gt; headersMap = new TreeMap&lt;&gt;(String.CASE_INSENSITIVE_ORDER);
          for (Header header : headers) {
              if (!skipHeader(header)) {
                  headersMap.put(header.getName(), header.getValue());
              }
          }
          return headersMap;
      }
  
      /**
       * @param header header line to check
       * @return true if the given header should be excluded when signing
       */
      private static boolean skipHeader(final Header header) {
          return ("content-length".equalsIgnoreCase(header.getName())
                  &amp;&amp; "0".equals(header.getValue())) // Strip Content-Length: 0
                  || "host".equalsIgnoreCase(header.getName()); // Host comes from endpoint
      }
  
      /**
       * @param mapHeaders Map of header entries
       * @return modeled Header objects
       */
      private static Header[] mapToHeaderArray(final Map&lt;String, String&gt; mapHeaders) {
          Header[] headers = new Header[mapHeaders.size()];
          int i = 0;
          for (Map.Entry&lt;String, String&gt; headerEntry : mapHeaders.entrySet()) {
              headers[i++] = new BasicHeader(headerEntry.getKey(), headerEntry.getValue());
          }
          return headers;
      }
  }
  </code></pre>
  
  <p><strong>AmazonElasticsearchServiceClient.java</strong></p>
  
  <pre><code>package com.amazonaws.samples;
  import org.apache.http.HttpHost;
  import org.apache.http.HttpRequestInterceptor;
  import org.elasticsearch.ElasticsearchStatusException;
  import org.elasticsearch.action.bulk.BulkItemResponse;
  import org.elasticsearch.action.bulk.BulkRequest;
  import org.elasticsearch.action.bulk.BulkResponse;
  import org.elasticsearch.action.index.IndexRequest;
  import org.elasticsearch.action.index.IndexResponse;
  import org.elasticsearch.client.RequestOptions;
  import org.elasticsearch.client.RestClient;
  import org.elasticsearch.client.RestHighLevelClient;
  import com.amazonaws.auth.AWS4Signer;
  import com.amazonaws.auth.AWSCredentialsProvider;
  import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
  import com.amazonaws.http.AWSRequestSigningApacheInterceptor;
  import java.io.IOException;
  import java.util.HashMap;
  import java.util.Map;
  
  public class AmazonElasticsearchServiceClient {
  
      private static final String serviceName = "es";
      private static final String region = "us-west-2";
      private static final String aesEndpoint = ""; // e.g. https://search-mydomain.us-west-1.es.amazonaws.com
      private static final String type = "_doc";
  
      static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain();
  
      public static void main(String[] args) throws IOException {
  
          RestHighLevelClient aesClient = aesClient();
  
          String index = "java-client-test-index";
  
          // Create a document that simulates a simple log line from a web server
          Map&lt;String, Object&gt; document = new HashMap&lt;&gt;();
          document.put("method", "GET");
          document.put("client_ip_address", "123.456.78.90");
          document.put("timestamp", "10/Oct/2000:14:56:14 -0700");
  
          System.out.println("Demoing a single index request:");
          String id = "1";
          IndexRequest indexRequest = new IndexRequest(index, type, id).source(document);
          IndexResponse indexResponse = aesClient.index(indexRequest, RequestOptions.DEFAULT);
          System.out.println(indexResponse.toString());
  
          System.out.println("Demoing a 1 MB bulk request:");
          BulkRequest bulkRequest = new BulkRequest();
  
          // Add documents (the simple log line from earlier) to the request until it exceeds 1 MB
          while (bulkRequest.estimatedSizeInBytes() &lt; 1000000) {
              // By not specifying an ID, these documents get auto-assigned IDs
              bulkRequest.add(new IndexRequest(index, type).source(document));
          }
  
          try {
              // Send the request and get the response
              BulkResponse bulkResponse = aesClient.bulk(bulkRequest, RequestOptions.DEFAULT);
  
              // Check the response for failures
              if (bulkResponse.hasFailures()) {
                  System.out.println("Encountered failures:");
                  for (BulkItemResponse bulkItemResponse : bulkResponse) {
                      if (bulkItemResponse.isFailed()) {
                          System.out.println(bulkItemResponse.getFailureMessage());
                      }
                  }
              }
              else {
                  System.out.println("No failures!");
                  // Uncomment these lines for a line-by-line summary
  //                for (BulkItemResponse bulkItemResponse : bulkResponse) {
  //                    System.out.println(bulkItemResponse.getResponse().toString());
  //                }
              }
          }
  
          // Usually happens when the request size is too large
          catch (ElasticsearchStatusException e) {
              System.out.println("Encountered exception:");
              System.out.println(e.toString());
          }
      }
  
      // Adds the interceptor to the Elasticsearch REST client
      public static RestHighLevelClient aesClient() {
          AWS4Signer signer = new AWS4Signer();
          signer.setServiceName(serviceName);
          signer.setRegionName(region);
          HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(serviceName, signer, credentialsProvider);
          return new RestHighLevelClient(RestClient.builder(HttpHost.create(aesEndpoint)).setHttpClientConfigCallback(hacb -&gt; hacb.addInterceptorLast(interceptor)));
      }
  }
  </code></pre>
  
  <p><strong>SSLContextHelper.java</strong></p>
  
  <pre><code>package com.amazonaws.http;
  
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.lang.invoke.MethodHandles;
  import java.net.URL;
  import java.security.KeyStore;
  import java.security.cert.CertificateFactory;
  import java.security.cert.X509Certificate;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.util.StringUtils;
  
  
  
  public class SSLContextHelper {
  
      private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
      private static final String DEFAULT_SSL_CERTIFICATE = "rds-combined-ca-bundle.pem";
      private static final String SSL_CERTIFICATE = "sslCertificate";
      private static final String KEY_STORE_TYPE = "JKS";
      private static final String KEY_STORE_PROVIDER = "SUN";
      private static final String KEY_STORE_FILE_PREFIX = "sys-connect-via-ssl-test-cacerts";
      private static final String KEY_STORE_FILE_SUFFIX = ".jks";
      private static final String DEFAULT_KEY_STORE_PASSWORD = "changeit";
      private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";
      private static final String SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
      private static final String SSL_TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType";
  
  
      public static void setSslProperties() {
  
          try {
              String sslCertificate= System.getProperty(SSL_CERTIFICATE);
              if(StringUtils.isEmpty(sslCertificate)) {
                  sslCertificate= DEFAULT_SSL_CERTIFICATE;
              }
              logger.info(" ssl certificate path {}",sslCertificate);
              System.setProperty(SSL_TRUST_STORE, createKeyStoreFile(sslCertificate));
              System.setProperty(SSL_TRUST_STORE_TYPE, KEY_STORE_TYPE);
              System.setProperty(SSL_TRUST_STORE_PASSWORD, DEFAULT_KEY_STORE_PASSWORD);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      private static String createKeyStoreFile(String sslCertificate) throws Exception {
          return createKeyStoreFile(createCertificate(sslCertificate)).getPath();
      }
  
      private static X509Certificate createCertificate(String sslCertificate) throws Exception {
          CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
          URL url = new File(sslCertificate).toURI().toURL();
          if (url == null) {
              throw new Exception();
          }
          try (InputStream certInputStream = url.openStream()) {
              return (X509Certificate) certFactory.generateCertificate(certInputStream);
          }
      }
  
      private static File createKeyStoreFile(X509Certificate rootX509Certificate) throws Exception {
          File keyStoreFile = File.createTempFile(KEY_STORE_FILE_PREFIX, KEY_STORE_FILE_SUFFIX);
          try (FileOutputStream fos = new FileOutputStream(keyStoreFile.getPath())) {
              KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE, KEY_STORE_PROVIDER);
              ks.load(null);
              ks.setCertificateEntry("rootCaCertificate", rootX509Certificate);
              ks.store(fos, DEFAULT_KEY_STORE_PASSWORD.toCharArray());
          }
          return keyStoreFile;
      }
  
  }
  </code></pre>