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

Filter out CA PrivateKeyEntry when creating a KeyManager #73807

Merged
merged 37 commits into from
Jul 8, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8f4d1c8
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 6, 2021
de1cf4d
Merge branch 'master' into Sobd_CA
elasticmachine Jun 6, 2021
dacef10
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
f38429e
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
52f8ae6
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
03db8e2
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
b345f2b
Merge branch 'master' into Sobd_CA
elasticmachine Jun 8, 2021
6a741f9
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
a8fbc74
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
3a0983b
Handle a case of multiple keys in the same keystore.
BigPandaToo Jun 8, 2021
62047cd
Merge branch 'master' into Sobd_CA
elasticmachine Jun 8, 2021
7a40b6a
Merge branch 'master' into Sobd_CA
elasticmachine Jun 16, 2021
cbd0623
Merge branch 'master' into Sobd_CA
elasticmachine Jun 18, 2021
d408fa9
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 21, 2021
6b4e3a6
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 21, 2021
2c4ff7d
Handle a case of multiple keys in the same keystore
BigPandaToo Jun 21, 2021
7467e99
Merge branch 'master' into Sobd_CA
elasticmachine Jun 23, 2021
b2fdcb6
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 23, 2021
2e24fd6
Merge branch 'master' into Sobd_CA
elasticmachine Jun 24, 2021
b6c5a91
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 24, 2021
d2bf651
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 24, 2021
03edb70
Handle the case of multiple keys in the same keystore
BigPandaToo Jun 27, 2021
168d417
Merge branch 'master' into Sobd_CA
elasticmachine Jun 29, 2021
0a12420
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 7, 2021
ded0c1b
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 7, 2021
7ef6884
Merge branch 'master' into Sobd_CA
elasticmachine Jul 7, 2021
628d6ba
Add xpack.security.enrollment.enabled to settings, so that it can be …
jkakavas Jul 7, 2021
d80d443
Clone the password when passing it to the ChangePasswordRequestBuilde…
jkakavas Jul 7, 2021
7b30f62
Fix TransportNodeEnrollmentAction so that we don't call listener's on…
jkakavas Jul 7, 2021
896a922
Kibana Enrollment request doesn't have a body anymore
jkakavas Jul 7, 2021
115732f
Fix test expected values
jkakavas Jul 7, 2021
16813e3
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 8, 2021
1aed5c4
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 8, 2021
480390c
Merge branch 'master' into Sobd_CA
elasticmachine Jul 8, 2021
90a8001
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 8, 2021
4733a06
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 8, 2021
beaee05
Handle the case of multiple keys in the same keystore
BigPandaToo Jul 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/rest-high-level/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ apply plugin: 'elasticsearch.rest-test'
apply plugin: 'elasticsearch.publish'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'elasticsearch.rest-resources'
apply plugin: 'elasticsearch.internal-test-artifact'

group = 'org.elasticsearch.client'
archivesBaseName = 'elasticsearch-rest-high-level-client'
Expand Down
45 changes: 45 additions & 0 deletions client/rest-high-level/qa/ssl-enabled/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.
*/

import org.elasticsearch.gradle.internal.test.RestIntegTestTask
import org.elasticsearch.gradle.internal.info.BuildParams

apply plugin: 'elasticsearch.java-rest-test'
dependencies {
javaRestTestImplementation(testArtifact(project(':client:rest-high-level')))
}

tasks.matching{ it.name == "javaRestTest" }.configureEach {
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
systemProperty 'tests.rest.cluster.password', System.getProperty('tests.rest.cluster.password', 'test-user-password')
}

