Browse Source

HLRC: Get SSL Certificates API (#34135)

This change adds support for the get SSL certificate API to 
the high level rest client.
Ioannis Kakavas 7 năm trước cách đây
mục cha
commit
55eaf7a3ff

+ 8 - 0
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",

+ 29 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

@@ -22,6 +22,8 @@ package org.elasticsearch.client;
 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 final class SecurityClient {
             EmptyResponse::fromXContent, listener, emptySet());
     }
 
+    /**
+     * Synchronously retrieve the X.509 certificates that are used to encrypt communications in an Elasticsearch cluster.
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html">
+     * the docs</a> 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 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-ssl.html">
+     * the docs</a> 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<GetSslCertificatesResponse> 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 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-change-password.html">

+ 49 - 0
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();
+    }
+}

+ 68 - 0
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<CertificateInfo> certificates;
+
+    public GetSslCertificatesResponse(List<CertificateInfo> 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<CertificateInfo> 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<CertificateInfo> getCertificates() {
+        return certificates;
+    }
+}

+ 133 - 0
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<CertificateInfo, Void> 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);
+    }
+}

+ 11 - 5
client/rest-high-level/src/test/java/org/elasticsearch/client/RestHighLevelClientTests.java

@@ -721,10 +721,16 @@ public class RestHighLevelClientTests extends ESTestCase {
                         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 class RestHighLevelClientTests extends ESTestCase {
 
                 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 {

+ 86 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java

@@ -27,12 +27,17 @@ import org.elasticsearch.client.RestHighLevelClient;
 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 class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    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<CertificateInfo> certificates = response.getCertificates(); // <1>
+            //end::get-certificates-response
+
+            assertThat(certificates.size(), Matchers.equalTo(9));
+            final Iterator<CertificateInfo> 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<GetSslCertificatesResponse> listener = new ActionListener<GetSslCertificatesResponse>() {
+                @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'};

+ 23 - 0
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-----

BIN
client/rest-high-level/testnode.jks


+ 53 - 0
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.

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -305,11 +305,13 @@ The Java High Level REST Client supports the following Security APIs:
 * <<java-rest-high-security-enable-user>>
 * <<java-rest-high-security-disable-user>>
 * <<java-rest-high-security-change-password>>
+* <<java-rest-high-security-get-certificates>>
 
 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