Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions docs/getting-started/connecting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,74 @@ concise DSL-like code. This pattern is explained in more detail in
include-tagged::{doc-tests-src}/getting_started/ConnectingTest.java[first-request]
--------------------------------------------------

[discrete]
==== Using a secure connection

The <<java-rest-low>> documentation explains how to set up encrypted communications in detail.

In self-managed installations, Elasticsearch will start with security features like authentication and TLS enabled. To connect to the Elasticsearch cluster you’ll need to configure the {java-client} to use HTTPS with the generated CA certificate in order to make requests successfully.

When you start Elasticsearch for the first time you’ll see a distinct block like the one below in the output from Elasticsearch (you may have to scroll up if it’s been a while):

["source","xml"]
----------------------------------------------------------------
-> Elasticsearch security features have been automatically configured!
-> Authentication is enabled and cluster connections are encrypted.

-> Password for the elastic user (reset with `bin/elasticsearch-reset-password -u elastic`):
lhQpLELkjkrawaBoaz0Q

-> HTTP CA certificate SHA-256 fingerprint:
a52dd93511e8c6045e21f16654b77c9ee0f34aea26d9f40320b531c474676228
...
----------------------------------------------------------------

Note down the elastic user password and HTTP CA fingerprint for the next sections. In the examples below they will be stored in the variables `password` and `fingerprint` respectively.

Depending on the context, you have two options for verifying the HTTPS connection: either verifying with the CA certificate itself or using the CA certificate fingerprint. For both cases, the {java-client}'s `TransportUtils` class provides convenience methods to easily create an `SSLContext`.

[discrete]
===== Verifying HTTPS with a certificate fingerprint

This method of verifying the HTTPS connection uses the certificate fingerprint value noted down earlier.

["source","java"]
--------------------------------------------------
include-tagged::{doc-tests-src}/getting_started/ConnectingTest.java[create-secure-client-fingerprint]
--------------------------------------------------
<1> Create an `SSLContext` with the certificate fingerprint.
<2> Set up authentication.
<3> Do not forget to set the protocol to `https`!
<4> Configure the http client with the SSL and authentication configurations.

Note that the certificate fingerprint can also be calculated using `openssl x509` with the certificate file:
["source","bash"]
--------------------------------------------------
openssl x509 -fingerprint -sha256 -noout -in /path/to/http_ca.crt
--------------------------------------------------

If you don’t have access to the generated CA file from Elasticsearch you can use the following script to output the root CA fingerprint of the Elasticsearch instance with `openssl s_client`:

["source","bash"]
--------------------------------------------------
openssl s_client -connect localhost:9200 -servername localhost -showcerts </dev/null 2>/dev/null \
| openssl x509 -fingerprint -sha256 -noout -in /dev/stdin
--------------------------------------------------

[discrete]
===== Verifying HTTPS with a CA certificate

The generated root CA certificate can be found in the `certs` directory in your Elasticsearch config location. If you’re running Elasticsearch in Docker there is {es-docs}/docker.html[additional documentation] for retrieving the CA certificate.

Once you have made the `http_ca.crt` file available to your application, you can use it to set up the client:

["source","java"]
--------------------------------------------------
include-tagged::{doc-tests-src}/getting_started/ConnectingTest.java[create-secure-client-cert]
--------------------------------------------------
<1> Create an `SSLContext` with the `http_ca.crt` file.
<2> Set up authentication.
<3> Do not forget to set the protocol to `https`!
<4> Configure the http client with the SSL and authentication configurations.

{doc-tests-blurb}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License 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 co.elastic.clients.transport;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;

public class TransportUtils {

/**
* Creates an <code>SSLContext</code> from the self-signed <code>http_ca.crt</code> certificate created by Elasticsearch during
* its first start.
*
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-stack-security.html">Elasticsearch
* documentation</a>
*/
public static SSLContext sslContextFromHttpCaCrt(File file) throws IOException {
try(InputStream in = new FileInputStream(file)) {
return sslContextFromHttpCaCrt(in);
}
}

/**
* Creates an <code>SSLContext</code> from the self-signed <code>http_ca.crt</code> certificate created by Elasticsearch during
* its first start.
*
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-stack-security.html">Elasticsearch
* documentation</a>
*/
public static SSLContext sslContextFromHttpCaCrt(InputStream in) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate certificate = cf.generateCertificate(in);

final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("elasticsearch-ca", certificate);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext;

} catch (CertificateException | NoSuchAlgorithmException | KeyManagementException | KeyStoreException | IOException e) {
throw new RuntimeException(e);
}
}