testClusters.matching { it.name == 'javaRestTest' }.configureEach {
testDistribution = 'DEFAULT'
numberOfNodes = 2
systemProperty 'es.scripting.update.ctx_in_params', 'false'
setting 'reindex.remote.whitelist', '[ "[::1]:*", "127.0.0.1:*" ]'
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
setting 'xpack.license.self_generated.type', 'trial'
setting 'xpack.security.enabled', 'true'
setting 'xpack.security.authc.token.enabled', 'true'
setting 'xpack.security.authc.api_key.enabled', 'true'

extraConfigFile 'httpCa.p12', file('./src/javaRestTest/resources/httpCa.p12')
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
extraConfigFile 'transport.p12', file('./transport.p12')

setting 'xpack.security.http.ssl.enabled', 'true'
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
setting 'xpack.security.transport.ssl.enabled', 'true'
setting 'xpack.security.http.ssl.keystore.path', 'httpCa.p12'
setting 'xpack.security.transport.ssl.keystore.path', 'transport.p12'
setting 'xpack.security.transport.ssl.verification_mode', 'none'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need verification mode to be none for transport ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to certificate. The reason I didn't leave it to be full - this transport cert doesn't have SAN (which is required for the full verification mode). I think it is orthogonal to these test purposes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to certificate.

This is what we will be configuring nodes with too, so it makes sense to have this to certificate instead of none.

The reason I didn't leave it to be full - this transport cert doesn't have SAN (which is required for the full verification mode).I think it is orthogonal to these test purposes.

We are using this cluster for Enrollment IT and our enrollment process will generate configuration that sets the transport verification mode to certificate so it's in our best interest to keep the test cluster config as close to what users will have as possible



keystore 'xpack.security.http.ssl.keystore.secure_password', 'password'
keystore 'xpack.security.transport.ssl.keystore.secure_password', 'password'
user username: 'admin_user', password: 'admin-password', role: 'superuser'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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;

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.AfterClass;
import org.junit.BeforeClass;

import java.io.FileNotFoundException;
import java.net.URL;
import java.nio.file.Path;
import java.util.List;

import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;

public class EnrollmentIT extends ESRestHighLevelClientTestCase {
private static Path httpTrustStore;

@BeforeClass
public static void findTrustStore() throws Exception {
final URL resource = EnrollmentIT.class.getResource("/httpCa.p12");
if (resource == null) {
throw new FileNotFoundException("Cannot find classpath resource /httpCa.p12");
}
httpTrustStore = PathUtils.get(resource.toURI());
}

@AfterClass
public static void cleanupStatics() {
httpTrustStore = null;
}

@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, httpTrustStore)
.put(TRUSTSTORE_PASSWORD, "password")
.build();
}

public void testEnrollNode() throws Exception {
final NodeEnrollmentResponse nodeEnrollmentResponse =
execute(highLevelClient().security()::enrollNode, highLevelClient().security()::enrollNodeAsync, RequestOptions.DEFAULT);
assertThat(nodeEnrollmentResponse, notNullValue());
assertThat(nodeEnrollmentResponse.getHttpCaKey(), endsWith("K2S3vidA="));
assertThat(nodeEnrollmentResponse.getHttpCaCert(), endsWith("LfkRjirc="));
assertThat(nodeEnrollmentResponse.getTransportKey(), endsWith("1I-r8vOQ=="));
assertThat(nodeEnrollmentResponse.getTransportCert(), endsWith("OpTdtgJo="));
List<String> nodesAddresses = nodeEnrollmentResponse.getNodesAddresses();
assertThat(nodesAddresses.size(), equalTo(2));
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
}

public void testEnrollKibana() throws Exception {
KibanaEnrollmentResponse kibanaResponse =
execute(highLevelClient().security()::enrollKibana, highLevelClient().security()::enrollKibanaAsync, RequestOptions.DEFAULT);
assertThat(kibanaResponse, notNullValue());
assertThat(kibanaResponse.getHttpCa()
, endsWith("brcNC5xq6YE7C4_06nH7F6le4kE4Uo6c9fpkl4ehOxQxndNLn462tFF-8VBA8IftJ1PPWzqGxLsCTzM6p6w8sa-XhgNYglLfkRjirc="));
assertNotNull(kibanaResponse.getPassword());
assertThat(kibanaResponse.getPassword().toString().length(), equalTo(14));
}
}
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.elasticsearch.client.security.GetRolesResponse;
import org.elasticsearch.client.security.GetUsersRequest;
import org.elasticsearch.client.security.GetUsersResponse;
import org.elasticsearch.client.security.NodeEnrollmentResponse;
import org.elasticsearch.client.security.PutRoleRequest;
import org.elasticsearch.client.security.PutRoleResponse;
import org.elasticsearch.client.security.PutUserRequest;
Expand All @@ -34,7 +33,6 @@
import org.elasticsearch.client.security.user.privileges.IndicesPrivileges;
import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests;
import org.elasticsearch.client.security.user.privileges.Role;
import org.elasticsearch.client.security.KibanaEnrollmentResponse;
import org.elasticsearch.core.CharArrays;

