Configuring TLS for Connecting Spring Boot to Elasticsearch
How to use self-signed certificates in Spring Boot to connect to Elasticsearch when using Elastic Cloud on Kubernetes (ECK)
I wanted to put together an article that combines everything I found around the web on how to use self-signed certificates with Spring Boot when using Elastic Cloud on Kubernetes.
The Keystore
There are two options for creating the keystore we need to connect securely to Elasticsearch: using the keytool or Keystore API. But I found that creating the Keystore programmatically was the better approach when publishing a Kubernetes cluster in the cloud since this configuration allows us to use the same configurations for a local development environment and in a cloud Kubernetes cluster such as AWS EKS.
Creating keystore with keytool
To create the keystore:
Download the cert from cluster secrets and save as
tls.crt
kubectl get secret "es1-es-http-certs-public" -o go-template='{{index .data "tls.crt" | base64decode }}' > tls.crt
Use the keytool to create the keystore and import the certificate
keytool -import -v -trustcacerts -file tls.crt -keystore keystore.jks -keypass changeit -storepass changeit
Java Keystore API
For more information on the API see Baeldung's awesome article on the java keystore
To create a keystore programmatically that will store the certificate retrieved from Kubernetes cluster secrets used to authenticate with ES cluster
public class SSLConfig {
private final char[] keyStorePass = "changeit".toCharArray();
private final String certPathName = new File("/etc/es-certificates/tls.crt").isFile() ? "/etc/es-certificates/tls.crt" : "tls.crt";
private final File keyStoreFile;
public SSLConfig() throws Exception {
keyStoreFile = this.generateKeyStoreFile();
}
Certificate generateCert() throws Exception {
InputStream inStream = new FileInputStream(certPathName);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertificate(inStream);
}
File generateKeyStoreFile() throws Exception {
String keyStoreName = "keystore.jks";
File f = new File(keyStoreName);
if (f.isFile()) return f;
Certificate cert = this.generateCert();
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setCertificateEntry("es-http-public", cert);
ks.store(new FileOutputStream(keyStoreName), keyStorePass);
return new File(keyStoreName);
}
public SSLContext getSSLContext() throws Exception {
SSLContextBuilder builder = SSLContexts.custom();
builder.loadTrustMaterial(keyStoreFile, keyStorePass, new TrustSelfSignedStrategy());
return builder.build();
}
}
Spring Elasticsearch Configuration
The Java High-Level REST Client is based on the Low-Level Client, which has can be used for encrypted communication
@Configuration
@ComponentScan
public class ESConfig extends AbstractElasticsearchConfiguration {
@Value("${elasticsearch.url}")
public String elasticsearchUrl;
@Value("${elasticsearch.port}")
public String elasticsearchPort;
@Value("${elasticsearch.username}")
public String elasticsearchUsername;
@Value("${elasticsearch.password}")
public String elasticsearchPassword;
private SSLConfig sslConfig;
public ESConfig() throws Exception {
sslConfig = new SSLConfig();
}
@Override
public RestHighLevelClient elasticsearchClient() {
SSLContext sslContext = null;
try {
sslContext = sslConfig.getSSLContext();
} catch (Exception e) {
e.printStackTrace();
}
final ClientConfiguration config = ClientConfiguration.builder()
.connectedTo(elasticsearchUrl + ":" + elasticsearchPort)
.usingSsl(sslContext)
.withBasicAuth(elasticsearchUsername, elasticsearchPassword)
.build();
return RestClients.create(config).rest();
}
}
Using TLS Secret as a file in the Spring Pod
To use the certificate stored as a secret by ECK, we can mount it in our deployment manifest and make it available in the API container.
apiVersion: apps/v1
kind: Deployment
metadata:
name: search-api
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: search-api
template:
metadata:
labels:
app: search-api
namespace: default
spec:
containers:
- image: $IMAGE
name: search-api
imagePullPolicy: Always
ports:
- containerPort: 8080
volumeMounts:
- name: es-certificates
mountPath: "/etc/es-certificates"
env:
- name: ES_CERT
valueFrom:
secretKeyRef:
name: es1-es-http-certs-public
key: tls.crt
- name: ES_USER
value: "elastic"
- name: ES_PWD
valueFrom:
secretKeyRef:
name: es1-es-elastic-user
key: elastic
- name: ES_URL
value: "es1-es-http"
volumes:
- name: es-certificates
secret:
secretName: es1-es-http-certs-public