/**
* Creates an <code>SSLContext</code> from the SHA-256 fingerprint of self-signed <code>http_ca.crt</code> certificate output by
* Elasticsearch at startup time.
*
* @param fingerPrint the SHA-256 fingerprint. Can be uppercase or lowercase, with or without colons separating bytes
* @see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/configuring-stack-security.html">Elasticsearch
* documentation</a>
*/
public static SSLContext sslContextFromCaFingerprint(String fingerPrint) {

fingerPrint = fingerPrint.replace(":", "");
int len = fingerPrint.length();
byte[] fpBytes = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
fpBytes[i / 2] = (byte) (
(Character.digit(fingerPrint.charAt(i), 16) << 4) +
Character.digit(fingerPrint.charAt(i+1), 16)
);
}

try {
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
throw new CertificateException("This is a client-side only trust manager");
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

// The CA root is the last element of the chain
X509Certificate anchor = chain[chain.length - 1];

byte[] bytes;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(anchor.getEncoded());
bytes = md.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}

if (Arrays.equals(fpBytes, bytes)) {
return;
}

throw new CertificateException("Untrusted certificate: " + anchor.getSubjectX500Principal());
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new X509TrustManager[] { tm }, null);
return sslContext;

} catch (NoSuchAlgorithmException | KeyManagementException e) {
// Exceptions that should normally not occur
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.TransportUtils;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import javax.net.ssl.SSLContext;
import java.io.File;

public class ConnectingTest {

@Disabled // we don't have a running ES
Expand Down Expand Up @@ -65,6 +72,76 @@ public void createClient() throws Exception {
//end::first-request
}

@Disabled // we don't have a running ES
@Test
public void createSecureClientCert() throws Exception {

// Create the low-level client
String host = "localhost";
int port = 9200;
String login = "elastic";
String password = "changeme";

//tag::create-secure-client-cert
File certFile = new File("/path/to/http_ca.crt");

SSLContext sslContext = TransportUtils
.sslContextFromHttpCaCrt(certFile); // <1>

BasicCredentialsProvider credsProv = new BasicCredentialsProvider(); // <2>
credsProv.setCredentials(
AuthScope.ANY, new UsernamePasswordCredentials(login, password)
);

RestClient restClient = RestClient
.builder(new HttpHost(host, port, "https")) // <3>
.setHttpClientConfigCallback(hc -> hc
.setSSLContext(sslContext) // <4>
.setDefaultCredentialsProvider(credsProv)
)
.build();

// Create the transport and the API client
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);
//end::create-secure-client-cert
}

@Disabled // we don't have a running ES
@Test
public void createSecureClientFingerPrint() throws Exception {

// Create the low-level client
String host = "localhost";
int port = 9200;
String login = "elastic";
String password = "changeme";

//tag::create-secure-client-fingerprint
String fingerprint = "<certificate fingerprint>";

SSLContext sslContext = TransportUtils
.sslContextFromCaFingerprint(fingerprint); // <1>

BasicCredentialsProvider credsProv = new BasicCredentialsProvider(); // <2>
credsProv.setCredentials(
AuthScope.ANY, new UsernamePasswordCredentials(login, password)
);

RestClient restClient = RestClient
.builder(new HttpHost(host, port, "https")) // <3>
.setHttpClientConfigCallback(hc -> hc
.setSSLContext(sslContext) // <4>
.setDefaultCredentialsProvider(credsProv)
)
.build();

// Create the transport and the API client
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
ElasticsearchClient client = new ElasticsearchClient(transport);
//end::create-secure-client-fingerprint
}

private void processProduct(Product p) {}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ public class ElasticsearchTestServer implements AutoCloseable {

private final String[] plugins;
private volatile ElasticsearchContainer container;
private int port;
private final JsonpMapper mapper = new JsonbJsonpMapper();
private RestClient restClient;
private ElasticsearchTransport transport;
Expand Down Expand Up @@ -102,7 +101,7 @@ public synchronized ElasticsearchTestServer start() {
.withPassword("changeme");
container.start();

port = container.getMappedPort(9200);
int port = container.getMappedPort(9200);

boolean useTLS = version.major() >= 8;
HttpHost host = new HttpHost("localhost", port, useTLS ? "https": "http");
Expand Down Expand Up @@ -165,8 +164,8 @@ public void close() {
container = null;
}

public int port() {
return port;
public ElasticsearchContainer container() {
return this.container;
}

public RestClient restClient() {
Expand Down
Loading