Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enroll Kibana API uses Service Accounts #76370

Merged
merged 16 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions client/rest-high-level/qa/ssl-enabled/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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-"));
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 token, String httpCa) {
this.tokenName = tokenName;
this.tokenValue = token;
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 {
Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -101,7 +100,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;
Expand Down Expand Up @@ -2875,90 +2873,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)) {
Expand Down