diff --git a/client/rest-high-level/build.gradle b/client/rest-high-level/build.gradle
index c608d7c91f16e..bfe7c3d956cd2 100644
--- a/client/rest-high-level/build.gradle
+++ b/client/rest-high-level/build.gradle
@@ -76,6 +76,8 @@ forbiddenApisMain {
addSignatureFiles 'http-signatures'
signaturesFiles += files('src/main/resources/forbidden/rest-high-level-signatures.txt')
}
+File nodeCert = file("./testnode.crt")
+File nodeTrustStore = file("./testnode.jks")
integTestRunner {
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
@@ -85,11 +87,17 @@ integTestRunner {
integTestCluster {
setting 'xpack.license.self_generated.type', 'trial'
setting 'xpack.security.enabled', 'true'
+ // Truststore settings are not used since TLS is not enabled. Included for testing the get certificates API
+ setting 'xpack.ssl.certificate_authorities', 'testnode.crt'
+ setting 'xpack.security.transport.ssl.truststore.path', 'testnode.jks'
+ setting 'xpack.security.transport.ssl.truststore.password', 'testnode'
setupCommand 'setupDummyUser',
'bin/elasticsearch-users',
'useradd', System.getProperty('tests.rest.cluster.username', 'test_user'),
'-p', System.getProperty('tests.rest.cluster.password', 'test-password'),
'-r', 'superuser'
+ extraConfigFile nodeCert.name, nodeCert
+ extraConfigFile nodeTrustStore.name, nodeTrustStore
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
index 7192d82f474a8..8afaa3551ad88 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
@@ -22,6 +22,8 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
+import org.elasticsearch.client.security.GetSslCertificatesRequest;
+import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.EmptyResponse;
@@ -133,6 +135,33 @@ public void disableUserAsync(DisableUserRequest request, RequestOptions options,
EmptyResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Synchronously retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
+ * See
+ * the docs for more.
+ *
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the get certificates call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public GetSslCertificatesResponse getSslCertificates(RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(GetSslCertificatesRequest.INSTANCE, GetSslCertificatesRequest::getRequest,
+ options, GetSslCertificatesResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
+ * See
+ * the docs for more.
+ *
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void getSslCertificatesAsync(RequestOptions options, ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(GetSslCertificatesRequest.INSTANCE, GetSslCertificatesRequest::getRequest,
+ options, GetSslCertificatesResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Change the password of a user of a native realm or built-in user synchronously.
* See
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesRequest.java
new file mode 100644
index 0000000000000..c4dbef4e422b6
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesRequest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 org.elasticsearch.client.security;
+
+import org.apache.http.client.methods.HttpGet;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * Request object to retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
+ */
+public final class GetSslCertificatesRequest implements Validatable, ToXContentObject {
+
+ public static final GetSslCertificatesRequest INSTANCE = new GetSslCertificatesRequest();
+ private final Request request;
+
+ private GetSslCertificatesRequest() {
+ request = new Request(HttpGet.METHOD_NAME, "/_xpack/ssl/certificates");
+ }
+
+ public Request getRequest() {
+ return request;
+ }
+
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder.startObject().endObject();
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesResponse.java
new file mode 100644
index 0000000000000..80f016b3ae1bf
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetSslCertificatesResponse.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 org.elasticsearch.client.security;
+
+import org.elasticsearch.client.security.support.CertificateInfo;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.XContentParserUtils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response object when retrieving the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
+ * Returns a list of {@link CertificateInfo} objects describing each of the certificates.
+ */
+public final class GetSslCertificatesResponse {
+
+ private final List certificates;
+
+ public GetSslCertificatesResponse(List certificates) {
+ this.certificates = certificates;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final GetSslCertificatesResponse that = (GetSslCertificatesResponse) o;
+ return Objects.equals(this.certificates, that.certificates);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(certificates);
+ }
+
+ public static GetSslCertificatesResponse fromXContent(XContentParser parser) throws IOException {
+ List certificates = new ArrayList<>();
+ XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_ARRAY, parser.nextToken(), parser::getTokenLocation);
+ while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
+ certificates.add(CertificateInfo.PARSER.parse(parser, null));
+ }
+ return new GetSslCertificatesResponse(certificates);
+ }
+
+ public List getCertificates() {
+ return certificates;
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/CertificateInfo.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/CertificateInfo.java
new file mode 100644
index 0000000000000..28fd1c61c0d5c
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/support/CertificateInfo.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch 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 org.elasticsearch.client.security.support;
+
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+/**
+ * Simple model of an X.509 certificate
+ */
+public final class CertificateInfo {
+ public static final ParseField PATH = new ParseField("path");
+ public static final ParseField FORMAT = new ParseField("format");
+ public static final ParseField ALIAS = new ParseField("alias");
+ public static final ParseField SUBJECT_DN = new ParseField("subject_dn");
+ public static final ParseField SERIAL_NUMBER = new ParseField("serial_number");
+ public static final ParseField HAS_PRIVATE_KEY = new ParseField("has_private_key");
+ public static final ParseField EXPIRY = new ParseField("expiry");
+
+ private final String path;
+ private final String format;
+ private final String alias;
+ private final String subjectDn;
+ private final String serialNumber;
+ private final boolean hasPrivateKey;
+ private final String expiry;
+
+ public CertificateInfo(String path, String format, @Nullable String alias, String subjectDn, String serialNumber, boolean hasPrivateKey,
+ String expiry) {
+ this.path = path;
+ this.format = format;
+ this.alias = alias;
+ this.subjectDn = subjectDn;
+ this.serialNumber = serialNumber;
+ this.hasPrivateKey = hasPrivateKey;
+ this.expiry = expiry;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public String getSubjectDn() {
+ return subjectDn;
+ }
+
+ public String getSerialNumber() {
+ return serialNumber;
+ }
+
+ public boolean isHasPrivateKey() {
+ return hasPrivateKey;
+ }
+
+ public String getExpiry() {
+ return expiry;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("certificate_info",
+ true, args -> new CertificateInfo((String) args[0], (String) args[1], (String) args[2], (String) args[3], (String) args[4],
+ (boolean) args[5], (String) args[6]));
+
+ static {
+ PARSER.declareString(constructorArg(), PATH);
+ PARSER.declareString(constructorArg(), FORMAT);
+ PARSER.declareStringOrNull(constructorArg(), ALIAS);
+ PARSER.declareString(constructorArg(), SUBJECT_DN);
+ PARSER.declareString(constructorArg(), SERIAL_NUMBER);
+ PARSER.declareBoolean(constructorArg(), HAS_PRIVATE_KEY);
+ PARSER.declareString(constructorArg(), EXPIRY);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+
+ final CertificateInfo that = (CertificateInfo) other;
+ return this.path.equals(that.path)
+ && this.format.equals(that.format)
+ && this.hasPrivateKey == that.hasPrivateKey
+ && Objects.equals(this.alias, that.alias)
+ && this.serialNumber.equals(that.serialNumber)
+ && this.subjectDn.equals(that.subjectDn)
+ && this.expiry.equals(that.expiry);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path, format, alias, subjectDn, serialNumber, hasPrivateKey, expiry);
+ }
+
+ public static CertificateInfo fromXContent(XContentParser parser) throws IOException {
+ return PARSER.parse(parser, null);
+ }
+}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
index acdfc50b5a13a..fda7ecdd6d6a2 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java
@@ -721,10 +721,16 @@ public void testApiNamingConventions() throws Exception {
methods.containsKey(apiName.substring(0, apiName.length() - 6)));
assertThat(method.getReturnType(), equalTo(Void.TYPE));
assertEquals(0, method.getExceptionTypes().length);
- assertEquals(3, method.getParameterTypes().length);
- assertThat(method.getParameterTypes()[0].getSimpleName(), endsWith("Request"));
- assertThat(method.getParameterTypes()[1], equalTo(RequestOptions.class));
- assertThat(method.getParameterTypes()[2], equalTo(ActionListener.class));
+ if (apiName.equals("security.get_ssl_certificates_async")) {
+ assertEquals(2, method.getParameterTypes().length);
+ assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class));
+ assertThat(method.getParameterTypes()[1], equalTo(ActionListener.class));
+ } else {
+ assertEquals(3, method.getParameterTypes().length);
+ assertThat(method.getParameterTypes()[0].getSimpleName(), endsWith("Request"));
+ assertThat(method.getParameterTypes()[1], equalTo(RequestOptions.class));
+ assertThat(method.getParameterTypes()[2], equalTo(ActionListener.class));
+ }
} else {
//A few methods return a boolean rather than a response object
if (apiName.equals("ping") || apiName.contains("exist")) {
@@ -735,7 +741,7 @@ public void testApiNamingConventions() throws Exception {
assertEquals(1, method.getExceptionTypes().length);
//a few methods don't accept a request object as argument
- if (apiName.equals("ping") || apiName.equals("info")) {
+ if (apiName.equals("ping") || apiName.equals("info") || apiName.equals("security.get_ssl_certificates")) {
assertEquals(1, method.getParameterTypes().length);
assertThat(method.getParameterTypes()[0], equalTo(RequestOptions.class));
} else {
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
index 778ec7b5707cd..f80cbb0e67fa3 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
@@ -27,12 +27,17 @@
import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
+import org.elasticsearch.client.security.GetSslCertificatesResponse;
import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.client.security.EmptyResponse;
+import org.elasticsearch.client.security.support.CertificateInfo;
+import org.hamcrest.Matchers;
import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -175,6 +180,87 @@ public void onFailure(Exception e) {
}
}
+ public void testGetSslCertificates() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+ {
+ //tag::get-certificates-execute
+ GetSslCertificatesResponse response = client.security().getSslCertificates(RequestOptions.DEFAULT);
+ //end::get-certificates-execute
+
+ assertNotNull(response);
+
+ //tag::get-certificates-response
+ List certificates = response.getCertificates(); // <1>
+ //end::get-certificates-response
+
+ assertThat(certificates.size(), Matchers.equalTo(9));
+ final Iterator it = certificates.iterator();
+ CertificateInfo c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=testnode-client-profile"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.crt"));
+ assertThat(c.getFormat(), Matchers.equalTo("PEM"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=OpenLDAP, OU=Elasticsearch, O=Elastic, L=Mountain View, ST=CA, C=US"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=Elasticsearch Test Client, OU=elasticsearch, O=org"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=ad-ELASTICSEARCHAD-CA, DC=ad, DC=test, DC=elasticsearch, DC=com"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=Elasticsearch Test Node"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=samba4"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ c = it.next();
+ assertThat(c.getSubjectDn(), Matchers.equalTo("CN=Elasticsearch Test Node"));
+ assertThat(c.getPath(), Matchers.equalTo("testnode.jks"));
+ assertThat(c.getFormat(), Matchers.equalTo("jks"));
+ }
+
+ {
+ // tag::get-certificates-execute-listener
+ ActionListener listener = new ActionListener() {
+ @Override
+ public void onResponse(GetSslCertificatesResponse getSslCertificatesResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+
+ // end::get-certificates-execute-listener
+
+ // Replace the empty listener by a blocking listener in test
+ final CountDownLatch latch = new CountDownLatch(1);
+ listener = new LatchedActionListener<>(listener, latch);
+
+ // tag::get-certificates-execute-async
+ client.security().getSslCertificatesAsync(RequestOptions.DEFAULT, listener); // <1>
+ // end::end-certificates-execute-async
+
+ assertTrue(latch.await(30L, TimeUnit.SECONDS));
+ }
+ }
+
public void testChangePassword() throws Exception {
RestHighLevelClient client = highLevelClient();
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
diff --git a/client/rest-high-level/testnode.crt b/client/rest-high-level/testnode.crt
new file mode 100644
index 0000000000000..08c160bcea5ff
--- /dev/null
+++ b/client/rest-high-level/testnode.crt
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID0zCCArugAwIBAgIJALi5bDfjMszLMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV
+BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp
+Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTUwOTIzMTg1MjU3WhcNMTkwOTIyMTg1MjU3
+WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV
+BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEA3rGZ1QbsW0+MuyrSLmMfDFKtLBkIFW8V0gRuurFg1PUKKNR1
+Mq2tMVwjjYETAU/UY0iKZOzjgvYPKhDTYBTte/WHR1ZK4CYVv7TQX/gtFQG/ge/c
+7u0sLch9p7fbd+/HZiLS/rBEZDIohvgUvzvnA8+OIYnw4kuxKo/5iboAIS41klMg
+/lATm8V71LMY68inht71/ZkQoAHKgcR9z4yNYvQ1WqKG8DG8KROXltll3sTrKbl5
+zJhn660es/1ZnR6nvwt6xnSTl/mNHMjkfv1bs4rJ/py3qPxicdoSIn/KyojUcgHV
+F38fuAy2CQTdjVG5fWj9iz+mQvLm3+qsIYQdFwIDAQABo4G/MIG8MAkGA1UdEwQC
+MAAwHQYDVR0OBBYEFEMMWLWQi/g83PzlHYqAVnty5L7HMIGPBgNVHREEgYcwgYSC
+CWxvY2FsaG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghds
+b2NhbGhvc3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5s
+b2NhbGRvbWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQEL
+BQADggEBAMjGGXT8Nt1tbl2GkiKtmiuGE2Ej66YuZ37WSJViaRNDVHLlg87TCcHe
+k2rdO+6sFqQbbzEfwQ05T7xGmVu7tm54HwKMRugoQ3wct0bQC5wEWYN+oMDvSyO6
+M28mZwWb4VtR2IRyWP+ve5DHwTM9mxWa6rBlGzsQqH6YkJpZojzqk/mQTug+Y8aE
+mVoqRIPMHq9ob+S9qd5lp09+MtYpwPfTPx/NN+xMEooXWW/ARfpGhWPkg/FuCu4z
+1tFmCqHgNcWirzMm3dQpF78muE9ng6OB2MXQwL4VgnVkxmlZNHbkR2v/t8MyZJxC
+y4g6cTMM3S/UMt5/+aIB2JAuMKyuD+A=
+-----END CERTIFICATE-----
diff --git a/client/rest-high-level/testnode.jks b/client/rest-high-level/testnode.jks
new file mode 100644
index 0000000000000..ebe6146124e8f
Binary files /dev/null and b/client/rest-high-level/testnode.jks differ
diff --git a/docs/java-rest/high-level/security/get-certificates.asciidoc b/docs/java-rest/high-level/security/get-certificates.asciidoc
new file mode 100644
index 0000000000000..6820b1564fb8e
--- /dev/null
+++ b/docs/java-rest/high-level/security/get-certificates.asciidoc
@@ -0,0 +1,53 @@
+[[java-rest-high-security-get-certificates]]
+=== SSL Certificate API
+
+[[java-rest-high-security-get-certificates-execution]]
+==== Execution
+
+The X.509 Certificates that are used to encrypt communications in an
+Elasticsearch cluster using the `security().getSslCertificates()` method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SecurityDocumentationIT.java[get-certificates-execute]
+--------------------------------------------------
+
+[[java-rest-high-security-get-certificates-response]]
+==== Response
+
+The returned `GetSslCertificatesResponse` contains a single field, `certificates`.
+This field, accessed with `getCertificates` returns a List of `CertificateInfo`
+objects containing the information for all the certificates used.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SecurityDocumentationIT.java[get-certificates-response]
+--------------------------------------------------
+<1> `certificates` is a List of `CertificateInfo`
+
+[[java-rest-high-security-get-certificates-execute-async]]
+==== Asynchronous Execution
+
+This request can be executed asynchronously using the `security().getSslCertificatesAsync()`
+method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SecurityDocumentationIT.java[get-certificates-execute-async]
+--------------------------------------------------
+<1> The `ActionListener` to use when the execution completes.
+
+The asynchronous method does not block and returns immediately. Once the request
+has completed the `ActionListener` is called back using the `onResponse` method
+if the execution successfully completed or using the `onFailure` method if
+it failed.
+
+A typical listener for a `GetSslCertificatesResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/SecurityDocumentationIT.java[get-certificates-execute-listener]
+--------------------------------------------------
+<1> Called when the execution is successfully completed. The response is
+provided as an argument.
+<2> Called in case of failure. The raised exception is provided as an argument.
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 3bc5905a46fe5..2cec49f8bcfd7 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -298,11 +298,13 @@ The Java High Level REST Client supports the following Security APIs:
* <>
* <>
* <>
+* <>
include::security/put-user.asciidoc[]
include::security/enable-user.asciidoc[]
include::security/disable-user.asciidoc[]
include::security/change-password.asciidoc[]
+include::security/get-certificates.asciidoc[]
== Watcher APIs