diff --git a/client/rest-high-level/qa/ssl-enabled/build.gradle b/client/rest-high-level/qa/ssl-enabled/build.gradle index 164c74e6b292d..8be09ae3b5e80 100644 --- a/client/rest-high-level/qa/ssl-enabled/build.gradle +++ b/client/rest-high-level/qa/ssl-enabled/build.gradle @@ -6,6 +6,14 @@ * Side Public License, v 1. */ +/* + * We need this separate project as tests related to the enrollment process require + * test clusters with a specific TLS setup which is also not FIPS 140-2 compliant + * (as it uses PKCS#12 keystores). In order to not disable the entire rest-high-level + * project when running in fips mode, we moved enrollment tests in this subproject. + * + */ + import org.elasticsearch.gradle.internal.test.RestIntegTestTask import org.elasticsearch.gradle.internal.info.BuildParams diff --git a/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java b/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java index 5266db022bd4a..6940035fc72a5 100644 --- a/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java +++ b/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java @@ -25,6 +25,7 @@ import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; public class EnrollmentIT extends ESRestHighLevelClientTestCase { private static Path httpTrustStore; @@ -76,7 +77,7 @@ public void testEnrollKibana() throws Exception { assertThat(kibanaResponse, notNullValue()); assertThat(kibanaResponse.getHttpCa() , endsWith("brcNC5xq6YE7C4/06nH7F6le4kE4Uo6c9fpkl4ehOxQxndNLn462tFF+8VBA8IftJ1PPWzqGxLsCTzM6p6w8sa+XhgNYglLfkRjirc=")); - assertNotNull(kibanaResponse.getPassword()); - assertThat(kibanaResponse.getPassword().toString().length(), equalTo(14)); + assertNotNull(kibanaResponse.getTokenValue()); + assertNotNull(kibanaResponse.getTokenName(), startsWith("enroll-process-token-")); } } diff --git a/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java b/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java new file mode 100644 index 0000000000000..9511df7a4fd03 --- /dev/null +++ b/client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.client.documentation; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.client.ESRestHighLevelClientTestCase; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.security.KibanaEnrollmentResponse; +import org.elasticsearch.client.security.NodeEnrollmentResponse; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.core.PathUtils; +import org.junit.BeforeClass; + +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.Matchers.startsWith; + +public class EnrollmentDocumentationIT extends ESRestHighLevelClientTestCase { + static Path HTTP_TRUSTSTORE; + + @BeforeClass + public static void getResources() throws Exception { + HTTP_TRUSTSTORE = PathUtils.get(EnrollmentDocumentationIT.class.getResource("/httpCa.p12").toURI()); + } + + @Override + protected String getProtocol() { + return "https"; + } + + @Override + protected Settings restClientSettings() { + String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray())); + + return Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", token) + .put(TRUSTSTORE_PATH, HTTP_TRUSTSTORE) + .put(TRUSTSTORE_PASSWORD, "password") + .build(); + } + + public void testNodeEnrollment() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + // tag::node-enrollment-execute + NodeEnrollmentResponse response = client.security().enrollNode(RequestOptions.DEFAULT); + // end::node-enrollment-execute + + // tag::node-enrollment-response + String httpCaKey = response.getHttpCaKey(); // <1> + String httpCaCert = response.getHttpCaCert(); // <2> + String transportKey = response.getTransportKey(); // <3> + String transportCert = response.getTransportCert(); // <4> + List<String> nodesAddresses = response.getNodesAddresses(); // <5> + // end::node-enrollment-response + } + + { + // tag::node-enrollment-execute-listener + ActionListener<NodeEnrollmentResponse> listener = + new ActionListener<NodeEnrollmentResponse>() { + @Override + public void onResponse(NodeEnrollmentResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + }}; + // end::node-enrollment-execute-listener + + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::node-enrollment-execute-async + client.security().enrollNodeAsync(RequestOptions.DEFAULT, listener); + // end::node-enrollment-execute-async + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } + + public void testKibanaEnrollment() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + // tag::kibana-enrollment-execute + KibanaEnrollmentResponse response = client.security().enrollKibana(RequestOptions.DEFAULT); + // end::kibana-enrollment-execute + + // tag::kibana-enrollment-response + String tokenName = response.getTokenName(); // <1> + SecureString tokenValue = response.getTokenValue(); // <2> + String httoCa = response.getHttpCa(); // <3> + // end::kibana-enrollment-response + assertNotNull(tokenValue); + assertThat(tokenName, startsWith("enroll-process-token-")); + } + + { + // tag::kibana-enrollment-execute-listener + ActionListener<KibanaEnrollmentResponse> listener = + new ActionListener<KibanaEnrollmentResponse>() { + @Override + public void onResponse(KibanaEnrollmentResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + }}; + // end::kibana-enrollment-execute-listener + + final CountDownLatch latch = new CountDownLatch(1); + listener = new LatchedActionListener<>(listener, latch); + + // tag::kibana-enrollment-execute-async + client.security().enrollKibanaAsync(RequestOptions.DEFAULT, listener); + // end::kibana-enrollment-execute-async + assertTrue(latch.await(30L, TimeUnit.SECONDS)); + } + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/KibanaEnrollmentResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/KibanaEnrollmentResponse.java index b9e71f2a08ed6..56604d849ed1d 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/KibanaEnrollmentResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/KibanaEnrollmentResponse.java @@ -16,34 +16,51 @@ import java.io.IOException; import java.util.Objects; +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + public final class KibanaEnrollmentResponse { - private SecureString password; + private String tokenName; + private SecureString tokenValue; private String httpCa; - public KibanaEnrollmentResponse(SecureString password, String httpCa) { - this.password = password; + public KibanaEnrollmentResponse(String tokenName, SecureString tokenValue, String httpCa) { + this.tokenName = tokenName; + this.tokenValue = tokenValue; this.httpCa = httpCa; } - public SecureString getPassword() { return password; } + public String getTokenName() { return tokenName; } + + public SecureString getTokenValue() { return tokenValue; } public String getHttpCa() { return httpCa; } - private static final ParseField PASSWORD = new ParseField("password"); + private static final ParseField TOKEN = new ParseField("token"); + private static final ParseField TOKEN_NAME = new ParseField("name"); + private static final ParseField TOKEN_VALUE = new ParseField("value"); private static final ParseField HTTP_CA = new ParseField("http_ca"); - @SuppressWarnings("unchecked") + static final ConstructingObjectParser<Token, Void> TOKEN_PARSER = new ConstructingObjectParser<>( + KibanaEnrollmentResponse.class.getName(), true, + a -> new Token((String) a[0], (String) a[1]) + ); + private static final ConstructingObjectParser<KibanaEnrollmentResponse, Void> PARSER = new ConstructingObjectParser<>( KibanaEnrollmentResponse.class.getName(), true, - a -> new KibanaEnrollmentResponse(new SecureString(((String) a[0]).toCharArray()), (String) a[1])); + a -> { + final Token token = (Token) a[0]; + return new KibanaEnrollmentResponse(token.name, new SecureString(token.value.toCharArray()), (String) a[1]); + }); static { - PARSER.declareString(ConstructingObjectParser.constructorArg(), PASSWORD); - PARSER.declareString(ConstructingObjectParser.constructorArg(), HTTP_CA); + TOKEN_PARSER.declareString(constructorArg(), TOKEN_NAME); + TOKEN_PARSER.declareString(constructorArg(), TOKEN_VALUE); + PARSER.declareObject(constructorArg(), TOKEN_PARSER, TOKEN); + PARSER.declareString(constructorArg(), HTTP_CA); } public static KibanaEnrollmentResponse fromXContent(XContentParser parser) throws IOException { @@ -54,10 +71,20 @@ public static KibanaEnrollmentResponse fromXContent(XContentParser parser) throw if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; KibanaEnrollmentResponse that = (KibanaEnrollmentResponse) o; - return password.equals(that.password) && httpCa.equals(that.httpCa); + return tokenName.equals(that.tokenName) && tokenValue.equals(that.tokenValue) && httpCa.equals(that.httpCa); } @Override public int hashCode() { - return Objects.hash(password, httpCa); + return Objects.hash(tokenName, tokenValue, httpCa); + } + + private static class Token { + private final String name; + private final String value; + + Token(String name, String value) { + this.name = name; + this.value = value; + } } } 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 153e412296e5a..6a463926d4a43 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 @@ -75,7 +75,6 @@ import org.elasticsearch.client.security.InvalidateApiKeyResponse; import org.elasticsearch.client.security.InvalidateTokenRequest; import org.elasticsearch.client.security.InvalidateTokenResponse; -import org.elasticsearch.client.security.NodeEnrollmentResponse; import org.elasticsearch.client.security.PutPrivilegesRequest; import org.elasticsearch.client.security.PutPrivilegesResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; @@ -103,7 +102,6 @@ import org.elasticsearch.client.security.user.privileges.Role.ClusterPrivilegeName; import org.elasticsearch.client.security.user.privileges.Role.IndexPrivilegeName; import org.elasticsearch.client.security.user.privileges.UserIndicesPrivileges; -import org.elasticsearch.client.security.KibanaEnrollmentResponse; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.SecureString; @@ -3008,90 +3006,6 @@ public void onFailure(Exception e) { } } - @AwaitsFix(bugUrl = "Determine behavior for keystores with multiple keys") - public void testNodeEnrollment() throws Exception { - RestHighLevelClient client = highLevelClient(); - - { - // tag::node-enrollment-execute - NodeEnrollmentResponse response = client.security().enrollNode(RequestOptions.DEFAULT); - // end::node-enrollment-execute - - // tag::node-enrollment-response - String httpCaKey = response.getHttpCaKey(); // <1> - String httpCaCert = response.getHttpCaCert(); // <2> - String transportKey = response.getTransportKey(); // <3> - String transportCert = response.getTransportCert(); // <4> - List<String> nodesAddresses = response.getNodesAddresses(); // <5> - // end::node-enrollment-response - } - - { - // tag::node-enrollment-execute-listener - ActionListener<NodeEnrollmentResponse> listener = - new ActionListener<NodeEnrollmentResponse>() { - @Override - public void onResponse(NodeEnrollmentResponse response) { - // <1> - } - - @Override - public void onFailure(Exception e) { - // <2> - }}; - // end::node-enrollment-execute-listener - - final CountDownLatch latch = new CountDownLatch(1); - listener = new LatchedActionListener<>(listener, latch); - - // tag::node-enrollment-execute-async - client.security().enrollNodeAsync(RequestOptions.DEFAULT, listener); - // end::node-enrollment-execute-async - assertTrue(latch.await(30L, TimeUnit.SECONDS)); - } - } - - @AwaitsFix(bugUrl = "Determine behavior for keystores with multiple keys") - public void testKibanaEnrollment() throws Exception { - RestHighLevelClient client = highLevelClient(); - - { - // tag::kibana-enrollment-execute - KibanaEnrollmentResponse response = client.security().enrollKibana(RequestOptions.DEFAULT); - // end::kibana-enrollment-execute - - // tag::kibana-enrollment-response - SecureString password = response.getPassword(); // <1> - String httoCa = response.getHttpCa(); // <2> - // end::kibana-enrollment-response - assertThat(password.length(), equalTo(14)); - } - - { - // tag::kibana-enrollment-execute-listener - ActionListener<KibanaEnrollmentResponse> listener = - new ActionListener<KibanaEnrollmentResponse>() { - @Override - public void onResponse(KibanaEnrollmentResponse response) { - // <1> - } - - @Override - public void onFailure(Exception e) { - // <2> - }}; - // end::kibana-enrollment-execute-listener - - final CountDownLatch latch = new CountDownLatch(1); - listener = new LatchedActionListener<>(listener, latch); - - // tag::kibana-enrollment-execute-async - client.security().enrollKibanaAsync(RequestOptions.DEFAULT, listener); - // end::kibana-enrollment-execute-async - assertTrue(latch.await(30L, TimeUnit.SECONDS)); - } - } - private X509Certificate readCertForPkiDelegation(String certificateName) throws Exception { Path path = getDataPath("/org/elasticsearch/client/security/delegate_pki/" + certificateName); try (InputStream in = Files.newInputStream(path)) { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaEnrollmentResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaEnrollmentResponseTests.java new file mode 100644 index 0000000000000..35d5453e798da --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaEnrollmentResponseTests.java @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.client.security; + +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.EqualsHashCodeTestUtils; + +import java.io.IOException; + +import static org.hamcrest.Matchers.equalTo; + +public class KibanaEnrollmentResponseTests extends ESTestCase { + + public void testFromXContent() throws IOException { + final String tokenName = randomAlphaOfLengthBetween(8 ,14); + final String tokenValue = randomAlphaOfLengthBetween(58, 70); + final String httpCa = randomAlphaOfLength(50); + + final XContentType xContentType = randomFrom(XContentType.values()); + final XContentBuilder builder = XContentFactory.contentBuilder(xContentType); + builder.startObject() + .startObject("token") + .field("name", tokenName) + .field("value", tokenValue) + .endObject() + .field("http_ca", httpCa) + .endObject(); + BytesReference xContent = BytesReference.bytes(builder); + + final KibanaEnrollmentResponse response = KibanaEnrollmentResponse.fromXContent(createParser(xContentType.xContent(), xContent)); + assertThat(response.getTokenName(), equalTo(tokenName)); + assertThat(response.getTokenValue(), equalTo(tokenValue)); + assertThat(response.getHttpCa(), equalTo(httpCa)); + } + + public void testEqualsHashCode() { + final String tokenName = randomAlphaOfLengthBetween(8 ,14); + final SecureString tokenValue = new SecureString(randomAlphaOfLengthBetween(58, 70).toCharArray()); + final String httpCa = randomAlphaOfLength(50); + KibanaEnrollmentResponse kibanaEnrollmentResponse = new KibanaEnrollmentResponse(tokenName, tokenValue, httpCa); + + EqualsHashCodeTestUtils.checkEqualsAndHashCode(kibanaEnrollmentResponse, + (original) -> new KibanaEnrollmentResponse(original.getTokenName(), original.getTokenValue(), original.getHttpCa())); + + EqualsHashCodeTestUtils.checkEqualsAndHashCode(kibanaEnrollmentResponse, + (original) -> new KibanaEnrollmentResponse(original.getTokenName(), original.getTokenValue(), original.getHttpCa()), + KibanaEnrollmentResponseTests::mutateTestItem); + } + + private static KibanaEnrollmentResponse mutateTestItem(KibanaEnrollmentResponse original) { + switch (randomIntBetween(0, 3)) { + case 0: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + randomAlphaOfLength(52) + ); + case 1: + return new KibanaEnrollmentResponse( + original.getTokenName(), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + randomAlphaOfLength(52) + ); + case 2: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + original.getTokenValue(), + randomAlphaOfLength(52) + ); + case 3: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + original.getHttpCa() + ); + } + // we never reach here + return null; + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaErnollmentResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaErnollmentResponseTests.java deleted file mode 100644 index 937e750ccb72c..0000000000000 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/KibanaErnollmentResponseTests.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.client.security; - -import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.EqualsHashCodeTestUtils; - -import java.io.IOException; -import java.util.List; - -import static org.hamcrest.Matchers.equalTo; - -public class KibanaErnollmentResponseTests extends ESTestCase { - - public void testFromXContent() throws IOException { - final String password = randomAlphaOfLength(14); - final String httpCa = randomAlphaOfLength(50); - final List<String> nodesAddresses = randomList(2, 10, () -> buildNewFakeTransportAddress().toString()); - - final XContentType xContentType = randomFrom(XContentType.values()); - final XContentBuilder builder = XContentFactory.contentBuilder(xContentType); - builder.startObject().field("password", password).field("http_ca", httpCa).field("nodes_addresses", nodesAddresses).endObject(); - BytesReference xContent = BytesReference.bytes(builder); - - final KibanaEnrollmentResponse response = KibanaEnrollmentResponse.fromXContent(createParser(xContentType.xContent(), xContent)); - assertThat(response.getPassword(), equalTo(password)); - assertThat(response.getHttpCa(), equalTo(httpCa)); - } - - public void testEqualsHashCode() { - final SecureString password = new SecureString(randomAlphaOfLength(14).toCharArray()); - final String httpCa = randomAlphaOfLength(50); - KibanaEnrollmentResponse kibanaEnrollmentResponse = new KibanaEnrollmentResponse(password, httpCa); - - EqualsHashCodeTestUtils.checkEqualsAndHashCode(kibanaEnrollmentResponse, - (original) -> new KibanaEnrollmentResponse(original.getPassword(), original.getHttpCa())); - - EqualsHashCodeTestUtils.checkEqualsAndHashCode(kibanaEnrollmentResponse, - (original) -> new KibanaEnrollmentResponse(original.getPassword(), original.getHttpCa()), - KibanaErnollmentResponseTests::mutateTestItem); - } - - private static KibanaEnrollmentResponse mutateTestItem(KibanaEnrollmentResponse original) { - switch (randomIntBetween(0, 1)) { - case 0: - return new KibanaEnrollmentResponse(new SecureString(randomAlphaOfLength(14).toCharArray()), - original.getHttpCa()); - case 1: - return new KibanaEnrollmentResponse(original.getPassword(), randomAlphaOfLength(51)); - default: - return new KibanaEnrollmentResponse(original.getPassword(), - original.getHttpCa()); - } - } -} diff --git a/docs/java-rest/high-level/security/enroll_kibana.asciidoc b/docs/java-rest/high-level/security/enroll_kibana.asciidoc index 6e9111375b9bf..c6a7da354d066 100644 --- a/docs/java-rest/high-level/security/enroll_kibana.asciidoc +++ b/docs/java-rest/high-level/security/enroll_kibana.asciidoc @@ -19,11 +19,15 @@ executed operation as follows: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api-kibana-response] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-response] -------------------------------------------------- -<1> The password for the `kibana_system` user -<2> The CA certificate that has signed the certificate that the cluster uses for TLS on the HTTP layer, -as a Base64 encoded string of the ASN.1 DER encoding of the certificate. +<1> The bearer token for the `elastic/kibana` service account. +Use this token to {ref}/service-accounts.html#authenticate-with-service-account-token[authenticate the service account] with {es}. +<2> The value of the bearer token for the `elastic/kibana` service account. +<3> The CA certificate used to sign the node certificates that {es} uses for TLS +on the HTTP layer. The certificate is returned as a Base64 encoded string of the +ASN.1 DER encoding of the certificate. + [id="{upid}-{api}-execute-async"] ==== Asynchronous Execution @@ -33,14 +37,14 @@ method: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api}-execute-async] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-execute-async] -------------------------------------------------- A typical listener for a `KibanaEnrollmentResponse` looks like: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api}-execute-listener] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-execute-listener] -------------------------------------------------- <1> Called when the execution is successfully completed. The response is provided as an argument diff --git a/docs/java-rest/high-level/cluster/enroll_node.asciidoc b/docs/java-rest/high-level/security/enroll_node.asciidoc similarity index 91% rename from docs/java-rest/high-level/cluster/enroll_node.asciidoc rename to docs/java-rest/high-level/security/enroll_node.asciidoc index e8bdaef30850f..374e2d46a8b41 100644 --- a/docs/java-rest/high-level/cluster/enroll_node.asciidoc +++ b/docs/java-rest/high-level/security/enroll_node.asciidoc @@ -27,7 +27,7 @@ executed operation as follows: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api}-response] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-response] -------------------------------------------------- <1> The CA private key that can be used by the new node in order to sign its certificate for the HTTP layer, as a Base64 encoded string of the ASN.1 DER encoding of the key. @@ -49,14 +49,14 @@ method: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api}-execute-async] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-execute-async] -------------------------------------------------- A typical listener for a `NodeEnrollmentResponse` looks like: ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- -include-tagged::{doc-tests-file}[{api}-execute-listener] +include-tagged::{doc-tests}/EnrollmentDocumentationIT.java[{api}-execute-listener] -------------------------------------------------- <1> Called when the execution is successfully completed. The response is provided as an argument diff --git a/x-pack/docs/en/rest-api/security.asciidoc b/x-pack/docs/en/rest-api/security.asciidoc index 09b244fe32d00..e023094ef8d2a 100644 --- a/x-pack/docs/en/rest-api/security.asciidoc +++ b/x-pack/docs/en/rest-api/security.asciidoc @@ -140,7 +140,6 @@ include::security/clear-privileges-cache.asciidoc[] include::security/clear-api-key-cache.asciidoc[] include::security/clear-service-token-caches.asciidoc[] include::security/create-api-keys.asciidoc[] -include::security/enroll-kibana.asciidoc[] include::security/put-app-privileges.asciidoc[] include::security/create-role-mappings.asciidoc[] include::security/create-roles.asciidoc[] @@ -154,6 +153,7 @@ include::security/delete-service-token.asciidoc[] include::security/delete-users.asciidoc[] include::security/disable-users.asciidoc[] include::security/enable-users.asciidoc[] +include::security/enroll-kibana.asciidoc[] include::security/enroll-node.asciidoc[] include::security/get-api-keys.asciidoc[] include::security/get-app-privileges.asciidoc[] diff --git a/x-pack/docs/en/rest-api/security/enroll-kibana.asciidoc b/x-pack/docs/en/rest-api/security/enroll-kibana.asciidoc index 0736900ce2104..55de31b5407d1 100644 --- a/x-pack/docs/en/rest-api/security/enroll-kibana.asciidoc +++ b/x-pack/docs/en/rest-api/security/enroll-kibana.asciidoc @@ -35,10 +35,16 @@ The API returns the following response: [source,console_result] ---- { - "password" : "longsecurepassword", <1> - "http_ca" : "MIIJlAIBAzCCCVoGCSqGSIb3....vsDfsA3UZBAjEPfhubpQysAICCAA=", <2> + "token" : { + "name" : "enroll-process-token-1629123923000", <1> + "value": "AAEAAWVsYXN0aWM...vZmxlZXQtc2VydmVyL3Rva2VuMTo3TFdaSDZ" <2> + }, + "http_ca" : "MIIJlAIBAzVoGCSqGSIb3...vsDfsA3UZBAjEPfhubpQysAICAA=", <3> } ---- -<1> The password for the `kibana_system` user. -<2> The CA certificate used to sign the node certificates that {es} uses for TLS on the HTTP layer. -The certificate is returned as a Base64 encoded string of the ASN.1 DER encoding of the certificate. +<1> The name of the bearer token for the `elastic/kibana` service account. +<2> The value of the bearer token for the `elastic/kibana` service account. +Use this value to {ref}/service-accounts.html#authenticate-with-service-account-token[authenticate the service account] with {es}. +<3> The CA certificate used to sign the node certificates that {es} uses for TLS +on the HTTP layer. The certificate is returned as a Base64 encoded string of the +ASN.1 DER encoding of the certificate. diff --git a/x-pack/docs/en/rest-api/security/enroll-node.asciidoc b/x-pack/docs/en/rest-api/security/enroll-node.asciidoc index d7d41d71f2367..a6a876b9d98c0 100644 --- a/x-pack/docs/en/rest-api/security/enroll-node.asciidoc +++ b/x-pack/docs/en/rest-api/security/enroll-node.asciidoc @@ -1,7 +1,7 @@ [[security-api-node-enrollment]] === Enroll Node API ++++ -<titleabbrev>Enroll Node</titleabbrev> +<titleabbrev>Enroll node</titleabbrev> ++++ Allows a new node to join an existing cluster with security features enabled. diff --git a/x-pack/docs/en/security/authentication/service-accounts.asciidoc b/x-pack/docs/en/security/authentication/service-accounts.asciidoc index 6964d2e0785d6..67cd98e1a1df2 100644 --- a/x-pack/docs/en/security/authentication/service-accounts.asciidoc +++ b/x-pack/docs/en/security/authentication/service-accounts.asciidoc @@ -29,7 +29,9 @@ traditional roles Service accounts are not included in the response of the <<security-api-get-user,get users API>>. To retrieve a service account, use the -<<security-api-get-service-accounts,get service accounts API>>. +<<security-api-get-service-accounts,get service accounts API>>. Use the +<<security-api-get-service-credentials,get service account credentials API>> +to retrieve all service credentials for a service account. [discrete] [[service-accounts-explanation]] diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponse.java index bad9b0fdb678d..5e35355a70869 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponse.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponse.java @@ -11,75 +11,61 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.xcontent.ConstructingObjectParser; -import org.elasticsearch.common.xcontent.ParseField; import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; -import org.elasticsearch.common.xcontent.XContentParser; - import java.io.IOException; import java.util.Objects; public final class KibanaEnrollmentResponse extends ActionResponse implements ToXContentObject { - private static final ParseField PASSWORD = new ParseField("password"); - private static final ParseField HTTP_CA = new ParseField("http_ca"); - - @SuppressWarnings("unchecked") - private static final ConstructingObjectParser<KibanaEnrollmentResponse, Void> PARSER = - new ConstructingObjectParser<>( - KibanaEnrollmentResponse.class.getName(), true, - a -> new KibanaEnrollmentResponse(new SecureString(((String) a[0]).toCharArray()), (String) a[1])); - - static { - PARSER.declareString(ConstructingObjectParser.constructorArg(), PASSWORD); - PARSER.declareString(ConstructingObjectParser.constructorArg(), HTTP_CA); - } - - private final SecureString password; + private final String tokenName; + private final SecureString tokenValue; private final String httpCa; public KibanaEnrollmentResponse(StreamInput in) throws IOException { super(in); - password = in.readSecureString(); + tokenName = in.readString(); + tokenValue = in.readSecureString(); httpCa = in.readString(); } - public KibanaEnrollmentResponse(SecureString password, String httpCa) { - this.password = password; + public KibanaEnrollmentResponse(String tokenName, SecureString tokenValue, String httpCa) { + this.tokenName = tokenName; + this.tokenValue = tokenValue; this.httpCa = httpCa; } - public SecureString getPassword() { return password; } + public String getTokenName() { return tokenName; } + public SecureString getTokenValue() { return tokenValue; } public String getHttpCa() { return httpCa; } - @Override public XContentBuilder toXContent( - XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - builder.field(PASSWORD.getPreferredName(), password.toString()); - builder.field(HTTP_CA.getPreferredName(), httpCa); - return builder.endObject(); - } - @Override public void writeTo(StreamOutput out) throws IOException { - out.writeSecureString(password); + out.writeString(tokenName); + out.writeSecureString(tokenValue); out.writeString(httpCa); } - public static KibanaEnrollmentResponse fromXContent(XContentParser parser) { - return PARSER.apply(parser, null); - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - KibanaEnrollmentResponse response = (KibanaEnrollmentResponse) o; - return password.equals(response.password) && httpCa.equals(response.httpCa); + KibanaEnrollmentResponse that = (KibanaEnrollmentResponse) o; + return tokenName.equals(that.tokenName) && tokenValue.equals(that.tokenValue) && httpCa.equals(that.httpCa); } @Override public int hashCode() { - return Objects.hash(password, httpCa); + return Objects.hash(tokenName, tokenValue, httpCa); + } + + @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject() + .startObject("token") + .field("name", tokenName) + .field("value", tokenValue.toString()) + .endObject() + .field("http_ca", httpCa) + .endObject(); + return builder; } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponseTests.java index 1868a2365a121..ac56dd0c87009 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/enrollment/KibanaEnrollmentResponseTests.java @@ -7,41 +7,81 @@ package org.elasticsearch.xpack.core.security.action.enrollment; -import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.xcontent.XContentParser; -import org.elasticsearch.test.AbstractXContentTestCase; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.test.AbstractWireSerializingTestCase; import java.io.IOException; +import java.util.Map; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.equalTo; -public class KibanaEnrollmentResponseTests extends AbstractXContentTestCase<KibanaEnrollmentResponse> { +public class KibanaEnrollmentResponseTests extends AbstractWireSerializingTestCase<KibanaEnrollmentResponse> { - @Override protected KibanaEnrollmentResponse createTestInstance() { - return new KibanaEnrollmentResponse( - new SecureString(randomAlphaOfLength(14).toCharArray()), - randomAlphaOfLength(50)); - } - @Override protected KibanaEnrollmentResponse doParseInstance(XContentParser parser) throws IOException { - return KibanaEnrollmentResponse.fromXContent(parser); + @Override + protected Writeable.Reader<KibanaEnrollmentResponse> instanceReader() { + return KibanaEnrollmentResponse::new; } - @Override protected boolean supportsUnknownFields() { - return false; + @Override + protected KibanaEnrollmentResponse createTestInstance() { + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(8, 12), + new SecureString(randomAlphaOfLengthBetween(58, 70).toCharArray()), + randomAlphaOfLength(50) + ); } - public void testSerialization() throws IOException{ - KibanaEnrollmentResponse response = createTestInstance(); - try (BytesStreamOutput out = new BytesStreamOutput()) { - response.writeTo(out); - try (StreamInput in = out.bytes().streamInput()) { - KibanaEnrollmentResponse serialized = new KibanaEnrollmentResponse(in); - assertThat(response.getHttpCa(), is(serialized.getHttpCa())); - assertThat(response.getPassword(), is(serialized.getPassword())); - } + @Override + protected KibanaEnrollmentResponse mutateInstance(KibanaEnrollmentResponse instance) throws IOException { + switch (randomIntBetween(0, 3)) { + case 0: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + randomAlphaOfLength(52) + ); + case 1: + return new KibanaEnrollmentResponse( + instance.getTokenName(), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + randomAlphaOfLength(52) + ); + case 2: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + instance.getTokenValue(), + randomAlphaOfLength(52) + ); + case 3: + return new KibanaEnrollmentResponse( + randomAlphaOfLengthBetween(14, 20), + new SecureString(randomAlphaOfLengthBetween(71, 90).toCharArray()), + instance.getHttpCa() + ); } + // we never reach here + return null; } + + public void testToXContent() throws IOException { + final KibanaEnrollmentResponse response = createTestInstance(); + XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); + response.toXContent(jsonBuilder, ToXContent.EMPTY_PARAMS); + final Map<String, Object> responseMap = XContentHelper.convertToMap( + BytesReference.bytes(jsonBuilder), + false, jsonBuilder.contentType()).v2(); + + assertThat(responseMap, equalTo(Map.of( + "token", Map.of("name", response.getTokenName(), "value", response.getTokenValue().toString()), + "http_ca", response.getHttpCa() + ))); + } + } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentAction.java index 1ca47ab7a8911..ce4c77b2b6528 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentAction.java @@ -16,28 +16,25 @@ import org.elasticsearch.client.Client; import org.elasticsearch.client.OriginSettingClient; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.core.Tuple; import org.elasticsearch.env.Environment; import org.elasticsearch.tasks.Task; import org.elasticsearch.transport.TransportService; -import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentAction; import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentRequest; import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentResponse; -import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction; -import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest; -import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequestBuilder; -import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenAction; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest; import org.elasticsearch.xpack.core.ssl.KeyConfig; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.core.ssl.StoreKeyConfig; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; -import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Base64; import java.util.List; import java.util.stream.Collectors; @@ -58,7 +55,6 @@ public class TransportKibanaEnrollmentAction extends HandledTransportAction<Kiba ActionFilters actionFilters) { super(KibanaEnrollmentAction.NAME, transportService, actionFilters, KibanaEnrollmentRequest::new); this.environment = environment; - // Should we use a specific origin for this ? Are we satisfied with the auditability of the change password request as-is ? this.client = new OriginSettingClient(client, SECURITY_ORIGIN); this.sslService = sslService; } @@ -98,28 +94,20 @@ public class TransportKibanaEnrollmentAction extends HandledTransportAction<Kiba cee)); return; } - final char[] password = generateKibanaSystemPassword(); - final ChangePasswordRequest changePasswordRequest = - new ChangePasswordRequestBuilder(client).username("kibana_system") - .password(password.clone(), Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(environment.settings()))) - .request(); - client.execute(ChangePasswordAction.INSTANCE, changePasswordRequest, ActionListener.wrap(response -> { - logger.debug("Successfully set the password for user [kibana_system] during kibana enrollment"); - listener.onResponse(new KibanaEnrollmentResponse(new SecureString(password), httpCa)); - }, e -> listener.onFailure(new ElasticsearchException("Failed to set the password for user [kibana_system]", e)))); + final CreateServiceAccountTokenRequest createServiceAccountTokenRequest = + new CreateServiceAccountTokenRequest("elastic", "kibana", getTokenName()); + client.execute(CreateServiceAccountTokenAction.INSTANCE, createServiceAccountTokenRequest, ActionListener.wrap(response -> { + logger.debug("Successfully created token [{}] for the [elastic/kibana] service account during kibana enrollment", + response.getName()); + listener.onResponse(new KibanaEnrollmentResponse(response.getName(), response.getValue(), httpCa)); + }, e -> listener.onFailure( + new ElasticsearchException("Failed to create token for the [elastic/kibana] service account", e)))); } - } - private char[] generateKibanaSystemPassword() { - final char[] passwordChars = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*-_=+?").toCharArray(); - final SecureRandom secureRandom = new SecureRandom(); - int passwordLength = 14; - char[] characters = new char[passwordLength]; - for (int i = 0; i < passwordLength; ++i) { - characters[i] = passwordChars[secureRandom.nextInt(passwordChars.length)]; - } - return characters; + protected static String getTokenName(){ + final ZonedDateTime enrollTime = ZonedDateTime.now(ZoneOffset.UTC); + final String prefix = "enroll-process-token-"; + return prefix + enrollTime.toInstant().toEpochMilli(); } - } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentActionTests.java index fdfedc7acfabc..106f16687d753 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/enrollment/TransportKibanaEnrollmentActionTests.java @@ -9,12 +9,11 @@ import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.Client; -import org.elasticsearch.client.ValidationException; import org.elasticsearch.common.settings.MockSecureSettings; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; @@ -25,8 +24,9 @@ import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentRequest; import org.elasticsearch.xpack.core.security.action.enrollment.KibanaEnrollmentResponse; -import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction; -import org.elasticsearch.xpack.core.security.action.user.ChangePasswordRequest; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenAction; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest; +import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse; import org.elasticsearch.xpack.core.ssl.SSLConfiguration; import org.elasticsearch.xpack.core.ssl.SSLService; import org.junit.Before; @@ -48,9 +48,11 @@ import static org.mockito.Mockito.when; public class TransportKibanaEnrollmentActionTests extends ESTestCase { - private List<ChangePasswordRequest> changePasswordRequests; + private List<CreateServiceAccountTokenRequest> createServiceAccountTokenRequests; private TransportKibanaEnrollmentAction action; private Client client; + private static final String TOKEN_NAME = TransportKibanaEnrollmentAction.getTokenName(); + private static final SecureString TOKEN_VALUE = new SecureString("token-value".toCharArray()); @BeforeClass public static void muteInFips(){ @@ -58,7 +60,7 @@ public static void muteInFips(){ } @Before @SuppressWarnings("unchecked") public void setup() throws Exception { - changePasswordRequests = new ArrayList<>(); + createServiceAccountTokenRequests = new ArrayList<>(); final Environment env = mock(Environment.class); final Path tempDir = createTempDir(); final Path httpCaPath = tempDir.resolve("httpCa.p12"); @@ -80,12 +82,13 @@ public static void muteInFips(){ client = mock(Client.class); when(client.threadPool()).thenReturn(threadPool); doAnswer(invocation -> { - ChangePasswordRequest changePasswordRequest = (ChangePasswordRequest) invocation.getArguments()[1]; - changePasswordRequests.add(changePasswordRequest); - ActionListener<ActionResponse.Empty> listener = (ActionListener) invocation.getArguments()[2]; - listener.onResponse(ActionResponse.Empty.INSTANCE); + CreateServiceAccountTokenRequest createServiceAccountTokenRequest = + (CreateServiceAccountTokenRequest) invocation.getArguments()[1]; + createServiceAccountTokenRequests.add(createServiceAccountTokenRequest); + ActionListener<CreateServiceAccountTokenResponse> listener = (ActionListener) invocation.getArguments()[2]; + listener.onResponse(CreateServiceAccountTokenResponse.created(TOKEN_NAME, TOKEN_VALUE)); return null; - }).when(client).execute(eq(ChangePasswordAction.INSTANCE), any(), any()); + }).when(client).execute(eq(CreateServiceAccountTokenAction.INSTANCE), any(), any()); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), @@ -98,6 +101,7 @@ public static void muteInFips(){ } public void testKibanaEnrollment() { + assertThat(TOKEN_NAME, startsWith("enroll-process-token-")); final KibanaEnrollmentRequest request = new KibanaEnrollmentRequest(); final PlainActionFuture<KibanaEnrollmentResponse> future = new PlainActionFuture<>(); action.doExecute(mock(Task.class), request, future); @@ -105,22 +109,22 @@ public void testKibanaEnrollment() { assertThat(response.getHttpCa(), startsWith("MIIDSjCCAjKgAwIBAgIVALCgZXvbceUrjJaQMheDCX0kXnRJMA0GCSqGSIb3DQEBCwUAMDQxMjAw" + "BgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2VuZXJhdGVkIENBMB4XDTIxMDQyODEyNTY0MVoXDTI0MDQyNzEyNTY0MVowNDEyMDAGA1UEA" + "xMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCCJbOU4JvxDD/F")); - assertNotNull(response.getPassword()); - assertThat(changePasswordRequests.size(), equalTo(1)); + assertThat(response.getTokenValue(), equalTo(TOKEN_VALUE)); + assertThat(createServiceAccountTokenRequests.size(), equalTo(1)); } - public void testKibanaEnrollmentFailedPasswordChange() { + public void testKibanaEnrollmentFailedTokenCreation() { // Override change password mock doAnswer(invocation -> { @SuppressWarnings("unchecked") - ActionListener<ActionResponse.Empty> listener = (ActionListener<ActionResponse.Empty>) invocation.getArguments()[2]; - listener.onFailure(new ValidationException()); + ActionListener<CreateServiceAccountTokenResponse> listener = (ActionListener) invocation.getArguments()[2]; + listener.onFailure(new IllegalStateException()); return null; - }).when(client).execute(eq(ChangePasswordAction.INSTANCE), any(), any()); + }).when(client).execute(eq(CreateServiceAccountTokenAction.INSTANCE), any(), any()); final KibanaEnrollmentRequest request = new KibanaEnrollmentRequest(); final PlainActionFuture<KibanaEnrollmentResponse> future = new PlainActionFuture<>(); action.doExecute(mock(Task.class), request, future); ElasticsearchException e = expectThrows(ElasticsearchException.class, future::actionGet); - assertThat(e.getDetailedMessage(), containsString("Failed to set the password for user [kibana_system]")); + assertThat(e.getDetailedMessage(), containsString("Failed to create token for the [elastic/kibana] service account")); } }