import java.io.IOException;
Expand All @@ -47,12 +45,10 @@
import java.util.Map;

import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

public class SecurityIT extends ESRestHighLevelClientTestCase {
Expand Down Expand Up @@ -196,30 +192,6 @@ public void testPutRole() throws Exception {
assertThat(deleteRoleResponse.isFound(), is(true));
}

@AwaitsFix(bugUrl = "Determine behavior for keystore with multiple keys")
public void testEnrollNode() throws Exception {
final NodeEnrollmentResponse nodeEnrollmentResponse =
execute(highLevelClient().security()::enrollNode, highLevelClient().security()::enrollNodeAsync, RequestOptions.DEFAULT);
assertThat(nodeEnrollmentResponse, notNullValue());
assertThat(nodeEnrollmentResponse.getHttpCaKey(), endsWith("ECAwGGoA=="));
assertThat(nodeEnrollmentResponse.getHttpCaCert(), endsWith("ECAwGGoA=="));
assertThat(nodeEnrollmentResponse.getTransportKey(), endsWith("fSI09on8AgMBhqA="));
assertThat(nodeEnrollmentResponse.getTransportCert(), endsWith("fSI09on8AgMBhqA="));
List<String> nodesAddresses = nodeEnrollmentResponse.getNodesAddresses();
assertThat(nodesAddresses.size(), equalTo(1));
}

@AwaitsFix(bugUrl = "Determine behavior for keystores with multiple keys")
public void testEnrollKibana() throws Exception {
KibanaEnrollmentResponse kibanaResponse =
execute(highLevelClient().security()::enrollKibana, highLevelClient().security()::enrollKibanaAsync, RequestOptions.DEFAULT);
assertThat(kibanaResponse, notNullValue());
assertThat(kibanaResponse.getHttpCa()
, endsWith("OWFyeGNmcwovSDJReE1tSG1leXJRaWxYbXJPdk9PUDFTNGRrSTFXbFJLOFdaN3c9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"));
assertNotNull(kibanaResponse.getPassword());
assertThat(kibanaResponse.getPassword().toString().length(), equalTo(14));
}

private void deleteUser(User user) throws IOException {
final Request deleteUserRequest = new Request(HttpDelete.METHOD_NAME, "/_security/user/" + user.getUsername());
highLevelClient().getLowLevelClient().performRequest(deleteUserRequest);
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ List projects = [
'docs',
'client:rest',
'client:rest-high-level',
'client:rest-high-level:qa:ssl-enabled',
'client:sniffer',
'client:transport',
'client:test',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ public static List<Setting<?>> getAllSettings() {
settings.add(API_KEY_SERVICE_ENABLED_SETTING);
settings.add(USER_SETTING);
settings.add(PASSWORD_HASHING_ALGORITHM);
settings.add(ENROLLMENT_ENABLED);
return Collections.unmodifiableList(settings);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ X509ExtendedKeyManager createKeyManager(@Nullable Environment environment) {
Path ksPath = keyStorePath == null ? null : CertParsingUtils.resolvePath(keyStorePath, environment);
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
try {
KeyStore ks = getStore(ksPath, keyStoreType, keyStorePassword);
// TBD: filte out only http.ssl.keystore
ArrayList<String> aliases = Collections.list(ks.aliases());
if (this instanceof StoreKeyConfig && aliases.size() > 1) {
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
for (String alias : aliases) {
Certificate certificate = ks.getCertificate(alias);
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
if (certificate instanceof X509Certificate) {
if (((X509Certificate) certificate).getBasicConstraints() != -1) {
ks.deleteEntry(alias);
}
}
}
}
checkKeyStore(ks);
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
return CertParsingUtils.keyManager(ks, keyPassword.getChars(), keyStoreAlgorithm);
} catch (FileNotFoundException | NoSuchFileException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public class TransportKibanaEnrollmentAction extends HandledTransportAction<Kiba
final char[] password = generateKibanaSystemPassword();
final ChangePasswordRequest changePasswordRequest =
new ChangePasswordRequestBuilder(client).username("kibana_system")
.password(password, Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(environment.settings())))
.password(password.clone(), Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(environment.settings())))
jkakavas marked this conversation as resolved.
Show resolved Hide resolved
.request();
client.execute(ChangePasswordAction.INSTANCE, changePasswordRequest, ActionListener.wrap(response -> {
logger.debug("Successfully set the password for user [kibana_system] during kibana enrollment");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,22 @@ protected void doExecute(Task task, NodeEnrollmentRequest request, ActionListene
for (NodeInfo nodeInfo : response.getNodes()) {
nodeList.add(nodeInfo.getInfo(TransportInfo.class).getAddress().publishAddress().toString());
}
try {
final String httpCaKey = Base64.getUrlEncoder().encodeToString(httpCaKeysAndCertificates.get(0).v1().getEncoded());
final String httpCaCert = Base64.getUrlEncoder().encodeToString(httpCaKeysAndCertificates.get(0).v2().getEncoded());
final String transportKey =
Base64.getUrlEncoder().encodeToString(transportKeysAndCertificates.get(0).v1().getEncoded());
final String transportCert =
Base64.getUrlEncoder().encodeToString(transportKeysAndCertificates.get(0).v2().getEncoded());
listener.onResponse(new NodeEnrollmentResponse(httpCaKey,
httpCaCert,
transportKey,
transportCert,
nodeList));
} catch (CertificateEncodingException e) {
listener.onFailure(new ElasticsearchException("Unable to enroll node", e));
}
}, listener::onFailure
));
try {
final String httpCaKey = Base64.getUrlEncoder().encodeToString(httpCaKeysAndCertificates.get(0).v1().getEncoded());
final String httpCaCert = Base64.getUrlEncoder().encodeToString(httpCaKeysAndCertificates.get(0).v2().getEncoded());
final String transportKey = Base64.getUrlEncoder().encodeToString(transportKeysAndCertificates.get(0).v1().getEncoded());
final String transportCert = Base64.getUrlEncoder().encodeToString(transportKeysAndCertificates.get(0).v2().getEncoded());
listener.onResponse(new NodeEnrollmentResponse(httpCaKey,
httpCaCert,
transportKey,
transportCert,
nodeList));
} catch (CertificateEncodingException e) {
listener.onFailure(new ElasticsearchException("Unable to enroll node", e));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestRequest;
Expand Down Expand Up @@ -43,19 +42,15 @@ public RestKibanaEnrollAction(Settings settings, XPackLicenseState licenseState)
return List.of(new Route(RestRequest.Method.GET, "/_security/enroll/kibana"));
}

@Override protected RestChannelConsumer innerPrepareRequest(
RestRequest request, NodeClient client) throws IOException {
try (XContentParser parser = request.contentParser()) {
return restChannel -> client.execute(
KibanaEnrollmentAction.INSTANCE, new KibanaEnrollmentRequest(),
new RestBuilderListener<>(restChannel) {
@Override public RestResponse buildResponse(
KibanaEnrollmentResponse kibanaEnrollmentResponse, XContentBuilder builder) throws Exception {
kibanaEnrollmentResponse.toXContent(builder, channel.request());
return new BytesRestResponse(RestStatus.OK, builder);
}
});
}
@Override protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
return restChannel -> client.execute(KibanaEnrollmentAction.INSTANCE,
new KibanaEnrollmentRequest(),
new RestBuilderListener<KibanaEnrollmentResponse>(restChannel) {
@Override public RestResponse buildResponse(
KibanaEnrollmentResponse kibanaEnrollmentResponse, XContentBuilder builder) throws Exception {
kibanaEnrollmentResponse.toXContent(builder, channel.request());
return new BytesRestResponse(RestStatus.OK, builder);
}
});
}

}