Using NodeJS to Connect to Elasticsearch with a Private Certificate Authority


I was asked to help troubleshoot a NodeJS project recently where the team was encountering trouble connecting to an elasticsearch instance securely (via https/tls). They would get an error back about 'self signed certificate in certificate chain`. In examining further, we were able to come up with a client configuration for the elasticsearch library that addressed the issue.

References

Error

This was the error the team consistently received:

Elasticsearch ERROR: 2018-11-20T11:19:38Z
  Error: Request error, retrying
  HEAD https://es.domain.tld:3003/ => self signed certificate in certificate chain
      at Log.error (/path/to/project/node_modules/elasticsearch/src/lib/log.js:226:56)
      at checkRespForFailure (/path/to/project/node_modules/elasticsearch/src/lib/transport.js:259:18)
      at HttpConnector.<anonymous> (/path/to/project/node_modules/elasticsearch/src/lib/connectors/http.js:163:7)
      at ClientRequest.wrapper (/path/to/project/node_modules/lodash/lodash.js:4935:19)
      at ClientRequest.emit (events.js:182:13)
      at TLSSocket.socketErrorListener (_http_client.js:391:9)
      at TLSSocket.emit (events.js:182:13)
      at emitErrorNT (internal/streams/destroy.js:82:8)
      at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
      at process._tickCallback (internal/process/next_tick.js:63:19)

Investigation & Solution

I'm pretty new to NodeJS so I setup a test project with a 'return to zero' approach to troubleshoot the certificate error. In playing around with the options, I found this config worked with the elastic server having a certificate issued by a private CA and intermediate root Certificate Authority (complete toy project source follows):

var elasticsearch=require('elasticsearch');
var fs = require('fs');

var client = new elasticsearch.Client( {
    requestTimeout: 5000,
    host: [{
        protocol: "https",
    host: "es.domain.tld",
    port: 3003,
        auth: "usernamehere:passwordhere",
    }],
    ssl: {
        // Load the CA pem as well as the intermediate root pem
        ca: [fs.readFileSync('./ca.pem'), fs.readFileSync('./intermediateRoot.pem')],

        // This ensures that certificates that are not signed by the 'ca' above get rejected
        rejectUnauthorized: true
    }
});

// Simple test to see if we can access the elastic search server
client.ping({
    requestTimeout: 2000
}, function(error){
    if (error) {
        console.error("not ok");
        console.log(error);
    } else {
        console.log("a ok");
    }
});

The key for us was loading both the CA and Intermediate Root CA certificates in the right order under the ssl object. With that configuration we were able to successfully connect to elastic