PKI realm authentication delegation (#45906)

This commit introduces PKI realm delegation. This feature
supports the PKI authentication feature in Kibana.

In essence, this creates a new API endpoint which Kibana must
call to authenticate clients that use certificates in their TLS
connection to Kibana. The API call passes to Elasticsearch the client's
certificate chain. The response contains an access token to be further
used to authenticate as the client. The client's certificates are validated
by the PKI realms that have been explicitly configured to permit
certificates from the proxy (Kibana). The user calling the delegation
API must have the delegate_pki privilege.

Closes #34396
This commit is contained in:
Albert Zaharovits 2019-08-27 14:42:46 +03:00 committed by GitHub
parent b249e25bb4
commit 1ebee5bf9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 2875 additions and 66 deletions

View File

@ -78,6 +78,7 @@ processTestResources {
from({ zipTree(configurations.restSpec.singleFile) }) {
include 'rest-api-spec/api/**'
}
from(project(':client:rest-high-level').file('src/test/resources'))
}
dependencyLicenses {
@ -96,6 +97,7 @@ forbiddenApisMain {
}
File nodeCert = file("./testnode.crt")
File nodeTrustStore = file("./testnode.jks")
File pkiTrustCert = file("./src/test/resources/org/elasticsearch/client/security/delegate_pki/testRootCA.crt")
integTest.runner {
systemProperty 'tests.rest.cluster.username', System.getProperty('tests.rest.cluster.username', 'test_user')
@ -113,6 +115,12 @@ testClusters.integTest {
// Truststore settings are not used since TLS is not enabled. Included for testing the get certificates API
setting 'xpack.security.http.ssl.certificate_authorities', 'testnode.crt'
setting 'xpack.security.transport.ssl.truststore.path', 'testnode.jks'
setting 'xpack.security.authc.realms.file.default_file.order', '0'
setting 'xpack.security.authc.realms.native.default_native.order', '1'
setting 'xpack.security.authc.realms.pki.pki1.order', '2'
setting 'xpack.security.authc.realms.pki.pki1.certificate_authorities', '[ "testRootCA.crt" ]'
setting 'xpack.security.authc.realms.pki.pki1.delegation.enabled', 'true'
setting 'indices.lifecycle.poll_interval', '1000ms'
keystore 'xpack.security.transport.ssl.truststore.secure_password', 'testnode'
user username: System.getProperty('tests.rest.cluster.username', 'test_user'),
@ -120,4 +128,5 @@ testClusters.integTest {
extraConfigFile nodeCert.name, nodeCert
extraConfigFile nodeTrustStore.name, nodeTrustStore
extraConfigFile pkiTrustCert.name, pkiTrustCert
}

View File

@ -31,6 +31,8 @@ import org.elasticsearch.client.security.CreateApiKeyRequest;
import org.elasticsearch.client.security.CreateApiKeyResponse;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.CreateTokenResponse;
import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
import org.elasticsearch.client.security.DelegatePkiAuthenticationResponse;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeletePrivilegesResponse;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
@ -969,4 +971,38 @@ public final class SecurityClient {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateApiKey, options,
InvalidateApiKeyResponse::fromXContent, listener, emptySet());
}
/**
* Get an Elasticsearch access token from an {@code X509Certificate} chain. The certificate chain is that of the client from a mutually
* authenticated TLS session, and it is validated by the PKI realms with {@code delegation.enabled} toggled to {@code true}.<br>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delegate-pki-authentication.html"> the
* docs</a> for more details.
*
* @param request the request containing the certificate chain
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return the response from the delegate-pki-authentication API key call
* @throws IOException in case there is a problem sending the request or parsing back the response
*/
public DelegatePkiAuthenticationResponse delegatePkiAuthentication(DelegatePkiAuthenticationRequest request, RequestOptions options)
throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::delegatePkiAuthentication, options,
DelegatePkiAuthenticationResponse::fromXContent, emptySet());
}
/**
* Asynchronously get an Elasticsearch access token from an {@code X509Certificate} chain. The certificate chain is that of the client
* from a mutually authenticated TLS session, and it is validated by the PKI realms with {@code delegation.enabled} toggled to
* {@code true}.<br>
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-delegate-pki-authentication.html"> the
* docs</a> for more details.
*
* @param request the request containing the certificate chain
* @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 delegatePkiAuthenticationAsync(DelegatePkiAuthenticationRequest request, RequestOptions options,
ActionListener<DelegatePkiAuthenticationResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::delegatePkiAuthentication, options,
DelegatePkiAuthenticationResponse::fromXContent, listener, emptySet());
}
}

View File

@ -28,6 +28,7 @@ import org.elasticsearch.client.security.ClearRealmCacheRequest;
import org.elasticsearch.client.security.ClearRolesCacheRequest;
import org.elasticsearch.client.security.CreateApiKeyRequest;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
@ -221,6 +222,12 @@ final class SecurityRequestConverters {
return request;
}
static Request delegatePkiAuthentication(DelegatePkiAuthenticationRequest delegatePkiAuthenticationRequest) throws IOException {
Request request = new Request(HttpPost.METHOD_NAME, "/_security/delegate_pki");
request.setEntity(createEntity(delegatePkiAuthenticationRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
static Request invalidateToken(InvalidateTokenRequest invalidateTokenRequest) throws IOException {
Request request = new Request(HttpDelete.METHOD_NAME, "/_security/oauth2/token");
request.setEntity(createEntity(invalidateTokenRequest, REQUEST_BODY_CONTENT_TYPE));

View File

@ -0,0 +1,107 @@
/*
* 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.Validatable;
import org.elasticsearch.client.ValidationException;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static java.util.Collections.unmodifiableList;
public final class DelegatePkiAuthenticationRequest implements Validatable, ToXContentObject {
private final List<X509Certificate> x509CertificateChain;
public DelegatePkiAuthenticationRequest(final List<X509Certificate> x509CertificateChain) {
if (x509CertificateChain == null || x509CertificateChain.isEmpty()) {
throw new IllegalArgumentException("certificate chain must not be empty or null");
}
this.x509CertificateChain = unmodifiableList(x509CertificateChain);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject().startArray("x509_certificate_chain");
try {
for (X509Certificate cert : x509CertificateChain) {
builder.value(Base64.getEncoder().encodeToString(cert.getEncoded()));
}
} catch (CertificateEncodingException e) {
throw new IOException(e);
}
return builder.endArray().endObject();
}
public List<X509Certificate> getCertificateChain() {
return this.x509CertificateChain;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final DelegatePkiAuthenticationRequest that = (DelegatePkiAuthenticationRequest) o;
return Objects.equals(x509CertificateChain, that.x509CertificateChain);
}
@Override
public int hashCode() {
return Objects.hash(x509CertificateChain);
}
@Override
public Optional<ValidationException> validate() {
ValidationException validationException = new ValidationException();
if (false == isOrderedCertificateChain(x509CertificateChain)) {
validationException.addValidationError("certificates chain must be an ordered chain");
}
return validationException.validationErrors().isEmpty() ? Optional.empty() : Optional.of(validationException);
}
/**
* Checks that the {@code X509Certificate} list is ordered, such that the end-entity certificate is first and it is followed by any
* certificate authorities'. The check validates that the {@code issuer} of every certificate is the {@code subject} of the certificate
* in the next array position. No other certificate attributes are checked.
*/
private static boolean isOrderedCertificateChain(List<X509Certificate> chain) {
for (int i = 1; i < chain.size(); i++) {
X509Certificate cert = chain.get(i - 1);
X509Certificate issuer = chain.get(i);
if (false == cert.getIssuerX500Principal().equals(issuer.getSubjectX500Principal())) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.common.ParseField;
import org.elasticsearch.common.unit.TimeValue;
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;
public final class DelegatePkiAuthenticationResponse {
private final String accessToken;
private final String type;
private final TimeValue expiresIn;
public DelegatePkiAuthenticationResponse(String accessToken, String type, TimeValue expiresIn) {
this.accessToken = accessToken;
this.type = type;
this.expiresIn = expiresIn;
}
public String getAccessToken() {
return accessToken;
}
public String getType() {
return type;
}
public TimeValue getExpiresIn() {
return expiresIn;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final DelegatePkiAuthenticationResponse that = (DelegatePkiAuthenticationResponse) o;
return Objects.equals(accessToken, that.accessToken) &&
Objects.equals(type, that.type) &&
Objects.equals(expiresIn, that.expiresIn);
}
@Override
public int hashCode() {
return Objects.hash(accessToken, type, expiresIn);
}
private static final ConstructingObjectParser<DelegatePkiAuthenticationResponse, Void> PARSER = new ConstructingObjectParser<>(
"delegate_pki_response", true,
args -> new DelegatePkiAuthenticationResponse((String) args[0], (String) args[1], TimeValue.timeValueSeconds((Long) args[2])));
static {
PARSER.declareString(constructorArg(), new ParseField("access_token"));
PARSER.declareString(constructorArg(), new ParseField("type"));
PARSER.declareLong(constructorArg(), new ParseField("expires_in"));
}
public static DelegatePkiAuthenticationResponse fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

View File

@ -26,6 +26,7 @@ import org.apache.http.client.methods.HttpPut;
import org.elasticsearch.client.security.ChangePasswordRequest;
import org.elasticsearch.client.security.CreateApiKeyRequest;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.DeleteRoleRequest;
@ -59,6 +60,7 @@ import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -68,6 +70,8 @@ import java.util.Map;
import static org.elasticsearch.client.RequestConvertersTests.assertToXContentBody;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class SecurityRequestConvertersTests extends ESTestCase {
@ -305,6 +309,18 @@ public class SecurityRequestConvertersTests extends ESTestCase {
assertToXContentBody(createTokenRequest, request.getEntity());
}
public void testDelegatePkiAuthentication() throws Exception {
X509Certificate mockCertificate = mock(X509Certificate.class);
when(mockCertificate.getEncoded()).thenReturn(new byte[0]);
DelegatePkiAuthenticationRequest delegatePkiAuthenticationRequest = new DelegatePkiAuthenticationRequest(
Arrays.asList(mockCertificate));
Request request = SecurityRequestConverters.delegatePkiAuthentication(delegatePkiAuthenticationRequest);
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
assertEquals("/_security/delegate_pki", request.getEndpoint());
assertEquals(0, request.getParameters().size());
assertToXContentBody(delegatePkiAuthenticationRequest, request.getEntity());
}
public void testGetApplicationPrivilege() throws Exception {
final String application = randomAlphaOfLength(6);
final String privilege = randomAlphaOfLength(4);

View File

@ -37,6 +37,8 @@ import org.elasticsearch.client.security.CreateApiKeyRequest;
import org.elasticsearch.client.security.CreateApiKeyResponse;
import org.elasticsearch.client.security.CreateTokenRequest;
import org.elasticsearch.client.security.CreateTokenResponse;
import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
import org.elasticsearch.client.security.DelegatePkiAuthenticationResponse;
import org.elasticsearch.client.security.DeletePrivilegesRequest;
import org.elasticsearch.client.security.DeletePrivilegesResponse;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
@ -77,6 +79,7 @@ import org.elasticsearch.client.security.PutUserRequest;
import org.elasticsearch.client.security.PutUserResponse;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.client.security.TemplateRoleName;
import org.elasticsearch.client.security.AuthenticateResponse.RealmInfo;
import org.elasticsearch.client.security.support.ApiKey;
import org.elasticsearch.client.security.support.CertificateInfo;
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
@ -99,6 +102,11 @@ import org.hamcrest.Matchers;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@ -2194,4 +2202,84 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
assertThat(response.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
}
}
public void testDelegatePkiAuthentication() throws Exception {
final RestHighLevelClient client = highLevelClient();
X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
{
//tag::delegate-pki-request
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(
Arrays.asList(clientCertificate, intermediateCA));
//end::delegate-pki-request
//tag::delegate-pki-execute
DelegatePkiAuthenticationResponse response = client.security().delegatePkiAuthentication(request, RequestOptions.DEFAULT);
//end::delegate-pki-execute
//tag::delegate-pki-response
String accessToken = response.getAccessToken(); // <1>
//end::delegate-pki-response
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", "Bearer " + accessToken);
AuthenticateResponse resp = client.security().authenticate(optionsBuilder.build());
User user = resp.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.getUsername(), is("Elasticsearch Test Client"));
RealmInfo authnRealm = resp.getAuthenticationRealm();
assertThat(authnRealm, is(notNullValue()));
assertThat(authnRealm.getName(), is("pki1"));
assertThat(authnRealm.getType(), is("pki"));
}
{
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(
Arrays.asList(clientCertificate, intermediateCA));
ActionListener<DelegatePkiAuthenticationResponse> listener;
//tag::delegate-pki-execute-listener
listener = new ActionListener<DelegatePkiAuthenticationResponse>() {
@Override
public void onResponse(DelegatePkiAuthenticationResponse getRolesResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
//end::delegate-pki-execute-listener
assertNotNull(listener);
// Replace the empty listener by a blocking listener in test
final PlainActionFuture<DelegatePkiAuthenticationResponse> future = new PlainActionFuture<>();
listener = future;
//tag::delegate-pki-execute-async
client.security().delegatePkiAuthenticationAsync(request, RequestOptions.DEFAULT, listener); // <1>
//end::delegate-pki-execute-async
final DelegatePkiAuthenticationResponse response = future.get(30, TimeUnit.SECONDS);
String accessToken = response.getAccessToken();
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", "Bearer " + accessToken);
AuthenticateResponse resp = client.security().authenticate(optionsBuilder.build());
User user = resp.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.getUsername(), is("Elasticsearch Test Client"));
RealmInfo authnRealm = resp.getAuthenticationRealm();
assertThat(authnRealm, is(notNullValue()));
assertThat(authnRealm.getName(), is("pki1"));
assertThat(authnRealm.getType(), is("pki"));
}
}
private X509Certificate readCertForPkiDelegation(String certificateName) throws Exception {
Path path = getDataPath("/org/elasticsearch/client/security/delegate_pki/" + certificateName);
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
}
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.AbstractRequestTestCase;
import org.elasticsearch.client.ValidationException;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.security.auth.x500.X500Principal;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DelegatePkiAuthenticationRequestTests extends AbstractRequestTestCase<DelegatePkiAuthenticationRequest,
org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest> {
public void testEmptyOrNullCertificateChain() throws Exception {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
new DelegatePkiAuthenticationRequest((List<X509Certificate>)null);
});
assertThat(e.getMessage(), is("certificate chain must not be empty or null"));
e = expectThrows(IllegalArgumentException.class, () -> {
new DelegatePkiAuthenticationRequest(Collections.emptyList());
});
assertThat(e.getMessage(), is("certificate chain must not be empty or null"));
}
public void testUnorderedCertificateChain() throws Exception {
List<X509Certificate> mockCertChain = new ArrayList<>(2);
mockCertChain.add(mock(X509Certificate.class));
when(mockCertChain.get(0).getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
mockCertChain.add(mock(X509Certificate.class));
when(mockCertChain.get(1).getSubjectX500Principal()).thenReturn(new X500Principal("CN=Not Test, OU=elasticsearch, O=org"));
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(mockCertChain);
Optional<ValidationException> ve = request.validate();
assertThat(ve.isPresent(), is(true));
assertThat(ve.get().validationErrors().size(), is(1));
assertThat(ve.get().validationErrors().get(0), is("certificates chain must be an ordered chain"));
}
@Override
protected DelegatePkiAuthenticationRequest createClientTestInstance() {
List<X509Certificate> certificates = randomCertificateList();
return new DelegatePkiAuthenticationRequest(certificates);
}
@Override
protected org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest doParseToServerInstance(XContentParser parser)
throws IOException {
return org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest.fromXContent(parser);
}
@Override
protected void assertInstances(org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest serverInstance,
DelegatePkiAuthenticationRequest clientTestInstance) {
assertThat(serverInstance.getCertificateChain(), is(clientTestInstance.getCertificateChain()));
}
private List<X509Certificate> randomCertificateList() {
List<X509Certificate> certificates = Arrays.asList(randomArray(1, 3, X509Certificate[]::new, () -> {
try {
return readCertForPkiDelegation(randomFrom("testClient.crt", "testIntermediateCA.crt", "testRootCA.crt"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
return certificates;
}
private X509Certificate readCertForPkiDelegation(String certificateName) throws Exception {
Path path = getDataPath("/org/elasticsearch/client/security/delegate_pki/" + certificateName);
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.AbstractResponseTestCase;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.client.security.DelegatePkiAuthenticationResponse;
import java.io.IOException;
import static org.hamcrest.Matchers.is;
public class DelegatePkiAuthenticationResponseTests extends
AbstractResponseTestCase<org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse,
DelegatePkiAuthenticationResponse> {
@Override
protected org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse createServerTestInstance() {
return new org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse(randomAlphaOfLength(6),
TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn"));
}
@Override
protected DelegatePkiAuthenticationResponse doParseToClientInstance(XContentParser parser) throws IOException {
return DelegatePkiAuthenticationResponse.fromXContent(parser);
}
@Override
protected void assertInstances(org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse serverTestInstance,
DelegatePkiAuthenticationResponse clientInstance) {
assertThat(serverTestInstance.getAccessToken(), is(clientInstance.getAccessToken()));
assertThat(serverTestInstance.getExpiresIn(), is(clientInstance.getExpiresIn()));
assertThat(clientInstance.getType(), is("Bearer"));
}
}

View File

@ -0,0 +1,35 @@
= Certificate Chain details
This document details the steps used to create the certificate chain in this directory.
The chain has a length of 3: the Root CA, the Intermediate CA and the Client Certificate.
All openssl commands use the same configuration file, albeit different sections of it.
The OpenSSL Configuration file is located in this directory as `openssl_config.cnf`.
== Instructions on generating self-signed Root CA
The self-signed Root CA, 'testRootCA.crt', and its associated private key in this directory
have been generated using the following openssl commands.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testRootCA.key 2048
openssl req -x509 -new -key testRootCA.key -days 1460 -subj "/CN=Elasticsearch Test Root CA/OU=elasticsearch/O=org" -out testRootCA.crt -config ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------
== Instructions on generating the Intermediate CA
The `testIntermediateCA.crt` CA certificate is "issued" by the `testRootCA.crt`.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testIntermediateCA.key 2048
openssl req -new -key testIntermediateCA.key -subj "/CN=Elasticsearch Test Intermediate CA/OU=Elasticsearch/O=org" -out testIntermediateCA.csr -config ./openssl_config.cnf
openssl x509 -req -in testIntermediateCA.csr -CA testRootCA.crt -CAkey testRootCA.key -CAcreateserial -out testIntermediateCA.crt -days 1460 -sha256 -extensions v3_ca -extfile ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------
== Instructions on generating the Client Certificate
The `testClient.crt` end entity certificate is "issued" by the `testIntermediateCA.crt`.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testClient.key 2048
openssl req -new -key testClient.key -subj "/CN=Elasticsearch Test Client/OU=Elasticsearch/O=org" -out testClient.csr -config ./openssl_config.cnf
openssl x509 -req -in testClient.csr -CA testIntermediateCA.crt -CAkey testIntermediateCA.key -CAcreateserial -out testClient.crt -days 1460 -sha256 -extensions usr_cert -extfile ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,185 @@
####################################################################
# CA Definition
[ ca ]
default_ca = CA_default # The default ca section
####################################################################
# Per the above, this is where we define CA values
[ CA_default ]
# By default we use "user certificate" extensions when signing
x509_extensions = usr_cert # The extentions to add to the cert
# Honor extensions requested of us
copy_extensions = copy
# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
#crl_extensions = crl_ext
default_days = 1460 # how long to certify for
default_md = sha256 # which md to use.
preserve = no # keep passed DN ordering
# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_anything
####################################################################
# The default policy for the CA when signing requests, requires some
# resemblence to the CA cert
#
[ policy_match ]
countryName = match # Must be the same as the CA
stateOrProvinceName = match # Must be the same as the CA
organizationName = match # Must be the same as the CA
organizationalUnitName = optional # not required
commonName = supplied # must be there, whatever it is
emailAddress = optional # not required
####################################################################
# An alternative policy not referred to anywhere in this file. Can
# be used by specifying '-policy policy_anything' to ca(8).
#
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
# This is where we define how to generate CSRs
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name # where to get DN for reqs
attributes = req_attributes # req attributes
x509_extensions = v3_ca # The extentions to add to self signed certs
req_extensions = v3_req # The extensions to add to req's
# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix : PrintableString, BMPString.
# utf8only: only UTF8Strings.
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
# so use this option with caution!
string_mask = nombstr
####################################################################
# Per "req" section, this is where we define DN info
[ req_distinguished_name ]
0.organizationName = Organization Name (company)
0.organizationName_default = org
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = elasticsearch
commonName = Common Name (hostname, IP, or your name)
commonName_default = Elasticsearch Test Certificate
commonName_max = 64
####################################################################
# We don't want these, but the section must exist
[ req_attributes ]
#challengePassword = A challenge password
#challengePassword_min = 4
#challengePassword_max = 20
#unstructuredName = An optional company name
####################################################################
# Extensions for when we sign normal certs (specified as default)
[ usr_cert ]
# User certs aren't CAs, by definition
basicConstraints=CA:false
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
#nsCertType = server
# For an object signing certificate this would be used.
#nsCertType = objsign
# For normal client use this is typical
#nsCertType = client, email
# and for everything including object signing:
#nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
#subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
#subjectAltName=email:move
####################################################################
# Extension for requests
[ v3_req ]
basicConstraints = CA:FALSE
# PKIX recommendation.
subjectKeyIdentifier = hash
subjectAltName = @alt_names
####################################################################
# An alternative section of extensions, not referred to anywhere
# else in the config. We'll use this via '-extensions v3_ca' when
# using ca(8) to sign another CA.
#
[ v3_ca ]
# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier = keyid,issuer
# This is what PKIX recommends but some broken software chokes on critical
# extensions.
#basicConstraints = critical,CA:true
# So we do this instead.
basicConstraints = CA:true
# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign
# Some might want this also
# nsCertType = sslCA, emailCA
# Include email address in subject alt name: another PKIX recommendation
#subjectAltName=email:move
# Copy issuer details
#issuerAltName=issuer:copy
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = localhost.localdomain
DNS.3 = localhost4
DNS.4 = localhost4.localdomain4
DNS.5 = localhost6
DNS.6 = localhost6.localdomain6
IP.1 = 127.0.0.1
IP.2 = ::1

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIJAIxTS7Qdho9jMA0GCSqGSIb3DQEBCwUAMFMxKzApBgNV
BAMTIkVsYXN0aWNzZWFyY2ggVGVzdCBJbnRlcm1lZGlhdGUgQ0ExFjAUBgNVBAsT
DUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzAeFw0xOTA3MTkxMzMzNDFaFw0y
MzA3MTgxMzMzNDFaMEoxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGll
bnQxFjAUBgNVBAsTDUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANHgMX2aX8t0nj4sGLNuKISmmXIYCj9R
wRqS7L03l9Nng7kOKnhHu/nXDt7zMRJyHj+q6FAt5khlavYSVCQyrDybRuA5z31g
OdqXerrjs2OXS5HSHNvoDAnHFsaYX/5geMewVTtc/vqpd7Ph/QtaKfmG2FK0JNQo
0k24tcgCIcyMtBh6BA70yGBM0OT8GdOgd/d/mA7mRhaxIUMNYQzRYRsp4hMnnWoO
TkR5Q8KSO3MKw9dPSpPe8EnwtJE10S3s5aXmgytru/xQqrFycPBNj4KbKVmqMP0G
60CzXik5pr2LNvOFz3Qb6sYJtqeZF+JKgGWdaTC89m63+TEnUHqk0lcCAwEAAaNN
MEswCQYDVR0TBAIwADAdBgNVHQ4EFgQU/+aAD6Q4mFq1vpHorC25/OY5zjcwHwYD
VR0jBBgwFoAU8siFCiMiYZZm/95qFC75AG/LRE0wDQYJKoZIhvcNAQELBQADggEB
AIRpCgDLpvXcgDHUk10uhxev21mlIbU+VP46ANnCuj0UELhTrdTuWvO1PAI4z+Wb
DUxryQfOOXO9R6D0dE5yR56L/J7d+KayW34zU7yRDZM7+rXpocdQ1Ex8mjP9HJ/B
f56YZTBQJpXeDrKow4FvtkI3bcIMkqmbG16LHQXeG3RS4ds4S4wCnE2nA6vIn9y+
4R999q6y1VSBORrYULcDWxS54plHLEdiMr1vVallg82AGobS9GMcTL2U4Nx5IYZG
7sbTk3LrDxVpVg/S2wLofEdOEwqCeHug/iOihNLJBabEW6z4TDLJAVW5KCY1Dfhk
YlBfHn7vxKkfKoCUK/yLWWI=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0eAxfZpfy3SePiwYs24ohKaZchgKP1HBGpLsvTeX02eDuQ4q
eEe7+dcO3vMxEnIeP6roUC3mSGVq9hJUJDKsPJtG4DnPfWA52pd6uuOzY5dLkdIc
2+gMCccWxphf/mB4x7BVO1z++ql3s+H9C1op+YbYUrQk1CjSTbi1yAIhzIy0GHoE
DvTIYEzQ5PwZ06B393+YDuZGFrEhQw1hDNFhGyniEyedag5ORHlDwpI7cwrD109K
k97wSfC0kTXRLezlpeaDK2u7/FCqsXJw8E2PgpspWaow/QbrQLNeKTmmvYs284XP
dBvqxgm2p5kX4kqAZZ1pMLz2brf5MSdQeqTSVwIDAQABAoIBAQDAjP767Ioc4LZZ
9h0HafaUlUDMs4+bPkd7OPcoNnv+AceRHZULW0zz0EIdfGM2OCrWYNfYz/Op0hpK
/s/hkfgBdriU+ZUKwyDxEu8Pzd6EbYdwlqPRgdihk92qgJv5hsro8jeQSibJFHf1
Ok3tf2BpRTTs08fCOl2P3vowMPyPa5Ho9bf4lzP8IsR2BZvoaev3za9ZWR6ZDzE6
EWkBBNgIU4aPn1IJ6dz2+rVtN6+xXET0eYSBEac3xMQaPWLEX0EDBYPW1d+mUva/
3lJvTrs3g8oyiTyVu0l9Yxdgox1mtgmrqqwxJ6XuouzImuXMMDXaz0K/E/+u2yPF
V6kRvWuJAoGBAPOnEgBC3ezl+x+47cgbwpy97uZhZmV9HkMrSH9DKDwC+t57TdGX
ypt2S/IS/vbPupFv0aHaWmJ6SN/HyTN4znwuulV3kE8mEpQzIPbluWfgQzT6ukJe
+YFI/+IXwIRBLA7khtfo01LGHSmLTENsnd/aoRySY3K6zJz36Ys3vFdjAoGBANyC
7rF5YjPdgsAgOT7EboNGkc8UuW/Sh3xRp0c4Y+PBenf60yA5XkRJLYR4sZDjWTr0
aKBY7Y8r+59U+bBrwUuhhoW08JZ/SBWja05+4DhH0ToA3vtbPv9lRyQfkF1DdBkn
XpyM2vaJE5M454acwnKJ81AyoueYtZ8pD3Q7c219AoGAJ+F1wdMwDgGKvCOB0Boz
HYK9IrpYj04OcQIZqLLuV/xI4befAiptQEr5nVLcprtTl1CNKIfb+Xh4iyBhX2pr
qcngN/MNDNd3fQhtYdwyH72GYpqTeB+hiTbQo0ot+bfNJVbkd1ylkkvZJB6nyfVy
VdysOEgBvRq0OREfCemCi28CgYEAoF1EE6NQDKICTZDhsMkQCb5PmcbbmPwFdh63
xW64DlGNrCWoVt4BtS12wck4cUM1iE9oq3wgv6df5Z7ZuziSKVt9xk0xTnGgTcQ7
7KkOjT+FZGZvw2K3bOsNkrK1vW2pyAU+pCE3uGU17DJNBjOIod27Kk649C61ntsw
lvoJVs0CgYBLr9pzBRPyD5/lM9hm2EI7ITa+fVcu3V3bJfXENHKzpb0lB2fhl0PI
swpiU8RUEKWyjBuHsdQdxg7AgFi/7s+SX7KLo4cudDRd73iiXYdNGB7R0/MAG8Jl
/lMXn14noS4trA8fNGGg/2fANTBtLTbOX9i4s7clAo8ETywQ33owug==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEBTCCAu2gAwIBAgIJAIx9twpbtGkCMA0GCSqGSIb3DQEBCwUAMEsxIzAhBgNV
BAMTGkVsYXN0aWNzZWFyY2ggVGVzdCBSb290IENBMRYwFAYDVQQLEw1lbGFzdGlj
c2VhcmNoMQwwCgYDVQQKEwNvcmcwHhcNMTkwNzE5MTMzMjM0WhcNMjMwNzE4MTMz
MjM0WjBTMSswKQYDVQQDEyJFbGFzdGljc2VhcmNoIFRlc3QgSW50ZXJtZWRpYXRl
IENBMRYwFAYDVQQLEw1FbGFzdGljc2VhcmNoMQwwCgYDVQQKEwNvcmcwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnJ2KTJZnQzOt0uUf+5oLNcvDLnnWY
LzXZpOOX666Almwx+PVkDxkiGSe0QB9RWJqHSrsP1ryGIeCIzGMOctLt6QA7Peee
HdrKqOQgN620nDSd2EZ3s0Iddh1Ns/lfTtBJCP/03suaktm7j8EYKAyOlTIUhiKm
sTFlxPUSKjbtR4wR1ljnKN8X+j/ghr9mWhQrMR9rsGFObU8DQFho2Ti90C4HoMNU
dy4j+2G3VVpaq4he4/4CbPrWQQ3dKGpzVAngIuAv4eQ/y88EHAFwutxQZWAew4Va
5y3O112acSb9oC7g0NHQcBnos/WIChF5ki8V3LFnxN7jYvUUk9YxfA8hAgMBAAGj
geMwgeAwHQYDVR0OBBYEFPLIhQojImGWZv/eahQu+QBvy0RNMB8GA1UdIwQYMBaA
FM4SyNzpz82ihQ160zrLUVaWfI+1MAwGA1UdEwQFMAMBAf8wgY8GA1UdEQSBhzCB
hIIJbG9jYWxob3N0ghVsb2NhbGhvc3QubG9jYWxkb21haW6CCmxvY2FsaG9zdDSC
F2xvY2FsaG9zdDQubG9jYWxkb21haW40ggpsb2NhbGhvc3Q2ghdsb2NhbGhvc3Q2
LmxvY2FsZG9tYWluNocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B
AQsFAAOCAQEAMkh4nUi2yt5TX+ryBWaaA4/2ZOsxSeec5E1EjemPMUWGzFipV1YY
k/mpv51E+BbPgtmGMG8Win/PETKYuX8D+zPauFEmJmyJmm5B4mr1406RWERqNDql
36sOw89G0mDT/wIB4tkNdh830ml+d75aRVVB4X5pFAE8ZzI3g4OW4YxT3ZfUEhDl
QeGVatobvIaX8KpNSevjFAFuQzSgj61VXI+2+UIRV4tJP2xEqu5ISuArHcGhvNlS
bU3vZ80tTCa0tHyJrVqaqtQ23MDBzYPj6wJ/pvBQWAgZKnC3qJgXlJ9des117I1g
J98AXCDGu5LBW/p2C9VpSktpnfzsX4NHqg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEApydikyWZ0MzrdLlH/uaCzXLwy551mC812aTjl+uugJZsMfj1
ZA8ZIhkntEAfUViah0q7D9a8hiHgiMxjDnLS7ekAOz3nnh3ayqjkIDettJw0ndhG
d7NCHXYdTbP5X07QSQj/9N7LmpLZu4/BGCgMjpUyFIYiprExZcT1Eio27UeMEdZY
5yjfF/o/4Ia/ZloUKzEfa7BhTm1PA0BYaNk4vdAuB6DDVHcuI/tht1VaWquIXuP+
Amz61kEN3Shqc1QJ4CLgL+HkP8vPBBwBcLrcUGVgHsOFWuctztddmnEm/aAu4NDR
0HAZ6LP1iAoReZIvFdyxZ8Te42L1FJPWMXwPIQIDAQABAoIBABp4z1C0dL6vpV5v
9Wn2AaMd3+qvZro6R9H3HiAyMAmnSO1FGz/EcFuJFlOikBMm8BobCLMCdAreFJw1
mj5wit0ouGOpcyQEYGEWDELZ7oWa825IESjl18OosA1dQlIIvk3Cwh56pk4NkbP1
mUQFG6/9CthbQeOaTlNqtNEypE5Bc+JGbQaUhRP6tF+Rxnpys2nIJt/Vp9khw0Du
K7Z6astunhfPDwLFGwHhflc6re1B+mxpLKTDHCcydJo2Kuh/LuuEtPkE5Ar4LwQk
D+/61iZHC4B8/4IkBlAsgCJ1B18L6JdTbSYeVlepkSkJML5t6z+cvt5VcObF7F8X
pPZn+kECgYEA2NaB0eshWNnHTMRv+sE92DCv0M7uV1eKtaopxOElAKJ/J2gpqcTh
GzdTVRg1M2LgVNk97ViL5bsXaVStRe085m8oA0bI9WbIoQRUFp40dRFRUjl+4TN0
pdxXL4VmQMWuwlO6p8/JY8sInnHVCT+2z8lek8P3bdtTQZV9OZQTn0kCgYEAxVe8
obJdnUSXuRDWg588TW35PNqOTJcerIU6eRKwafvCcrhMoX62Xbv6y6kKXndW/JuW
AbfSNiAOV+HGUbf8Xc54Xzk2mouoJA0S0tJ040jqOkFOaKIxYQudTU8y9bTXNsAk
oX3wOhlt2q9xffAK1gYffP5XPXnYnsb8qaMIeRkCgYBM9yaxOgJmJTbGmtscaEbp
W66sMScMPXhwruuQhFG7/fGgLSrMpaM5I9QiWitYB/qUY1/FxS4y5suSiYnPTjvV
lxLexttBr6/65yxpstHv06vHwby1dqwqyyDvLyxyRTiYpVuVgP18vG5cvw7c746W
BmXZkS9cAQN2Pfdq3pJwcQKBgEbCZd2owg5hCPIPyosZbpro4uRiDYIC8bm0b7n3
7I+j+R3/XWLOt382pv+dlh03N1aORyRIkDReHCaAywaELRZJsTmbnyudBeYfVe+I
DOduPqYywnWcKo58hqOw0Tnu5Pg5vyi0qo16jrxKCiy5BHmnamT8IbXmWbjc6r28
uo4JAoGAfAPvPJ2fV5vpzr4LPoVyaSiFj414D+5XYxX6CWpdTryelpP2Rs1VfJ1a
7EusUtWs26pAKwttDY4yoTvog7rrskgtXzisaoNMDbH/PfsoqjMnnIgakvKmHpUM
l6E1ecWFExEg5v6yvmxFC7JIUzIYOoysWu3X44G8rQ+vDQNRFZQ=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID/TCCAuWgAwIBAgIJAIAPVUXOUQDNMA0GCSqGSIb3DQEBCwUAMEsxIzAhBgNV
BAMTGkVsYXN0aWNzZWFyY2ggVGVzdCBSb290IENBMRYwFAYDVQQLEw1lbGFzdGlj
c2VhcmNoMQwwCgYDVQQKEwNvcmcwHhcNMTkwNzE5MTMzMjIwWhcNMjMwNzE4MTMz
MjIwWjBLMSMwIQYDVQQDExpFbGFzdGljc2VhcmNoIFRlc3QgUm9vdCBDQTEWMBQG
A1UECxMNZWxhc3RpY3NlYXJjaDEMMAoGA1UEChMDb3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAzIgn8r2kirt90id0uoi6YEGBPx+XDzthLbLsN+M0
nXhj40OVcGPiww+cre14bJr0M6MG4CvFjRJc92RoVrE8+7XOKt0bgiHeVM+b0LEh
wVMH9koararPVMo0CjCMN4ChHMOWKBPUNZswvk+pFC+QbTcfgQLycqh+lTB1O6l3
hPnmunEqhLIj9ke3FwA326igdb+16EbKYVL2c5unNoC5ZMc5Z9bnn4/GNXptkHhy
+SvG7IZKW2pAzei3Df/n47ZhJfQKERUCe9eO7b/ZmTEzAzYj9xucE5lYcpkOZd6g
IMU3vXe4FeD/BM4sOLkKTtMejiElEecxw8cLI9Nji/0y1wIDAQABo4HjMIHgMB0G
A1UdDgQWBBTOEsjc6c/NooUNetM6y1FWlnyPtTAfBgNVHSMEGDAWgBTOEsjc6c/N
ooUNetM6y1FWlnyPtTAMBgNVHRMEBTADAQH/MIGPBgNVHREEgYcwgYSCCWxvY2Fs
aG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghdsb2NhbGhv
c3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5sb2NhbGRv
bWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEB
ACHjwoDJILv77sQ5QN6SoAp6GYqiC9/doDIzDFCd/WP7G8EbaosHM6jM7NbrlK3g
PNTzuY1pLPoI3YJSO4Al/UfzEffaYSbZC2QZG9F6fUSWhvR+nxzPSXWkjzIInv1j
pPMgnUl6oJaUbsSR/evtvWNSxrM3LewkRTOoktkXM6SjTUHjdP6ikrkrarrWZgzr
K30BqGL6kDSv9LkyXe6RSgQDtQe51Yut+lKGCcy8AoEwG/3cjb7XnrWcFsJXjYbf
4m3QsS8yHU/O/xgyvVHOfki+uGVepzSjdzDMLE1GBkju05NR2eJZ8omj/QiJa0+z
1d/AOKExvWvo1yQ28ORcwo4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzIgn8r2kirt90id0uoi6YEGBPx+XDzthLbLsN+M0nXhj40OV
cGPiww+cre14bJr0M6MG4CvFjRJc92RoVrE8+7XOKt0bgiHeVM+b0LEhwVMH9koa
rarPVMo0CjCMN4ChHMOWKBPUNZswvk+pFC+QbTcfgQLycqh+lTB1O6l3hPnmunEq
hLIj9ke3FwA326igdb+16EbKYVL2c5unNoC5ZMc5Z9bnn4/GNXptkHhy+SvG7IZK
W2pAzei3Df/n47ZhJfQKERUCe9eO7b/ZmTEzAzYj9xucE5lYcpkOZd6gIMU3vXe4
FeD/BM4sOLkKTtMejiElEecxw8cLI9Nji/0y1wIDAQABAoIBAQC6LMnoPFW1brs1
+3JWhTTZf2btlYzEcbGgjnhU2v0+xaJu8UrrFhEIq4JcE4gFm/rjsecFUPKu2eND
0eLj3st699+lxsRObRPbMWtMyJ/IQRNDTesA4DV/odtC1zQbJXwCGcrpyjrlXNE+
unZWiIE32PBVV+BnHBa1KHneCAFiSRLrySAiDAnTIJxB6ufweoxevLoJPPNLlbo7
H2jv6g1Som/Imjhof4KhD/1Q04Sed2wScSS/7Bz38eO68HG4NMFY+M2/cLzrbflg
QdeKHNhoIGnSFMEW5TCVlI4qrP8zvPPdZmLOMBT+Ocm3pc5xDAPwFYCe8wH1DVn+
b3sVpwu5AoGBAOhFA7gUDZjRBkNAqJfbUdhdWSslePQsjeTKsu5rc4gk2aiL4bZ4
fxG0Dq1hX7FjAmYrGqnsXsbxxDnCkhXGH1lY73kF0Zzwr2Pg1yRHyn1nCinhD4g4
G2vBr37QtWn4wS/L7V//D3xrcCTG3QgAmvZZ99tYgqlmnUzmawdZ8kQ7AoGBAOFt
qg7sTSNWVpKkfkyX2NXvBMt5e3Qcwnge2pX+SBgljwjNUwSSMLwxdBDSyDXIhk8W
s4pJLtMDJsT/2WBKC9WJm9m3gc7yYZznLJ+5YPcieXHGGNXCRldPePhTIjnL591H
CSXoc3BZ2iKK745BYuPqSuLb2XfE3/hwoaFR4S4VAoGAQ6ywG7dECu2ELJ4vQSe2
3hq8u1SMvGAq66mfntYR8G4EORagqkDLjUXwLNY9Qnr9nPUcLLxhFQgmS0oEtHFo
eujtxU5Lt7Vs9OXy6XA9cHJQRMl9dAwc+TWSw5ld8kV3TEzXmevAAFlxcFW82vMK
M5MdI3zTfTYXyOst7hNoAjcCgYAhz/cgAeWYFU0q9a1UA7qsbAuGEZSo1997cPVM
ZjWeGZQYt+Np3hudPrWwCE2rc4Zhun/3j/6L+/8GsXGDddfMkbVktJet2ME3bZ1N
39phdzRMEnCLL3aphewZIy8RCDqhABSpMPKPuYp0f+5qofgZQ300BdHamxcVBp/X
uJZT+QKBgQDdJQd+QxfCb8BZ11fWtyWJWQWZMmyX2EEbAIMvYQP3xh8PHmw2JoiQ
VQ103bCkegJ1S7ubrGltdt8pyjN4rrByXJmxCe1Y/LSHIp9w8D3jaiLCRSk1EmBw
jXjnZoiJn3GV5jmbV10hzrn7jqRcwhYA5zuoE7qb604V7cPZLzHtog==
-----END RSA PRIVATE KEY-----

View File

@ -170,10 +170,11 @@ public final class RequestOptions {
/**
* Add the provided header to the request.
*/
public void addHeader(String name, String value) {
public Builder addHeader(String name, String value) {
Objects.requireNonNull(name, "header name cannot be null");
Objects.requireNonNull(value, "header value cannot be null");
this.headers.add(new ReqHeader(name, value));
return this;
}
/**

View File

@ -0,0 +1,62 @@
--
:api: delegate-pki
:request: DelegatePkiAuthenticationRequest
:response: DelegatePkiAuthenticationResponse
--
[id="{upid}-{api}"]
=== Delegate PKI Authentication API
This API is called by *smart* proxies to Elasticsearch, such as Kibana, that
terminate the user's TLS session but that still wish to authenticate the user
on the Elasticsearch side using a PKI realm, which normally requires users to
authenticate over TLS directly to Elasticsearch. It implements the exchange of
the client's {@code X509Certificate} chain from the TLS authentication into an
Elasticsearch access token.
IMPORTANT: The association between the subject public key in the target
certificate and the corresponding private key is *not* validated. This is part
of the TLS authentication process and it is delegated to the proxy calling this
API. The proxy is *trusted* to have performed the TLS authentication, and this
API translates that authentication into an Elasticsearch access token.
[id="{upid}-{api}-request"]
==== Delegate PKI Authentication Request
The request contains the client's {@code X509Certificate} chain. The
certificate chain is represented as a list where the first element is the
target certificate containing the subject distinguished name that is requesting
access. This may be followed by additional certificates, with each subsequent
certificate being the one used to certify the previous one. The certificate
chain is validated according to RFC 5280, by sequentially considering the trust
configuration of every installed {@code PkiRealm} that has {@code
PkiRealmSettings#DELEGATION_ENABLED_SETTING} set to {@code true} (default is
{@code false}). A successfully trusted target certificate is also subject to
the validation of the subject distinguished name according to that respective's
realm {@code PkiRealmSettings#USERNAME_PATTERN_SETTING}.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[delegate-pki-request]
--------------------------------------------------
include::../execution.asciidoc[]
[id="{upid}-{api}-response"]
==== Delegate PKI Authentication Response
The returned +{response}+ contains the following properties:
`accessToken`:: This is the newly created access token.
It can be used to authenticate to the Elasticsearch cluster.
`type`:: The type of the token, this is always `"Bearer"`.
`expiresIn`:: The length of time (in seconds) until the token will expire.
The token will be considered invalid after that time.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests}/SecurityDocumentationIT.java[delegate-pki-response]
--------------------------------------------------
<1> The `accessToken` can be used to authentication to Elasticsearch.

View File

@ -857,6 +857,17 @@ Defaults to `20m`.
Specifies the maximum number of user entries that the cache can contain.
Defaults to `100000`.
`delegation.enabled`::
Generally, in order for the clients to be authenticated by the PKI realm they
must connect directly to {es}. That is, they must not pass through proxies
which terminate the TLS connection. In order to allow for a *trusted* and
*smart* proxy, such as Kibana, to sit before {es} and terminate TLS
connections, but still allow clients to be authenticated on {es} by this realm,
you need to toggle this to `true`. Defaults to `false`. If delegation is
enabled, then either `truststore.path` or `certificate_authorities` setting
must be defined. For more details, see <<pki-realm-for-proxied-clients,
Configuring authentication delegation for PKI realms>>.
[[ref-saml-settings]]
[float]
===== SAML realm settings

View File

@ -31,6 +31,7 @@ project.copyRestSpec.from(xpackResources) {
testClusters.integTest {
extraConfigFile 'op-jwks.json', xpackProject('test:idp-fixture').file("oidc/op-jwks.json")
extraConfigFile 'testClient.crt', xpackProject('plugin:security').file("src/test/resources/org/elasticsearch/xpack/security/action/pki_delegation/testClient.crt")
setting 'xpack.security.enabled', 'true'
setting 'xpack.security.authc.api_key.enabled', 'true'
setting 'xpack.security.authc.token.enabled', 'true'
@ -50,6 +51,9 @@ testClusters.integTest {
keystore 'xpack.security.authc.realms.oidc.oidc1.rp.client_secret', 'b07efb7a1cf6ec9462afe7b6d3ab55c6c7880262aa61ac28dded292aca47c9a2'
setting 'xpack.security.authc.realms.oidc.oidc1.rp.response_type', 'id_token'
setting 'xpack.security.authc.realms.oidc.oidc1.claims.principal', 'sub'
setting 'xpack.security.authc.realms.pki.pki1.order', '3'
setting 'xpack.security.authc.realms.pki.pki1.certificate_authorities', '[ "testClient.crt" ]'
setting 'xpack.security.authc.realms.pki.pki1.delegation.enabled', 'true'
user username: 'test_admin'
}

View File

@ -6,6 +6,7 @@ You can use the following APIs to perform security activities.
* <<security-api-authenticate>>
* <<security-api-clear-cache>>
* <<security-api-delegate-pki-authentication>>
* <<security-api-has-privileges>>
* <<security-api-ssl>>
* <<security-api-get-builtin-privileges>>
@ -98,6 +99,7 @@ include::security/put-app-privileges.asciidoc[]
include::security/create-role-mappings.asciidoc[]
include::security/create-roles.asciidoc[]
include::security/create-users.asciidoc[]
include::security/delegate-pki-authentication.asciidoc[]
include::security/delete-app-privileges.asciidoc[]
include::security/delete-role-mappings.asciidoc[]
include::security/delete-roles.asciidoc[]

View File

@ -0,0 +1,96 @@
[role="xpack"]
[[security-api-delegate-pki-authentication]]
=== Delegate PKI authentication API
++++
<titleabbrev>Delegate PKI authentication</titleabbrev>
++++
Implements the exchange of an {@code X509Certificate} chain into an {es} access
token.
[[security-api-delegate-pki-authentication-request]]
==== {api-request-title}
`POST /_security/delegate_pki`
[[security-api-delegate-pki-authentication-prereqs]]
==== {api-prereq-title}
* To call this API, the (proxy) user must have the `delegate_pki` or the `all`
cluster privilege. The `kibana_system` built-in role already grants this
privilege. See {stack-ov}/security-privileges.html[Security privileges].
[[security-api-delegate-pki-authentication-desc]]
==== {api-description-title}
This API implements the exchange of an _X509Certificate_ chain for an {es}
access token. The certificate chain is validated, according to RFC 5280, by
sequentially considering the trust configuration of every installed PKI realm
that has `delegation.enabled` set to `true` (default is `false`). A
successfully trusted client certificate is also subject to the validation of
the subject distinguished name according to that respective's realm
`username_pattern`.
This API is called by *smart* and *trusted* proxies, such as {kib}, which
terminate the user's TLS session but still want to authenticate the user
by using a PKI realm--as if the user connected directly to {es}. For more
details, see <<pki-realm-for-proxied-clients>>.
IMPORTANT: The association between the subject public key in the target
certificate and the corresponding private key is *not* validated. This is part
of the TLS authentication process and it is delegated to the proxy that calls
this API. The proxy is *trusted* to have performed the TLS authentication and
this API translates that authentication into an {es} access token.
[[security-api-delegate-pki-authentication-request-body]]
==== {api-request-body-title}
`x509_certificate_chain`::
(Required, list of strings) The _X509Certificate_ chain, which is represented as
an ordered string array. Each string in the array is a base64-encoded
(Section 4 of RFC4648 - not base64url-encoded) of the certificate's DER encoding.
+
The first element is the target certificate contains the subject distinguished
name that is requesting access. This may be followed by additional certificates;
each subsequent certificate is used to certify the previous one.
[[security-api-delegate-pki-authentication-response-body]]
==== {api-response-body-title}
`access_token`::
(string) An access token associated to the subject distinguished name of the
client's certificate.
`expires_in`::
(time units) The amount of time (in seconds) that the token expires in.
`type`::
(string) The type of token.
[[security-api-delegate-pki-authentication-example]]
==== {api-examples-title}
The following is an example request:
[source, js]
------------------------------------------------------------
POST /_security/delegate_pki
{
"x509_certificate_chain": ["MIIDbTCCAlWgAwIBAgIJAIxTS7Qdho9jMA0GCSqGSIb3DQEBCwUAMFMxKzApBgNVBAMTIkVsYXN0aWNzZWFyY2ggVGVzdCBJbnRlcm1lZGlhdGUgQ0ExFjAUBgNVBAsTDUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzAeFw0xOTA3MTkxMzMzNDFaFw0yMzA3MTgxMzMzNDFaMEoxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGllbnQxFjAUBgNVBAsTDUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANHgMX2aX8t0nj4sGLNuKISmmXIYCj9RwRqS7L03l9Nng7kOKnhHu/nXDt7zMRJyHj+q6FAt5khlavYSVCQyrDybRuA5z31gOdqXerrjs2OXS5HSHNvoDAnHFsaYX/5geMewVTtc/vqpd7Ph/QtaKfmG2FK0JNQo0k24tcgCIcyMtBh6BA70yGBM0OT8GdOgd/d/mA7mRhaxIUMNYQzRYRsp4hMnnWoOTkR5Q8KSO3MKw9dPSpPe8EnwtJE10S3s5aXmgytru/xQqrFycPBNj4KbKVmqMP0G60CzXik5pr2LNvOFz3Qb6sYJtqeZF+JKgGWdaTC89m63+TEnUHqk0lcCAwEAAaNNMEswCQYDVR0TBAIwADAdBgNVHQ4EFgQU/+aAD6Q4mFq1vpHorC25/OY5zjcwHwYDVR0jBBgwFoAU8siFCiMiYZZm/95qFC75AG/LRE0wDQYJKoZIhvcNAQELBQADggEBAIRpCgDLpvXcgDHUk10uhxev21mlIbU+VP46ANnCuj0UELhTrdTuWvO1PAI4z+WbDUxryQfOOXO9R6D0dE5yR56L/J7d+KayW34zU7yRDZM7+rXpocdQ1Ex8mjP9HJ/Bf56YZTBQJpXeDrKow4FvtkI3bcIMkqmbG16LHQXeG3RS4ds4S4wCnE2nA6vIn9y+4R999q6y1VSBORrYULcDWxS54plHLEdiMr1vVallg82AGobS9GMcTL2U4Nx5IYZG7sbTk3LrDxVpVg/S2wLofEdOEwqCeHug/iOihNLJBabEW6z4TDLJAVW5KCY1DfhkYlBfHn7vxKkfKoCUK/yLWWI="] <1>
}
------------------------------------------------------------
// CONSOLE
<1> A one element certificate chain.
Which returns the following response:
[source,js]
--------------------------------------------------
{
"access_token" : "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==",
"type" : "Bearer",
"expires_in" : 1200
}
--------------------------------------------------
// TESTRESPONSE[s/dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==/$body.access_token/]

View File

@ -66,6 +66,7 @@ A successful call returns an object with "cluster" and "index" fields.
"cluster" : [
"all",
"create_snapshot",
"delegate_pki",
"manage",
"manage_api_key",
"manage_ccr",

View File

@ -2,15 +2,29 @@
[[configuring-pki-realm]]
=== Configuring a PKI realm
You can configure {es} to use Public Key Infrastructure (PKI) certificates
to authenticate users. This requires clients to present X.509 certificates.
You can configure {es} to use Public Key Infrastructure (PKI) certificates to
authenticate users. This requires clients connecting directly to {es} to
present X.509 certificates. The certificates must first be accepted for
authentication on the SSL/TLS layer on {es}. Only then they are optionally
further validated by a PKI realm.
NOTE: You cannot use PKI certificates to authenticate users in {kib}.
Users may also use PKI certificates to authenticate to {kib}, however this
requires some <<pki-realm-for-proxied-clients,additional configuration>>. On
{es}, this configuration enables {kib} to act as a proxy for SSL/TLS
authentication and to submit the client certificates to {es} for further
validation by a PKI realm.
For more general information, see {stack-ov}/pki-realm.html[PKI user authentication].
[float]
[role="xpack"]
[[pki-realm-for-direct-clients]]
==== PKI authentication for clients connecting directly to {es}
To use PKI in {es}, you configure a PKI realm, enable client authentication on
the desired network layers (transport or http), and map the Distinguished Names
(DNs) from the user certificates to roles in the
<<security-api-role-mapping,role-mapping API>> or role-mapping file.
the desired network layers (transport or http), and map the Distinguished Name
(DN) from the Subject field in the user certificate to roles by using the
<<security-api-role-mapping,role-mapping API>> or the role-mapping file.
You can also use a combination of PKI and username/password authentication. For
example, you can enable SSL/TLS on the transport layer and define a PKI realm to
@ -19,9 +33,8 @@ authenticating HTTP traffic using username and password credentials. You can
also set `xpack.security.transport.ssl.client_authentication` to `optional` to
allow clients without certificates to authenticate with other credentials.
IMPORTANT: You must enable SSL/TLS and enable client authentication to use PKI.
For more information, see {stack-ov}/pki-realm.html[PKI User Authentication].
IMPORTANT: You must enable SSL/TLS with client authentication to use PKI when
clients connect directly to {es}.
. Add a realm configuration for a `pki` realm to `elasticsearch.yml` under the
`xpack.security.authc.realms.pki` namespace.
@ -43,17 +56,19 @@ xpack:
order: 1
------------------------------------------------------------
With this configuration, any certificate trusted by the SSL/TLS layer is accepted
for authentication. The username is the common name (CN) extracted from the DN
of the certificate.
With this configuration, any certificate trusted by the {es} SSL/TLS layer is
accepted for authentication. The username is the common name (CN) extracted
from the DN in the Subject field of the end-entity certificate. This
configuration does not permit PKI authentication to {kib}.
IMPORTANT: When you configure realms in `elasticsearch.yml`, only the
realms you specify are used for authentication. If you also want to use the
`native` or `file` realms, you must include them in the realm chain.
If you want to use something other than the CN of the DN as the username, you
can specify a regex to extract the desired username. For example, the regex in
the following configuration extracts the email address from the DN:
If you want to use something other than the CN of the Subject DN as the
username, you can specify a regex to extract the desired username. The regex is
applied on the Subject DN. For example, the regex in the following
configuration extracts the email address from the Subject DN:
[source, yaml]
------------------------------------------------------------
@ -65,9 +80,15 @@ xpack:
pki1:
username_pattern: "EMAILADDRESS=(.*?)(?:,|$)"
------------------------------------------------------------
NOTE: If the regex is too restrictive and does not match the Subject DN of the
client's certificate, then the realm does not authenticate the certificate.
--
. Restart {es}.
. Restart {es} because realm configuration is not reloaded automatically. If
you're following through with the next steps, you might wish to hold the
restart for last.
. <<configuring-tls,Enable SSL/TLS>>.
@ -75,13 +96,13 @@ xpack:
+
--
The PKI realm relies on the TLS settings of the node's network interface. The
realm can be configured to be more restrictive than the underlying network
connection - that is, it is possible to configure the node such that some
connections are accepted by the network interface but then fail to be
authenticated by the PKI realm. However, the reverse is not possible. The PKI
realm cannot authenticate a connection that has been refused by the network
interface.
When clients connect directly to {es} and are not proxy-authenticated, the PKI
realm relies on the TLS settings of the node's network interface. The realm can
be configured to be more restrictive than the underlying network connection.
That is, it is possible to configure the node such that some connections
are accepted by the network interface but then fail to be authenticated by the
PKI realm. However, the reverse is not possible. The PKI realm cannot
authenticate a connection that has been refused by the network interface.
In particular this means:
@ -96,14 +117,15 @@ In particular this means:
used by the client.
The relevant network interface (transport or http) must be configured to trust
any certificate that is to be used within the PKI realm. However, it possible to
any certificate that is to be used within the PKI realm. However, it is possible to
configure the PKI realm to trust only a _subset_ of the certificates accepted
by the network interface. This is useful when the SSL/TLS layer trusts clients
with certificates that are signed by a different CA than the one that signs your
users' certificates.
To configure the PKI realm with its own truststore, specify the `truststore.path`
option. For example:
To configure the PKI realm with its own truststore, specify the
`truststore.path` option. The path must be located within the Elasticsearch
configuration directory (ES_PATH_CONF). For example:
[source, yaml]
------------------------------------------------------------
@ -114,22 +136,33 @@ xpack:
pki:
pki1:
truststore:
path: "/path/to/pki_truststore.jks"
password: "x-pack-test-password"
path: "pki1_truststore.jks"
------------------------------------------------------------
If the truststore is password protected, the password should be configured by
adding the appropriate `secure_password` setting to the {es} keystore. For
example, the following command adds the password for the example realm above:
[source, shell]
------------------------------------------------------------
bin/elasticsearch-keystore add \
xpack.security.authc.realms.pki.pki1.truststore.secure_password
------------------------------------------------------------
The `certificate_authorities` option can be used as an alternative to the
`truststore.path` setting.
`truststore.path` setting, when the certificate files are PEM formatted
. The setting accepts a list. The two options are exclusive, they cannot be both used
simultaneously.
--
. Map roles for PKI users.
+
--
You map roles for PKI users through the
<<security-role-mapping-apis,role mapping APIs>> or by using a file stored on
each node. When a user authenticates against a PKI realm, the privileges for
that user are the union of all privileges defined by the roles to which the
user is mapped.
You map roles for PKI users through the <<security-role-mapping-apis,role
mapping APIs>> or by using a file stored on each node. Both configuration
options are merged together. When a user authenticates against a PKI realm, the
privileges for that user are the union of all privileges defined by the roles
to which the user is mapped.
You identify a user by the distinguished name in their certificate.
For example, the following mapping configuration maps `John Doe` to the
@ -150,7 +183,11 @@ PUT /_security/role_mapping/users
// CONSOLE
<1> The distinguished name (DN) of a PKI user.
Or, alternatively, configured in a role-mapping file:
Or, alternatively, configured inside a role-mapping file. The file's path
defaults to `ES_PATH_CONF/role_mapping.yml`. You can specify a different path (which must be within
ES_PATH_CONF) by using the `files.role_mapping` realm setting (e.g.
`xpack.security.authc.realms.pki.pki1.files.role_mapping`):
[source, yaml]
------------------------------------------------------------
user: <1>
@ -163,7 +200,7 @@ The distinguished name for a PKI user follows X.500 naming conventions which
place the most specific fields (like `cn` or `uid`) at the beginning of the
name, and the most general fields (like `o` or `dc`) at the end of the name.
Some tools, such as _openssl_, may print out the subject name in a different
format.
format.
One way that you can determine the correct DN for a certificate is to use the
<<security-api-authenticate,authenticate API>> (use the relevant PKI
@ -179,3 +216,76 @@ NOTE: The PKI realm supports
alternative to role mapping.
--
[float]
[role="xpack"]
[[pki-realm-for-proxied-clients]]
==== PKI authentication for clients connecting to {kib}
By default, the PKI realm relies on the node's network interface to perform the
SSL/TLS handshake and extract the client certificate. This behaviour requires
that that clients connect directly to {es} so that their SSL connection is
terminated by the {es} node. If SSL/TLS authenticatication is to be performed
by {kib}, the PKI realm must be configured to permit delegation.
Specifically, when clients presenting X.509 certificates connect to {kib},
{kib} performs the SSL/TLS authentication. {kib} then forwards the client's
certificate chain, by calling an {es} API, to have them further validated by
the PKI realms that have been configured for delegation.
To permit authentication delegation for a specific {es} PKI realm, start by
configuring the realm for the usual case, as detailed in the
<<pki-realm-for-direct-clients>>
section. Note that you must explicitly configure a `truststore` (or,
equivalently `certificate_authorities`) even though it is the same trust
configuration that you have configured on the network layer. Afterwards,
simply toggle the `delegation.enabled` realm setting to `true`. This realm is
now allowed to validate delegated PKI authentication (after restarting {es}).
NOTE: PKI authentication delegation requires that the
`xpack.security.authc.token.enabled` setting be `true` and that SSL/TLS be
configured (without SSL/TLS client authentication).
NOTE: {kib} also needs to be configured to allow PKI certificate authentication.
A PKI realm with `delegation.enabled` still works unchanged for clients
connecting directly to {es}. Directly authenticated users, and users that are PKI
authenticated by delegation to {kib} both follow the same
{stack-ov}/mapping-roles.html[role mapping rules] or
{stack-ov}/realm-chains.html#authorization_realms[authorization realms
configurations].
However, if you use the <<security-role-mapping-apis,role mapping APIs>>,
you can distinguish between users that are authenticated by delegation and
users that are authenticated directly. The former have the
extra fields `pki_delegated_by_user` and `pki_delegated_by_realm` in the user's
metadata. In the common setup, where authentication is delegated to {kib}, the
values of these fields are `kibana` and `reserved`, respectively. For example,
the following role mapping rule will assign the `role_for_pki1_direct` role to
all users that have been authenticated directly by the `pki1` realm, by
connecting to {es} instead of going through {kib}:
[source,js]
--------------------------------------------------
PUT /_security/role_mapping/direct_pki_only
{
"roles" : [ "role_for_pki1_direct" ],
"rules" : {
"all": [
{
"field": {"realm.name": "pki1"}
},
{
"field": {
"metadata.pki_delegated_by_user": null <1>
}
}
]
},
"enabled": true
}
--------------------------------------------------
// CONSOLE
<1> only when this metadata field is set (it is *not* `null`) the user has been
authenticated in the delegation scenario.

View File

@ -28,6 +28,11 @@ you are able to map users to both API-managed roles and file-managed roles
NOTE: The PKI, LDAP, Kerberos and SAML realms support using
<<authorization_realms, authorization realms>> as an alternative to role mapping.
NOTE: When <<anonymous-access, anonymous access>> is enabled, the roles
of the anonymous user are assigned to all the other users as well.
NOTE: Users with no roles assigned will be unauthorized for any action.
[[mapping-roles-api]]
==== Using the role mapping API

View File

@ -49,6 +49,7 @@ dependencies {
testCompile project(path: ':modules:parent-join', configuration: 'runtime')
testCompile project(path: ':modules:lang-mustache', configuration: 'runtime')
testCompile project(path: ':modules:analysis-common', configuration: 'runtime')
testCompile project(':client:rest-high-level')
testCompile(project(':x-pack:license-tools')) {
transitive = false
}

View File

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.action.ActionType;
/**
* ActionType for delegating PKI authentication
*/
public class DelegatePkiAuthenticationAction extends ActionType<DelegatePkiAuthenticationResponse> {
public static final String NAME = "cluster:admin/xpack/security/delegate_pki";
public static final DelegatePkiAuthenticationAction INSTANCE = new DelegatePkiAuthenticationAction();
private DelegatePkiAuthenticationAction() {
super(NAME, DelegatePkiAuthenticationResponse::new);
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* The request object for {@code TransportDelegatePkiAuthenticationAction} containing the certificate chain for the target subject
* distinguished name to be granted an access token.
*/
public final class DelegatePkiAuthenticationRequest extends ActionRequest implements ToXContentObject {
private static final ParseField X509_CERTIFICATE_CHAIN_FIELD = new ParseField("x509_certificate_chain");
public static final ConstructingObjectParser<DelegatePkiAuthenticationRequest, Void> PARSER = new ConstructingObjectParser<>(
"delegate_pki_request", false, a -> {
@SuppressWarnings("unchecked")
List<X509Certificate> certificates = (List<X509Certificate>) a[0];
return new DelegatePkiAuthenticationRequest(certificates);
});
static {
PARSER.declareFieldArray(optionalConstructorArg(), (parser,c) -> {
try (ByteArrayInputStream bis = new ByteArrayInputStream(Base64.getDecoder().decode(parser.text()))) {
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(bis);
} catch (CertificateException | IOException e) {
throw new RuntimeException(e);
}
}, X509_CERTIFICATE_CHAIN_FIELD, ValueType.STRING_ARRAY);
}
public static DelegatePkiAuthenticationRequest fromXContent(XContentParser parser) throws IOException {
return PARSER.apply(parser, null);
}
private List<X509Certificate> certificateChain;
public DelegatePkiAuthenticationRequest(List<X509Certificate> certificateChain) {
this.certificateChain = Collections.unmodifiableList(certificateChain);
}
public DelegatePkiAuthenticationRequest(StreamInput input) throws IOException {
super(input);
try {
final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
certificateChain = Collections.unmodifiableList(input.readList(in -> {
try (ByteArrayInputStream bis = new ByteArrayInputStream(in.readByteArray())) {
return (X509Certificate) certificateFactory.generateCertificate(bis);
} catch (CertificateException e) {
throw new IOException(e);
}
}));
} catch (CertificateException e) {
throw new IOException(e);
}
}
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (certificateChain.isEmpty()) {
validationException = addValidationError("certificates chain must not be empty", validationException);
} else if (false == CertParsingUtils.isOrderedCertificateChain(certificateChain)) {
validationException = addValidationError("certificates chain must be an ordered chain", validationException);
}
return validationException;
}
public List<X509Certificate> getCertificateChain() {
return certificateChain;
}
@Override
public void writeTo(StreamOutput output) throws IOException {
super.writeTo(output);
output.writeCollection(certificateChain, (out, cert) -> {
try {
out.writeByteArray(cert.getEncoded());
} catch (CertificateEncodingException e) {
throw new IOException(e);
}
});
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DelegatePkiAuthenticationRequest that = (DelegatePkiAuthenticationRequest) o;
return Objects.equals(certificateChain, that.certificateChain);
}
@Override
public int hashCode() {
return Objects.hashCode(certificateChain);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject().startArray(X509_CERTIFICATE_CHAIN_FIELD.getPreferredName());
try {
for (X509Certificate cert : certificateChain) {
builder.value(Base64.getEncoder().encodeToString(cert.getEncoded()));
}
} catch (CertificateEncodingException e) {
throw new IOException(e);
}
return builder.endArray().endObject();
}
}

View File

@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Objects;
/**
* The response object for {@code TransportDelegatePkiAuthenticationAction} containing the issued access token.
*/
public final class DelegatePkiAuthenticationResponse extends ActionResponse implements ToXContentObject {
private static final ParseField ACCESS_TOKEN_FIELD = new ParseField("access_token");
private static final ParseField TYPE_FIELD = new ParseField("type");
private static final ParseField EXPIRES_IN_FIELD = new ParseField("expires_in");
public static final ConstructingObjectParser<DelegatePkiAuthenticationResponse, Void> PARSER = new ConstructingObjectParser<>(
"delegate_pki_response", true, a -> {
final String accessToken = (String) a[0];
final String type = (String) a[1];
if (false == "Bearer".equals(type)) {
throw new IllegalArgumentException("Unknown token type [" + type + "], only [Bearer] type permitted");
}
final Long expiresIn = (Long) a[2];
return new DelegatePkiAuthenticationResponse(accessToken, TimeValue.timeValueSeconds(expiresIn));
});
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), ACCESS_TOKEN_FIELD);
PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD);
PARSER.declareLong(ConstructingObjectParser.constructorArg(), EXPIRES_IN_FIELD);
}
private String accessToken;
private TimeValue expiresIn;
DelegatePkiAuthenticationResponse() { }
public DelegatePkiAuthenticationResponse(String accessToken, TimeValue expiresIn) {
this.accessToken = Objects.requireNonNull(accessToken);
// always store expiration in seconds because this is how we "serialize" to JSON and we need to parse back
this.expiresIn = TimeValue.timeValueSeconds(Objects.requireNonNull(expiresIn).getSeconds());
}
public DelegatePkiAuthenticationResponse(StreamInput input) throws IOException {
super(input);
accessToken = input.readString();
expiresIn = input.readTimeValue();
}
public String getAccessToken() {
return accessToken;
}
public TimeValue getExpiresIn() {
return expiresIn;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(accessToken);
out.writeTimeValue(expiresIn);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DelegatePkiAuthenticationResponse that = (DelegatePkiAuthenticationResponse) o;
return Objects.equals(accessToken, that.accessToken) &&
Objects.equals(expiresIn, that.expiresIn);
}
@Override
public int hashCode() {
return Objects.hash(accessToken, expiresIn);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject()
.field(ACCESS_TOKEN_FIELD.getPreferredName(), accessToken)
.field(TYPE_FIELD.getPreferredName(), "Bearer")
.field(EXPIRES_IN_FIELD.getPreferredName(), expiresIn.getSeconds());
return builder.endObject();
}
}

View File

@ -37,6 +37,10 @@ public final class PkiRealmSettings {
RealmSettings.realmSettingPrefix(TYPE), "cache.max_users",
key -> Setting.intSetting(key, DEFAULT_MAX_USERS, Setting.Property.NodeScope));
public static final Setting.AffixSetting<Boolean> DELEGATION_ENABLED_SETTING = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "delegation.enabled",
key -> Setting.boolSetting(key, false, Setting.Property.NodeScope));
public static final Setting.AffixSetting<Optional<String>> TRUST_STORE_PATH;
public static final Setting.AffixSetting<Optional<String>> TRUST_STORE_TYPE;
public static final Setting.AffixSetting<SecureString> TRUST_STORE_PASSWORD;
@ -72,6 +76,7 @@ public final class PkiRealmSettings {
settings.add(USERNAME_PATTERN_SETTING);
settings.add(CACHE_TTL_SETTING);
settings.add(CACHE_MAX_USERS_SETTING);
settings.add(DELEGATION_ENABLED_SETTING);
settings.add(TRUST_STORE_PATH);
settings.add(TRUST_STORE_PASSWORD);

View File

@ -19,6 +19,7 @@ import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
import org.elasticsearch.xpack.core.ilm.action.GetStatusAction;
import org.elasticsearch.xpack.core.ilm.action.StartILMAction;
import org.elasticsearch.xpack.core.ilm.action.StopILMAction;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction;
import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction;
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
@ -37,7 +38,7 @@ import java.util.stream.Stream;
*/
public class ClusterPrivilegeResolver {
// shared automatons
private static final Set<String> MANAGE_SECURITY_PATTERN = Collections.singleton("cluster:admin/xpack/security/*");
private static final Set<String> ALL_SECURITY_PATTERN = Collections.singleton("cluster:admin/xpack/security/*");
private static final Set<String> MANAGE_SAML_PATTERN = Collections.unmodifiableSet(
Sets.newHashSet("cluster:admin/xpack/security/saml/*",
InvalidateTokenAction.NAME, RefreshTokenAction.NAME));
@ -86,8 +87,7 @@ public class ClusterPrivilegeResolver {
new ActionClusterPrivilege("monitor_data_frame_transforms", MONITOR_DATA_FRAME_PATTERN);
public static final NamedClusterPrivilege MONITOR_WATCHER = new ActionClusterPrivilege("monitor_watcher", MONITOR_WATCHER_PATTERN);
public static final NamedClusterPrivilege MONITOR_ROLLUP = new ActionClusterPrivilege("monitor_rollup", MONITOR_ROLLUP_PATTERN);
public static final NamedClusterPrivilege MANAGE = new ActionClusterPrivilege("manage",
ALL_CLUSTER_PATTERN, MANAGE_SECURITY_PATTERN);
public static final NamedClusterPrivilege MANAGE = new ActionClusterPrivilege("manage", ALL_CLUSTER_PATTERN, ALL_SECURITY_PATTERN);
public static final NamedClusterPrivilege MANAGE_ML = new ActionClusterPrivilege("manage_ml", MANAGE_ML_PATTERN);
public static final NamedClusterPrivilege MANAGE_DATA_FRAME =
new ActionClusterPrivilege("manage_data_frame_transforms", MANAGE_DATA_FRAME_PATTERN);
@ -100,7 +100,8 @@ public class ClusterPrivilegeResolver {
new ActionClusterPrivilege("manage_ingest_pipelines", MANAGE_INGEST_PIPELINE_PATTERN);
public static final NamedClusterPrivilege TRANSPORT_CLIENT = new ActionClusterPrivilege("transport_client",
TRANSPORT_CLIENT_PATTERN);
public static final NamedClusterPrivilege MANAGE_SECURITY = new ActionClusterPrivilege("manage_security", MANAGE_SECURITY_PATTERN);
public static final NamedClusterPrivilege MANAGE_SECURITY = new ActionClusterPrivilege("manage_security", ALL_SECURITY_PATTERN,
Collections.singleton(DelegatePkiAuthenticationAction.NAME));
public static final NamedClusterPrivilege MANAGE_SAML = new ActionClusterPrivilege("manage_saml", MANAGE_SAML_PATTERN);
public static final NamedClusterPrivilege MANAGE_OIDC = new ActionClusterPrivilege("manage_oidc", MANAGE_OIDC_PATTERN);
public static final NamedClusterPrivilege MANAGE_API_KEY = new ActionClusterPrivilege("manage_api_key", MANAGE_API_KEY_PATTERN);
@ -113,6 +114,8 @@ public class ClusterPrivilegeResolver {
public static final NamedClusterPrivilege READ_ILM = new ActionClusterPrivilege("read_ilm", READ_ILM_PATTERN);
public static final NamedClusterPrivilege MANAGE_SLM = new ActionClusterPrivilege("manage_slm", MANAGE_SLM_PATTERN);
public static final NamedClusterPrivilege READ_SLM = new ActionClusterPrivilege("read_slm", READ_SLM_PATTERN);
public static final NamedClusterPrivilege DELEGATE_PKI = new ActionClusterPrivilege("delegate_pki",
Sets.newHashSet(DelegatePkiAuthenticationAction.NAME, InvalidateTokenAction.NAME));
private static final Map<String, NamedClusterPrivilege> VALUES = Collections.unmodifiableMap(
Stream.of(
@ -143,7 +146,8 @@ public class ClusterPrivilegeResolver {
MANAGE_ILM,
READ_ILM,
MANAGE_SLM,
READ_SLM).collect(Collectors.toMap(cp -> cp.name(), cp -> cp)));
READ_SLM,
DELEGATE_PKI).collect(Collectors.toMap(cp -> cp.name(), cp -> cp)));
/**
* Resolves a {@link NamedClusterPrivilege} from a given name if it exists.

View File

@ -111,7 +111,7 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
new String[] {
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
GetBuiltinPrivilegesAction.NAME
GetBuiltinPrivilegesAction.NAME, "delegate_pki"
},
new RoleDescriptor.IndicesPrivileges[] {
RoleDescriptor.IndicesPrivileges.builder()

View File

@ -19,6 +19,7 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException;
import java.io.InputStream;
@ -289,4 +290,20 @@ public class CertParsingUtils {
}
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
}
/**
* Checks that the {@code X509Certificate} array is ordered, such that the end-entity certificate is first and it is followed by any
* certificate authorities'. The check validates that the {@code issuer} of every certificate is the {@code subject} of the certificate
* in the next array position. No other certificate attributes are checked.
*/
public static boolean isOrderedCertificateChain(List<X509Certificate> chain) {
for (int i = 1; i < chain.size(); i++) {
X509Certificate cert = chain.get(i - 1);
X509Certificate issuer = chain.get(i);
if (false == cert.getIssuerX500Principal().equals(issuer.getSubjectX500Principal())) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.action;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.security.auth.x500.X500Principal;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class DelegatePkiAuthenticationRequestTests extends AbstractXContentTestCase<DelegatePkiAuthenticationRequest> {
public void testRequestValidation() {
expectThrows(NullPointerException.class, () -> new DelegatePkiAuthenticationRequest((List<X509Certificate>) null));
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(Arrays.asList(new X509Certificate[0]));
ActionRequestValidationException ve = request.validate();
assertNotNull(ve);
assertEquals(1, ve.validationErrors().size());
assertThat(ve.validationErrors().get(0), is("certificates chain must not be empty"));
List<X509Certificate> mockCertChain = new ArrayList<>(2);
mockCertChain.add(mock(X509Certificate.class));
when(mockCertChain.get(0).getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
mockCertChain.add(mock(X509Certificate.class));
when(mockCertChain.get(1).getSubjectX500Principal()).thenReturn(new X500Principal("CN=Not Test, OU=elasticsearch, O=org"));
request = new DelegatePkiAuthenticationRequest(mockCertChain);
ve = request.validate();
assertNotNull(ve);
assertEquals(1, ve.validationErrors().size());
assertThat(ve.validationErrors().get(0), is("certificates chain must be an ordered chain"));
request = new DelegatePkiAuthenticationRequest(Arrays.asList(randomArray(1, 3, X509Certificate[]::new, () -> {
X509Certificate mockX509Certificate = mock(X509Certificate.class);
when(mockX509Certificate.getSubjectX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
when(mockX509Certificate.getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
return mockX509Certificate;
})));
ve = request.validate();
assertNull(ve);
}
public void testSerialization() throws Exception {
List<X509Certificate> certificates = randomCertificateList();
DelegatePkiAuthenticationRequest request = new DelegatePkiAuthenticationRequest(certificates);
try (BytesStreamOutput out = new BytesStreamOutput()) {
request.writeTo(out);
try (StreamInput in = out.bytes().streamInput()) {
final DelegatePkiAuthenticationRequest serialized = new DelegatePkiAuthenticationRequest(in);
assertThat(request.getCertificateChain(), is(certificates));
assertThat(request, is(serialized));
assertThat(request.hashCode(), is(serialized.hashCode()));
}
}
}
private List<X509Certificate> randomCertificateList() {
List<X509Certificate> certificates = Arrays.asList(randomArray(1, 3, X509Certificate[]::new, () -> {
try {
return readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/"
+ randomFrom("testclient.crt", "testnode.crt", "testnode-ip-only.crt", "openldap.crt", "samba4.crt")));
} catch (Exception e) {
throw new RuntimeException(e);
}
}));
return certificates;
}
private X509Certificate readCert(Path path) throws Exception {
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
}
}
@Override
protected DelegatePkiAuthenticationRequest createTestInstance() {
List<X509Certificate> certificates = randomCertificateList();
return new DelegatePkiAuthenticationRequest(certificates);
}
@Override
protected DelegatePkiAuthenticationRequest doParseInstance(XContentParser parser) throws IOException {
return DelegatePkiAuthenticationRequest.fromXContent(parser);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.action;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse;
import java.io.IOException;
import static org.hamcrest.Matchers.is;
public class DelegatePkiAuthenticationResponseTests extends AbstractXContentTestCase<DelegatePkiAuthenticationResponse> {
public void testSerialization() throws Exception {
DelegatePkiAuthenticationResponse response = createTestInstance();
try (BytesStreamOutput output = new BytesStreamOutput()) {
response.writeTo(output);
try (StreamInput input = output.bytes().streamInput()) {
DelegatePkiAuthenticationResponse serialized = new DelegatePkiAuthenticationResponse(input);
assertThat(response.getAccessToken(), is(serialized.getAccessToken()));
assertThat(response.getExpiresIn(), is(serialized.getExpiresIn()));
assertThat(response, is(serialized));
}
}
}
@Override
protected DelegatePkiAuthenticationResponse createTestInstance() {
return new DelegatePkiAuthenticationResponse(randomAlphaOfLengthBetween(0, 10),
TimeValue.parseTimeValue(randomTimeValue(), getClass().getSimpleName() + ".expiresIn"));
}
@Override
protected DelegatePkiAuthenticationResponse doParseInstance(XContentParser parser) throws IOException {
return DelegatePkiAuthenticationResponse.PARSER.apply(parser, null);
}
@Override
protected boolean supportsUnknownFields() {
return true;
}
}

View File

@ -111,6 +111,7 @@ import org.elasticsearch.xpack.core.ml.annotations.AnnotationIndex;
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndexFields;
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction;
@ -227,6 +228,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(snapshotUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(snapshotUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false));
assertThat(snapshotUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), is(false));
@ -263,6 +265,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(ingestAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)),
@ -322,6 +325,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
// Everything else
assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
assertThat(kibanaRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(true));
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false));
@ -391,6 +395,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(kibanaUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(kibanaUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -430,6 +435,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(monitoringUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(monitoringUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -491,6 +497,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
// we get this from the cluster:monitor privilege
assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request), is(true));
@ -545,6 +552,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -641,6 +649,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(reportingUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(reportingUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -681,6 +690,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(dashboardsOnlyUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@ -713,6 +723,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(superuserRole.cluster().check(PutUserAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(true));
assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false));
final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build();
@ -784,6 +795,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -812,6 +824,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false));
assertThat(beatsAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -848,6 +861,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(beatsSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -881,6 +895,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true));
assertThat(APMSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -902,6 +917,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role role = Role.builder(roleDescriptor, null).build();
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
assertNoAccessAllowed(role, "foo");
@ -968,6 +984,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); // internal use only
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(true));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -1051,6 +1068,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(false));
assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertNoAccessAllowed(role, "foo");
@ -1092,6 +1110,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertOnlyReadAllowed(role, ".data-frame-notifications-1");
@ -1128,6 +1147,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(false));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertOnlyReadAllowed(role, ".data-frame-notifications-1");
@ -1165,6 +1185,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(true));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -1194,6 +1215,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(false));
assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true));
assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
@ -1262,6 +1284,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false));
assertThat(logstashAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false));
assertThat(logstashAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false));
@ -1290,6 +1313,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role codeAdminRole = Role.builder(roleDescriptor, null).build();
assertThat(codeAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false));
assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false));
assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false));
@ -1316,6 +1340,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
Role codeUserRole = Role.builder(roleDescriptor, null).build();
assertThat(codeUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false));
assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false));
assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false));

View File

@ -31,18 +31,18 @@ class PkiRealmBootstrapCheck implements BootstrapCheck {
}
/**
* If a PKI realm is enabled, checks to see if SSL and Client authentication are enabled on at
* If a PKI realm is enabled, and does not support delegation(default), checks to see if SSL and Client authentication are enabled on at
* least one network communication layer.
*/
@Override
public BootstrapCheckResult check(BootstrapContext context) {
final Settings settings = context.settings();
final Map<RealmIdentifier, Settings> realms = RealmSettings.getRealmSettings(settings);
final boolean pkiRealmEnabled = realms.entrySet().stream()
final boolean pkiRealmEnabledWithoutDelegation = realms.entrySet().stream()
.filter(e -> PkiRealmSettings.TYPE.equals(e.getKey().getType()))
.map(Map.Entry::getValue)
.anyMatch(s -> s.getAsBoolean("enabled", true));
if (pkiRealmEnabled) {
.anyMatch(s -> s.getAsBoolean("enabled", true) && (false == s.getAsBoolean("delegation.enabled", false)));
if (pkiRealmEnabledWithoutDelegation) {
for (String contextName : getSslContextNames(settings)) {
final SSLConfiguration configuration = sslService.getSSLConfiguration(contextName);
if (sslService.isSSLClientAuthEnabled(configuration)) {

View File

@ -77,6 +77,7 @@ import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.SecurityField;
import org.elasticsearch.xpack.core.security.SecuritySettings;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateAction;
@ -136,6 +137,7 @@ import org.elasticsearch.xpack.core.ssl.action.GetCertificateInfoAction;
import org.elasticsearch.xpack.core.ssl.action.TransportGetCertificateInfoAction;
import org.elasticsearch.xpack.core.ssl.rest.RestGetCertificateInfoAction;
import org.elasticsearch.xpack.security.action.TransportCreateApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportDelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.security.action.TransportGetApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
@ -197,6 +199,7 @@ import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor;
import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.RestDelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction;
@ -778,7 +781,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new ActionHandler<>(DeletePrivilegesAction.INSTANCE, TransportDeletePrivilegesAction.class),
new ActionHandler<>(CreateApiKeyAction.INSTANCE, TransportCreateApiKeyAction.class),
new ActionHandler<>(InvalidateApiKeyAction.INSTANCE, TransportInvalidateApiKeyAction.class),
new ActionHandler<>(GetApiKeyAction.INSTANCE, TransportGetApiKeyAction.class)
new ActionHandler<>(GetApiKeyAction.INSTANCE, TransportGetApiKeyAction.class),
new ActionHandler<>(DelegatePkiAuthenticationAction.INSTANCE, TransportDelegatePkiAuthenticationAction.class)
);
}
@ -834,7 +838,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new RestDeletePrivilegesAction(settings, restController, getLicenseState()),
new RestCreateApiKeyAction(settings, restController, getLicenseState()),
new RestInvalidateApiKeyAction(settings, restController, getLicenseState()),
new RestGetApiKeyAction(settings, restController, getLicenseState())
new RestGetApiKeyAction(settings, restController, getLicenseState()),
new RestDelegatePkiAuthenticationAction(settings, restController, getLicenseState())
);
}

View File

@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.security.authc.AuthenticationService;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.pki.X509AuthenticationToken;
import java.security.cert.X509Certificate;
import java.util.Collections;
/**
* Implements the exchange of an {@code X509Certificate} chain into an access token. The certificate chain is represented as an array where
* the first element is the target certificate containing the subject distinguished name that is requesting access. This may be followed by
* additional certificates, with each subsequent certificate being the one used to certify the previous one. The certificate chain is
* validated according to RFC 5280, by sequentially considering the trust configuration of every installed {@code PkiRealm} that has
* {@code PkiRealmSettings#DELEGATION_ENABLED_SETTING} set to {@code true} (default is {@code false}). A successfully trusted target
* certificate is also subject to the validation of the subject distinguished name according to that respective's realm
* {@code PkiRealmSettings#USERNAME_PATTERN_SETTING}.
*
* IMPORTANT: The association between the subject public key in the target certificate and the corresponding private key is <b>not</b>
* validated. This is part of the TLS authentication process and it is delegated to the proxy calling this API. The proxy is <b>trusted</b>
* to have performed the TLS authentication, and this API translates that authentication into an Elasticsearch access token.
*/
public final class TransportDelegatePkiAuthenticationAction
extends HandledTransportAction<DelegatePkiAuthenticationRequest, DelegatePkiAuthenticationResponse> {
private static final Logger logger = LogManager.getLogger(TransportDelegatePkiAuthenticationAction.class);
private final ThreadPool threadPool;
private final AuthenticationService authenticationService;
private final TokenService tokenService;
@Inject
public TransportDelegatePkiAuthenticationAction(ThreadPool threadPool, TransportService transportService, ActionFilters actionFilters,
AuthenticationService authenticationService, TokenService tokenService) {
super(DelegatePkiAuthenticationAction.NAME, transportService, actionFilters, DelegatePkiAuthenticationRequest::new);
this.threadPool = threadPool;
this.authenticationService = authenticationService;
this.tokenService = tokenService;
}
@Override
protected void doExecute(Task task, DelegatePkiAuthenticationRequest request,
ActionListener<DelegatePkiAuthenticationResponse> listener) {
final ThreadContext threadContext = threadPool.getThreadContext();
Authentication delegateeAuthentication = Authentication.getAuthentication(threadContext);
if (delegateeAuthentication == null) {
listener.onFailure(new IllegalStateException("Delegatee authentication cannot be null"));
return;
}
final X509AuthenticationToken x509DelegatedToken = X509AuthenticationToken
.delegated(request.getCertificateChain().toArray(new X509Certificate[0]), delegateeAuthentication);
logger.trace("Attempting to authenticate delegated x509Token [{}]", x509DelegatedToken);
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
authenticationService.authenticate(DelegatePkiAuthenticationAction.NAME, request, x509DelegatedToken,
ActionListener.wrap(authentication -> {
assert authentication != null : "authentication should never be null at this point";
tokenService.createOAuth2Tokens(authentication, delegateeAuthentication, Collections.emptyMap(), false,
ActionListener.wrap(tuple -> {
final TimeValue expiresIn = tokenService.getExpirationDelay();
listener.onResponse(new DelegatePkiAuthenticationResponse(tuple.v1(), expiresIn));
}, listener::onFailure));
}, e -> {
logger.debug((Supplier<?>) () -> new ParameterizedMessage("Delegated x509Token [{}] could not be authenticated",
x509DelegatedToken), e);
listener.onFailure(e);
}));
}
}
}

View File

@ -20,6 +20,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.Realm;
@ -30,6 +31,7 @@ import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
import org.elasticsearch.xpack.security.authc.BytesKey;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.support.CachingRealm;
import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
@ -43,6 +45,8 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -76,6 +80,7 @@ public class PkiRealm extends Realm implements CachingRealm {
private final UserRoleMapper roleMapper;
private final Cache<BytesKey, User> cache;
private DelegatedAuthorizationSupport delegatedRealms;
private final boolean delegationEnabled;
public PkiRealm(RealmConfig config, ResourceWatcherService watcherService, NativeRoleMappingStore nativeRoleMappingStore) {
this(config, new CompositeRoleMapper(config, watcherService, nativeRoleMappingStore));
@ -84,6 +89,7 @@ public class PkiRealm extends Realm implements CachingRealm {
// pkg private for testing
PkiRealm(RealmConfig config, UserRoleMapper roleMapper) {
super(config);
this.delegationEnabled = config.getSetting(PkiRealmSettings.DELEGATION_ENABLED_SETTING);
this.trustManager = trustManagers(config);
this.principalPattern = config.getSetting(PkiRealmSettings.USERNAME_PATTERN_SETTING);
this.roleMapper = roleMapper;
@ -93,6 +99,7 @@ public class PkiRealm extends Realm implements CachingRealm {
.setMaximumWeight(config.getSetting(PkiRealmSettings.CACHE_MAX_USERS_SETTING))
.build();
this.delegatedRealms = null;
validateAuthenticationDelegationConfiguration(config);
}
@Override
@ -141,15 +148,19 @@ public class PkiRealm extends Realm implements CachingRealm {
assert delegatedRealms != null : "Realm has not been initialized correctly";
X509AuthenticationToken token = (X509AuthenticationToken) authToken;
try {
final BytesKey fingerprint = computeFingerprint(token.credentials()[0]);
final BytesKey fingerprint = computeTokenFingerprint(token);
User user = cache.get(fingerprint);
if (user != null) {
logger.debug((Supplier<?>) () -> new ParameterizedMessage("Using cached authentication for DN [{}], as principal [{}]",
token.dn(), user.principal()));
if (delegatedRealms.hasDelegation()) {
delegatedRealms.resolve(user.principal(), listener);
} else {
listener.onResponse(AuthenticationResult.success(user));
}
} else if (isCertificateChainTrusted(trustManager, token, logger) == false) {
} else if (false == delegationEnabled && token.isDelegated()) {
listener.onResponse(AuthenticationResult.unsuccessful("Realm does not permit delegation for " + token.dn(), null));
} else if (false == isCertificateChainTrusted(token)) {
listener.onResponse(AuthenticationResult.unsuccessful("Certificate for " + token.dn() + " is not trusted", null));
} else {
// parse the principal again after validating the cert chain, and do not rely on the token.principal one, because that could
@ -188,7 +199,16 @@ public class PkiRealm extends Realm implements CachingRealm {
}
private void buildUser(X509AuthenticationToken token, String principal, ActionListener<AuthenticationResult> listener) {
final Map<String, Object> metadata = Collections.singletonMap("pki_dn", token.dn());
final Map<String, Object> metadata;
if (token.isDelegated()) {
Map<String, Object> delegatedMetadata = new HashMap<>();
delegatedMetadata.put("pki_dn", token.dn());
delegatedMetadata.put("pki_delegated_by_user", token.getDelegateeAuthentication().getUser().principal());
delegatedMetadata.put("pki_delegated_by_realm", token.getDelegateeAuthentication().getAuthenticatedBy().getName());
metadata = Collections.unmodifiableMap(delegatedMetadata);
} else {
metadata = Collections.singletonMap("pki_dn", token.dn());
}
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(principal, token.dn(), Collections.emptySet(), metadata,
this.config);
roleMapper.resolveRoles(userData, ActionListener.wrap(roles -> {
@ -219,8 +239,13 @@ public class PkiRealm extends Realm implements CachingRealm {
return principal;
}
private static boolean isCertificateChainTrusted(X509TrustManager trustManager, X509AuthenticationToken token, Logger logger) {
if (trustManager != null) {
private boolean isCertificateChainTrusted(X509AuthenticationToken token) {
if (trustManager == null) {
// No extra trust managers specified
// If the token is NOT delegated then it is authenticated, because the certificate chain has been validated by the TLS channel.
// Otherwise, if the token is delegated, then it cannot be authenticated without a trustManager
return token.isDelegated() == false;
} else {
try {
trustManager.checkClientTrusted(token.credentials(), AUTH_TYPE);
return true;
@ -233,9 +258,6 @@ public class PkiRealm extends Realm implements CachingRealm {
}
return false;
}
// No extra trust managers specified, so at this point we can be considered authenticated.
return true;
}
private X509TrustManager trustManagers(RealmConfig realmConfig) {
@ -314,9 +336,44 @@ public class PkiRealm extends Realm implements CachingRealm {
}
}
private static BytesKey computeFingerprint(X509Certificate certificate) throws CertificateEncodingException {
@Override
public void usageStats(ActionListener<Map<String, Object>> listener) {
super.usageStats(ActionListener.wrap(stats -> {
stats.put("has_truststore", trustManager != null);
stats.put("has_authorization_realms", delegatedRealms != null && delegatedRealms.hasDelegation());
stats.put("has_default_username_pattern", PkiRealmSettings.DEFAULT_USERNAME_PATTERN.equals(principalPattern.pattern()));
stats.put("is_authentication_delegated", delegationEnabled);
listener.onResponse(stats);
}, listener::onFailure));
}
private void validateAuthenticationDelegationConfiguration(RealmConfig config) {
if (delegationEnabled) {
List<String> exceptionMessages = new ArrayList<>(2);
if (this.trustManager == null) {
exceptionMessages.add("a trust configuration ("
+ config.getConcreteSetting(PkiRealmSettings.CAPATH_SETTING).getKey() + " or "
+ config.getConcreteSetting(PkiRealmSettings.TRUST_STORE_PATH).getKey() + ")");
}
if (false == TokenService.isTokenServiceEnabled(config.settings())) {
exceptionMessages.add("that the token service be also enabled ("
+ XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey() + ")");
}
if (false == exceptionMessages.isEmpty()) {
String message = "PKI realms with delegation enabled require " + exceptionMessages.get(0);
if (exceptionMessages.size() == 2) {
message = message + " and " + exceptionMessages.get(1);
}
throw new IllegalStateException(message);
}
}
}
static BytesKey computeTokenFingerprint(X509AuthenticationToken token) throws CertificateEncodingException {
MessageDigest digest = MessageDigests.sha256();
for (X509Certificate certificate : token.credentials()) {
digest.update(certificate.getEncoded());
}
return new BytesKey(digest.digest());
}
}

View File

@ -5,21 +5,38 @@
*/
package org.elasticsearch.xpack.security.authc.pki;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.ssl.CertParsingUtils;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Objects;
public class X509AuthenticationToken implements AuthenticationToken {
private final String dn;
private final X509Certificate[] credentials;
private final Authentication delegateeAuthentication;
private String principal;
public X509AuthenticationToken(X509Certificate[] certificates) {
this(certificates, null);
}
private X509AuthenticationToken(X509Certificate[] certificates, Authentication delegateeAuthentication) {
this.credentials = Objects.requireNonNull(certificates);
if (false == CertParsingUtils.isOrderedCertificateChain(Arrays.asList(certificates))) {
throw new IllegalArgumentException("certificates chain array is not ordered");
}
this.dn = certificates.length == 0 ? "" : certificates[0].getSubjectX500Principal().toString();
this.principal = this.dn;
this.delegateeAuthentication = delegateeAuthentication;
}
public static X509AuthenticationToken delegated(X509Certificate[] certificates, Authentication delegateeAuthentication) {
Objects.requireNonNull(delegateeAuthentication);
return new X509AuthenticationToken(certificates, delegateeAuthentication);
}
@Override
@ -44,4 +61,12 @@ public class X509AuthenticationToken implements AuthenticationToken {
public void clearCredentials() {
// noop
}
public boolean isDelegated() {
return delegateeAuthentication != null;
}
public Authentication getDelegateeAuthentication() {
return delegateeAuthentication;
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.rest.action;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.LicenseUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestResponse;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestBuilderListener;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest;
import org.elasticsearch.xpack.security.action.TransportDelegatePkiAuthenticationAction;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse;
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.POST;
/**
* Implements the exchange of an {@code X509Certificate} chain into an access token. The chain is represented as an ordered string array.
* Each string in the array is a base64-encoded (Section 4 of RFC4648 - not base64url-encoded) DER PKIX certificate value.
* See also {@link TransportDelegatePkiAuthenticationAction}.
*/
public final class RestDelegatePkiAuthenticationAction extends SecurityBaseRestHandler {
protected Logger logger = LogManager.getLogger(RestDelegatePkiAuthenticationAction.class);
public RestDelegatePkiAuthenticationAction(Settings settings, RestController controller, XPackLicenseState xPackLicenseState) {
super(settings, xPackLicenseState);
controller.registerHandler(POST, "/_security/delegate_pki", this);
}
@Override
protected Exception checkFeatureAvailable(RestRequest request) {
Exception failedFeature = super.checkFeatureAvailable(request);
if (failedFeature != null) {
return failedFeature;
} else if (Realms.isRealmTypeAvailable(licenseState.allowedRealmType(), PkiRealmSettings.TYPE)) {
return null;
} else {
logger.info("The '{}' realm is not available under the current license", PkiRealmSettings.TYPE);
return LicenseUtils.newComplianceException(PkiRealmSettings.TYPE);
}
}
@Override
protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
try (XContentParser parser = request.contentParser()) {
final DelegatePkiAuthenticationRequest delegatePkiRequest = DelegatePkiAuthenticationRequest.fromXContent(parser);
return channel -> client.execute(DelegatePkiAuthenticationAction.INSTANCE, delegatePkiRequest,
new RestBuilderListener<DelegatePkiAuthenticationResponse>(channel) {
@Override
public RestResponse buildResponse(DelegatePkiAuthenticationResponse delegatePkiResponse, XContentBuilder builder)
throws Exception {
delegatePkiResponse.toXContent(builder, channel.request());
return new BytesRestResponse(RestStatus.OK, builder);
}
});
}
}
@Override
public String getName() {
return "delegate_pki_action";
}
}

View File

@ -18,6 +18,7 @@ import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
@ -534,4 +535,10 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
protected static Hasher getFastStoredHashAlgoForTests() {
return Hasher.resolve(randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9"));
}
protected class TestRestHighLevelClient extends RestHighLevelClient {
public TestRestHighLevelClient() {
super(getRestClient(), client -> {}, Collections.emptyList());
}
}
}

View File

@ -89,6 +89,17 @@ public class PkiRealmBootstrapCheckTests extends AbstractBootstrapCheckTestCase
assertFalse(runCheck(settings, env).isFailure());
}
public void testBootstrapCheckWithDelegationEnabled() throws Exception {
Settings settings = Settings.builder()
.put("xpack.security.authc.realms.pki.test_pki.enabled", true)
.put("xpack.security.authc.realms.pki.test_pki.delegation.enabled", true)
.put("xpack.security.transport.ssl.client_authentication", "none")
.put("path.home", createTempDir())
.build();
Environment env = TestEnvironment.newEnvironment(settings);
assertFalse(runCheck(settings, env).isFailure());
}
public void testBootstrapCheckWithClosedSecuredSetting() throws Exception {
final boolean expectFail = randomBoolean();
final MockSecureSettings secureSettings = new MockSecureSettings();

View File

@ -210,6 +210,9 @@ public class RealmSettingsTests extends ESTestCase {
} else {
builder.putList("certificate_authorities", generateRandomStringArray(5, 32, false, false));
}
if (randomBoolean()) {
builder.put("delegation.enabled", randomBoolean());
}
return builder;
}

View File

@ -0,0 +1,338 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.authc.pki;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.ValidationException;
import org.elasticsearch.client.security.AuthenticateResponse;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.RefreshPolicy;
import org.elasticsearch.client.security.AuthenticateResponse.RealmInfo;
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression;
import org.elasticsearch.client.security.DelegatePkiAuthenticationRequest;
import org.elasticsearch.client.security.DelegatePkiAuthenticationResponse;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.user.User;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.test.SecuritySettingsSourceField;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequestBuilder;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
import org.junit.Before;
import org.elasticsearch.test.SecuritySettingsSource;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Arrays;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.emptyCollectionOf;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.startsWith;
public class PkiAuthDelegationIntegTests extends SecurityIntegTestCase {
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true)
// pki1 does not allow delegation
.put("xpack.security.authc.realms.pki.pki1.order", "1")
.putList("xpack.security.authc.realms.pki.pki1.certificate_authorities",
getDataPath("/org/elasticsearch/xpack/security/action/pki_delegation/testRootCA.crt").toString())
.put("xpack.security.authc.realms.pki.pki1.files.role_mapping", getDataPath("role_mapping.yml"))
// pki2 allows delegation but has a non-matching username pattern
.put("xpack.security.authc.realms.pki.pki2.order", "2")
.putList("xpack.security.authc.realms.pki.pki2.certificate_authorities",
getDataPath("/org/elasticsearch/xpack/security/action/pki_delegation/testRootCA.crt").toString())
.put("xpack.security.authc.realms.pki.pki2.username_pattern", "CN=MISMATCH(.*?)(?:,|$)")
.put("xpack.security.authc.realms.pki.pki2.delegation.enabled", true)
.put("xpack.security.authc.realms.pki.pki2.files.role_mapping", getDataPath("role_mapping.yml"))
// pki3 allows delegation and the username pattern (default) matches
.put("xpack.security.authc.realms.pki.pki3.order", "3")
.putList("xpack.security.authc.realms.pki.pki3.certificate_authorities",
getDataPath("/org/elasticsearch/xpack/security/action/pki_delegation/testRootCA.crt").toString())
.put("xpack.security.authc.realms.pki.pki3.delegation.enabled", true)
.put("xpack.security.authc.realms.pki.pki3.files.role_mapping", getDataPath("role_mapping.yml"))
.build();
}
@Override
protected String configUsers() {
final String usersPasswdHashed = new String(Hasher.resolve(
randomFrom("pbkdf2", "pbkdf2_1000", "bcrypt", "bcrypt9")).hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
return super.configUsers() +
"user_manage:" + usersPasswdHashed + "\n" +
"user_manage_security:" + usersPasswdHashed + "\n" +
"user_delegate_pki:" + usersPasswdHashed + "\n" +
"user_all:" + usersPasswdHashed + "\n" +
"kibana_system:" + usersPasswdHashed + "\n";
}
@Override
protected String configRoles() {
return super.configRoles() + "\n" +
"role_manage:\n" +
" cluster: [ manage ]\n" +
"\n" +
"role_manage_security:\n" +
" cluster: [ manage_security ]\n" +
"\n" +
"role_delegate_pki:\n" +
" cluster: [ delegate_pki ]\n" +
"\n" +
"role_all:\n" +
" cluster: [ all ]\n";
}
@Override
protected String configUsersRoles() {
return super.configUsersRoles() + "\n" +
"role_manage:user_manage\n" +
"role_manage_security:user_manage_security\n" +
"role_delegate_pki:user_delegate_pki\n" +
"role_all:user_all\n" +
"kibana_system:kibana_system\n";
}
@Override
protected boolean transportSSLEnabled() {
return true;
}
@Override
protected boolean addMockHttpTransport() {
return false; // enable http
}
@Before
void clearRealmCache() {
new ClearRealmCacheRequestBuilder(client()).get();
}
public void testDelegateThenAuthenticate() throws Exception {
final X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
final X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
final X509Certificate rootCA = readCertForPkiDelegation("testRootCA.crt");
DelegatePkiAuthenticationRequest delegatePkiRequest;
// trust root is optional
if (randomBoolean()) {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA));
} else {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA, rootCA));
}
try (RestHighLevelClient restClient = new TestRestHighLevelClient()) {
for (String delegateeUsername : Arrays.asList("user_all", "user_delegate_pki", "kibana_system")) {
// delegate
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization",
basicAuthHeaderValue(delegateeUsername, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
DelegatePkiAuthenticationResponse delegatePkiResponse = restClient.security().delegatePkiAuthentication(delegatePkiRequest,
optionsBuilder.build());
String token = delegatePkiResponse.getAccessToken();
assertThat(token, is(notNullValue()));
// authenticate
optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", "Bearer " + token);
AuthenticateResponse resp = restClient.security().authenticate(optionsBuilder.build());
User user = resp.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.getUsername(), is("Elasticsearch Test Client"));
RealmInfo authnRealm = resp.getAuthenticationRealm();
assertThat(authnRealm, is(notNullValue()));
assertThat(authnRealm.getName(), is("pki3"));
assertThat(authnRealm.getType(), is("pki"));
}
}
}
public void testTokenInvalidate() throws Exception {
final X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
final X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
final X509Certificate rootCA = readCertForPkiDelegation("testRootCA.crt");
DelegatePkiAuthenticationRequest delegatePkiRequest;
// trust root is optional
if (randomBoolean()) {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA));
} else {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA, rootCA));
}
try (RestHighLevelClient restClient = new TestRestHighLevelClient()) {
String delegateeUsername = randomFrom("user_all", "user_delegate_pki", "kibana_system");
// delegate
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization",
basicAuthHeaderValue(delegateeUsername, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
DelegatePkiAuthenticationResponse delegatePkiResponse = restClient.security().delegatePkiAuthentication(delegatePkiRequest,
optionsBuilder.build());
String token = delegatePkiResponse.getAccessToken();
assertThat(token, is(notNullValue()));
// authenticate
optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", "Bearer " + token);
AuthenticateResponse resp = restClient.security().authenticate(optionsBuilder.build());
User user = resp.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.getUsername(), is("Elasticsearch Test Client"));
assertThat(user.getMetadata().get("pki_dn"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_dn"), is("O=org, OU=Elasticsearch, CN=Elasticsearch Test Client"));
assertThat(user.getMetadata().get("pki_delegated_by_user"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_delegated_by_user"), is(delegateeUsername));
assertThat(user.getMetadata().get("pki_delegated_by_realm"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_delegated_by_realm"), is("file"));
// no roles because no role mappings
assertThat(user.getRoles(), is(emptyCollectionOf(String.class)));
RealmInfo authnRealm = resp.getAuthenticationRealm();
assertThat(authnRealm, is(notNullValue()));
assertThat(authnRealm.getName(), is("pki3"));
assertThat(authnRealm.getType(), is("pki"));
// invalidate
InvalidateTokenRequest invalidateRequest = new InvalidateTokenRequest(token, null, null, null);
optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization",
basicAuthHeaderValue(delegateeUsername, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
InvalidateTokenResponse invalidateResponse = restClient.security().invalidateToken(invalidateRequest, optionsBuilder.build());
assertThat(invalidateResponse.getInvalidatedTokens(), is(1));
assertThat(invalidateResponse.getErrorsCount(), is(0));
// failed authenticate
ElasticsearchStatusException e1 = expectThrows(ElasticsearchStatusException.class, () -> restClient.security()
.authenticate(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Bearer " + token).build()));
assertThat(e1.getMessage(), is("Elasticsearch exception [type=security_exception, reason=token expired]"));
}
}
public void testDelegateUnauthorized() throws Exception {
final X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
final X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
final X509Certificate rootCA = readCertForPkiDelegation("testRootCA.crt");
DelegatePkiAuthenticationRequest delegatePkiRequest;
// trust root is optional
if (randomBoolean()) {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA));
} else {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA, rootCA));
}
try (RestHighLevelClient restClient = new TestRestHighLevelClient()) {
for (String delegateeUsername : Arrays.asList("user_manage", "user_manage_security")) {
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization",
basicAuthHeaderValue(delegateeUsername, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class, () -> {
restClient.security().delegatePkiAuthentication(delegatePkiRequest, optionsBuilder.build());
});
assertThat(e.getMessage(), startsWith("Elasticsearch exception [type=security_exception, reason=action"
+ " [cluster:admin/xpack/security/delegate_pki] is unauthorized for user"));
}
}
}
public void testDelegatePkiWithRoleMapping() throws Exception {
X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
X509Certificate rootCA = readCertForPkiDelegation("testRootCA.crt");
DelegatePkiAuthenticationRequest delegatePkiRequest;
// trust root is optional
if (randomBoolean()) {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA));
} else {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA, rootCA));
}
final RequestOptions.Builder testUserOptionsBuilder = RequestOptions.DEFAULT.toBuilder();
testUserOptionsBuilder.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME,
new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())));
final RequestOptions testUserOptions = testUserOptionsBuilder.build();
try (RestHighLevelClient restClient = new TestRestHighLevelClient()) {
// put role mappings for delegated PKI
PutRoleMappingRequest request = new PutRoleMappingRequest("role_by_delegated_user", true,
Collections.singletonList("role_by_delegated_user"), Collections.emptyList(),
new FieldRoleMapperExpression("metadata.pki_delegated_by_user", "test_user"), null, RefreshPolicy.IMMEDIATE);
restClient.security().putRoleMapping(request, testUserOptions);
request = new PutRoleMappingRequest("role_by_delegated_realm", true, Collections.singletonList("role_by_delegated_realm"),
Collections.emptyList(), new FieldRoleMapperExpression("metadata.pki_delegated_by_realm", "file"), null,
RefreshPolicy.IMMEDIATE);
restClient.security().putRoleMapping(request, testUserOptions);
// delegate
DelegatePkiAuthenticationResponse delegatePkiResponse = restClient.security().delegatePkiAuthentication(delegatePkiRequest,
testUserOptions);
// authenticate
AuthenticateResponse resp = restClient.security().authenticate(RequestOptions.DEFAULT.toBuilder()
.addHeader("Authorization", "Bearer " + delegatePkiResponse.getAccessToken()).build());
User user = resp.getUser();
assertThat(user, is(notNullValue()));
assertThat(user.getUsername(), is("Elasticsearch Test Client"));
assertThat(user.getMetadata().get("pki_dn"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_dn"), is("O=org, OU=Elasticsearch, CN=Elasticsearch Test Client"));
assertThat(user.getMetadata().get("pki_delegated_by_user"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_delegated_by_user"), is("test_user"));
assertThat(user.getMetadata().get("pki_delegated_by_realm"), is(notNullValue()));
assertThat(user.getMetadata().get("pki_delegated_by_realm"), is("file"));
// assert roles
assertThat(user.getRoles(), containsInAnyOrder("role_by_delegated_user", "role_by_delegated_realm"));
RealmInfo authnRealm = resp.getAuthenticationRealm();
assertThat(authnRealm, is(notNullValue()));
assertThat(authnRealm.getName(), is("pki3"));
assertThat(authnRealm.getType(), is("pki"));
// delete role mappings for delegated PKI
restClient.security().deleteRoleMapping(new DeleteRoleMappingRequest("role_by_delegated_user", RefreshPolicy.IMMEDIATE),
testUserOptions);
restClient.security().deleteRoleMapping(new DeleteRoleMappingRequest("role_by_delegated_realm", RefreshPolicy.IMMEDIATE),
testUserOptions);
}
}
public void testIncorrectCertChain() throws Exception {
X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
X509Certificate bogusCertificate = readCertForPkiDelegation("bogus.crt");
RequestOptions.Builder optionsBuilder = RequestOptions.DEFAULT.toBuilder();
optionsBuilder.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME,
new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())));
try (RestHighLevelClient restClient = new TestRestHighLevelClient()) {
// incomplete cert chain
DelegatePkiAuthenticationRequest delegatePkiRequest1 = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate));
ElasticsearchStatusException e1 = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().delegatePkiAuthentication(delegatePkiRequest1, optionsBuilder.build()));
assertThat(e1.getMessage(), is("Elasticsearch exception [type=security_exception, reason=unable to authenticate user"
+ " [O=org, OU=Elasticsearch, CN=Elasticsearch Test Client] for action [cluster:admin/xpack/security/delegate_pki]]"));
// swapped order
DelegatePkiAuthenticationRequest delegatePkiRequest2 = new DelegatePkiAuthenticationRequest(
Arrays.asList(intermediateCA, clientCertificate));
ValidationException e2 = expectThrows(ValidationException.class,
() -> restClient.security().delegatePkiAuthentication(delegatePkiRequest2, optionsBuilder.build()));
assertThat(e2.getMessage(), is("Validation Failed: 1: certificates chain must be an ordered chain;"));
// bogus certificate
DelegatePkiAuthenticationRequest delegatePkiRequest3 = new DelegatePkiAuthenticationRequest(Arrays.asList(bogusCertificate));
ElasticsearchStatusException e3 = expectThrows(ElasticsearchStatusException.class,
() -> restClient.security().delegatePkiAuthentication(delegatePkiRequest3, optionsBuilder.build()));
assertThat(e3.getMessage(), startsWith("Elasticsearch exception [type=security_exception, reason=unable to authenticate user"));
}
}
private X509Certificate readCertForPkiDelegation(String certName) throws Exception {
Path path = getDataPath("/org/elasticsearch/xpack/security/action/pki_delegation/" + certName);
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(in);
}
}
}

View File

@ -17,6 +17,8 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Authentication.RealmRef;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.InternalRealmsSettings;
import org.elasticsearch.xpack.core.security.authc.Realm;
@ -25,6 +27,7 @@ import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.support.NoOpLogger;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.BytesKey;
import org.elasticsearch.xpack.security.authc.support.MockLookupRealm;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
import org.junit.Before;
@ -40,6 +43,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@ -47,6 +51,7 @@ import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
@ -72,14 +77,18 @@ public class PkiRealmTests extends ESTestCase {
when(licenseState.isAuthorizationRealmAllowed()).thenReturn(true);
}
public void testTokenSupport() {
public void testTokenSupport() throws Exception {
RealmConfig config = new RealmConfig(new RealmConfig.RealmIdentifier("pki", "my_pki"), globalSettings,
TestEnvironment.newEnvironment(globalSettings), new ThreadContext(globalSettings));
PkiRealm realm = new PkiRealm(config, mock(UserRoleMapper.class));
assertRealmUsageStats(realm, false, false, true, false);
assertThat(realm.supports(null), is(false));
assertThat(realm.supports(new UsernamePasswordToken("", new SecureString(new char[0]))), is(false));
assertThat(realm.supports(new X509AuthenticationToken(new X509Certificate[0])), is(true));
X509AuthenticationToken token = randomBoolean()
? X509AuthenticationToken.delegated(new X509Certificate[0], mock(Authentication.class))
: new X509AuthenticationToken(new X509Certificate[0]);
assertThat(realm.supports(token), is(true));
}
public void testExtractToken() throws Exception {
@ -92,6 +101,7 @@ public class PkiRealmTests extends ESTestCase {
X509AuthenticationToken token = realm.token(threadContext);
assertThat(token, is(notNullValue()));
assertThat(token.dn(), is("CN=Elasticsearch Test Node, OU=elasticsearch, O=org"));
assertThat(token.isDelegated(), is(false));
}
public void testAuthenticateBasedOnCertToken() throws Exception {
@ -114,7 +124,6 @@ public class PkiRealmTests extends ESTestCase {
final String expectedUsername = PkiRealm.getPrincipalFromSubjectDN(Pattern.compile(PkiRealmSettings.DEFAULT_USERNAME_PATTERN),
token, NoOpLogger.INSTANCE);
final AuthenticationResult result = authenticate(token, realm);
final PlainActionFuture<AuthenticationResult> future;
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
User user = result.getUser();
assertThat(user, is(notNullValue()));
@ -199,6 +208,7 @@ public class PkiRealmTests extends ESTestCase {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
UserRoleMapper roleMapper = buildRoleMapper();
PkiRealm realm = buildRealm(roleMapper, settings);
assertRealmUsageStats(realm, false, false, false, false);
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
X509AuthenticationToken token = realm.token(threadContext);
@ -218,6 +228,7 @@ public class PkiRealmTests extends ESTestCase {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
UserRoleMapper roleMapper = buildRoleMapper();
PkiRealm realm = buildRealm(roleMapper, settings);
assertRealmUsageStats(realm, false, false, false, false);
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
X509AuthenticationToken token = realm.token(threadContext);
@ -238,6 +249,7 @@ public class PkiRealmTests extends ESTestCase {
.build();
ThreadContext threadContext = new ThreadContext(globalSettings);
PkiRealm realm = buildRealm(roleMapper, settings);
assertRealmUsageStats(realm, true, false, true, false);
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
@ -249,6 +261,97 @@ public class PkiRealmTests extends ESTestCase {
assertThat(user.roles().length, is(0));
}
public void testAuthenticationDelegationFailsWithoutTokenServiceAndTruststore() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
Settings settings = Settings.builder()
.put(globalSettings)
.put("xpack.security.authc.realms.pki.my_pki.delegation.enabled", true)
.build();
IllegalStateException e = expectThrows(IllegalStateException.class,
() -> new PkiRealm(new RealmConfig(new RealmConfig.RealmIdentifier("pki", "my_pki"), settings,
TestEnvironment.newEnvironment(globalSettings), threadContext), mock(UserRoleMapper.class)));
assertThat(e.getMessage(),
is("PKI realms with delegation enabled require a trust configuration "
+ "(xpack.security.authc.realms.pki.my_pki.certificate_authorities or "
+ "xpack.security.authc.realms.pki.my_pki.truststore.path)"
+ " and that the token service be also enabled (xpack.security.authc.token.enabled)"));
}
public void testAuthenticationDelegationFailsWithoutTruststore() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
Settings settings = Settings.builder()
.put(globalSettings)
.put("xpack.security.authc.realms.pki.my_pki.delegation.enabled", true)
.put("xpack.security.authc.token.enabled", true)
.build();
IllegalStateException e = expectThrows(IllegalStateException.class,
() -> new PkiRealm(new RealmConfig(new RealmConfig.RealmIdentifier("pki", "my_pki"), settings,
TestEnvironment.newEnvironment(globalSettings), threadContext), mock(UserRoleMapper.class)));
assertThat(e.getMessage(),
is("PKI realms with delegation enabled require a trust configuration "
+ "(xpack.security.authc.realms.pki.my_pki.certificate_authorities "
+ "or xpack.security.authc.realms.pki.my_pki.truststore.path)"));
}
public void testAuthenticationDelegationSuccess() throws Exception {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
Authentication mockAuthentication = mock(Authentication.class);
User mockUser = mock(User.class);
when(mockUser.principal()).thenReturn("mockup_delegate_username");
RealmRef mockRealmRef = mock(RealmRef.class);
when(mockRealmRef.getName()).thenReturn("mockup_delegate_realm");
when(mockAuthentication.getUser()).thenReturn(mockUser);
when(mockAuthentication.getAuthenticatedBy()).thenReturn(mockRealmRef);
X509AuthenticationToken delegatedToken = X509AuthenticationToken.delegated(new X509Certificate[] { certificate },
mockAuthentication);
UserRoleMapper roleMapper = buildRoleMapper();
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString("xpack.security.authc.realms.pki.my_pki.truststore.secure_password", "testnode");
Settings settings = Settings.builder()
.put(globalSettings)
.put("xpack.security.authc.realms.pki.my_pki.truststore.path",
getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"))
.put("xpack.security.authc.realms.pki.my_pki.delegation.enabled", true)
.put("xpack.security.authc.token.enabled", true)
.setSecureSettings(secureSettings)
.build();
PkiRealm realmWithDelegation = buildRealm(roleMapper, settings);
assertRealmUsageStats(realmWithDelegation, true, false, true, true);
AuthenticationResult result = authenticate(delegatedToken, realmWithDelegation);
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS));
assertThat(result.getUser(), is(notNullValue()));
assertThat(result.getUser().principal(), is("Elasticsearch Test Node"));
assertThat(result.getUser().roles(), is(notNullValue()));
assertThat(result.getUser().roles().length, is(0));
assertThat(result.getUser().metadata().get("pki_delegated_by_user"), is("mockup_delegate_username"));
assertThat(result.getUser().metadata().get("pki_delegated_by_realm"), is("mockup_delegate_realm"));
}
public void testAuthenticationDelegationFailure() throws Exception {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
X509AuthenticationToken delegatedToken = X509AuthenticationToken.delegated(new X509Certificate[] { certificate },
mock(Authentication.class));
UserRoleMapper roleMapper = buildRoleMapper();
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString("xpack.security.authc.realms.pki.my_pki.truststore.secure_password", "testnode");
Settings settings = Settings.builder()
.put(globalSettings)
.put("xpack.security.authc.realms.pki.my_pki.truststore.path",
getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"))
.setSecureSettings(secureSettings)
.build();
PkiRealm realmNoDelegation = buildRealm(roleMapper, settings);
assertRealmUsageStats(realmNoDelegation, true, false, true, false);
AuthenticationResult result = authenticate(delegatedToken, realmNoDelegation);
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.CONTINUE));
assertThat(result.getUser(), is(nullValue()));
assertThat(result.getMessage(), containsString("Realm does not permit delegation for"));
}
public void testVerificationFailsUsingADifferentTruststore() throws Exception {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
UserRoleMapper roleMapper = buildRoleMapper();
@ -262,6 +365,7 @@ public class PkiRealmTests extends ESTestCase {
.build();
ThreadContext threadContext = new ThreadContext(settings);
PkiRealm realm = buildRealm(roleMapper, settings);
assertRealmUsageStats(realm, true, false, true, false);
threadContext.putTransient(PkiRealm.PKI_CERT_HEADER_NAME, new X509Certificate[] { certificate });
@ -374,6 +478,7 @@ public class PkiRealmTests extends ESTestCase {
.build();
final UserRoleMapper roleMapper = buildRoleMapper(Collections.emptySet(), token.dn());
final PkiRealm pkiRealm = buildRealm(roleMapper, realmSettings, otherRealm);
assertRealmUsageStats(pkiRealm, false, true, true, false);
AuthenticationResult result = authenticate(token, pkiRealm);
assertThat(result.getStatus(), equalTo(AuthenticationResult.Status.SUCCESS));
@ -388,6 +493,50 @@ public class PkiRealmTests extends ESTestCase {
assertThat(result.getUser(), sameInstance(lookupUser2));
}
public void testX509AuthenticationTokenOrdered() throws Exception {
X509Certificate[] mockCertChain = new X509Certificate[2];
mockCertChain[0] = mock(X509Certificate.class);
when(mockCertChain[0].getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
mockCertChain[1] = mock(X509Certificate.class);
when(mockCertChain[1].getSubjectX500Principal()).thenReturn(new X500Principal("CN=Not Test, OU=elasticsearch, O=org"));
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new X509AuthenticationToken(mockCertChain));
assertThat(e.getMessage(), is("certificates chain array is not ordered"));
}
private void assertRealmUsageStats(Realm realm, Boolean hasTruststore, Boolean hasAuthorizationRealms,
Boolean hasDefaultUsernamePattern, Boolean isAuthenticationDelegated) throws Exception {
final PlainActionFuture<Map<String, Object>> future = new PlainActionFuture<>();
realm.usageStats(future);
Map<String, Object> usage = future.get();
assertThat(usage.get("has_truststore"), is(hasTruststore));
assertThat(usage.get("has_authorization_realms"), is(hasAuthorizationRealms));
assertThat(usage.get("has_default_username_pattern"), is(hasDefaultUsernamePattern));
assertThat(usage.get("is_authentication_delegated"), is(isAuthenticationDelegated));
}
public void testX509AuthenticationTokenCaching() throws Exception {
X509Certificate[] mockCertChain = new X509Certificate[2];
mockCertChain[0] = mock(X509Certificate.class);
when(mockCertChain[0].getSubjectX500Principal()).thenReturn(new X500Principal("CN=Test, OU=elasticsearch, O=org"));
when(mockCertChain[0].getIssuerX500Principal()).thenReturn(new X500Principal("CN=Test CA, OU=elasticsearch, O=org"));
when(mockCertChain[0].getEncoded()).thenReturn(randomByteArrayOfLength(2));
mockCertChain[1] = mock(X509Certificate.class);
when(mockCertChain[1].getSubjectX500Principal()).thenReturn(new X500Principal("CN=Test CA, OU=elasticsearch, O=org"));
when(mockCertChain[1].getEncoded()).thenReturn(randomByteArrayOfLength(3));
BytesKey cacheKey = PkiRealm.computeTokenFingerprint(new X509AuthenticationToken(mockCertChain));
BytesKey sameCacheKey = PkiRealm
.computeTokenFingerprint(new X509AuthenticationToken(new X509Certificate[] { mockCertChain[0], mockCertChain[1] }));
assertThat(cacheKey, is(sameCacheKey));
BytesKey cacheKeyClient = PkiRealm.computeTokenFingerprint(new X509AuthenticationToken(new X509Certificate[] { mockCertChain[0] }));
assertThat(cacheKey, is(not(cacheKeyClient)));
BytesKey cacheKeyRoot = PkiRealm.computeTokenFingerprint(new X509AuthenticationToken(new X509Certificate[] { mockCertChain[1] }));
assertThat(cacheKey, is(not(cacheKeyRoot)));
assertThat(cacheKeyClient, is(not(cacheKeyRoot)));
}
static X509Certificate readCert(Path path) throws Exception {
try (InputStream in = Files.newInputStream(path)) {
CertificateFactory factory = CertificateFactory.getInstance("X.509");

View File

@ -0,0 +1,35 @@
= Certificate Chain details
This document details the steps used to create the certificate chain in this directory.
The chain has a length of 3: the Root CA, the Intermediate CA and the Client Certificate.
All openssl commands use the same configuration file, albeit different sections of it.
The OpenSSL Configuration file is located in this directory as `openssl_config.cnf`.
== Instructions on generating self-signed Root CA
The self-signed Root CA, 'testRootCA.crt', and its associated private key in this directory
have been generated using the following openssl commands.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testRootCA.key 2048
openssl req -x509 -new -key testRootCA.key -days 1460 -subj "/CN=Elasticsearch Test Root CA/OU=elasticsearch/O=org" -out testRootCA.crt -config ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------
== Instructions on generating the Intermediate CA
The `testIntermediateCA.crt` CA certificate is "issued" by the `testRootCA.crt`.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testIntermediateCA.key 2048
openssl req -new -key testIntermediateCA.key -subj "/CN=Elasticsearch Test Intermediate CA/OU=Elasticsearch/O=org" -out testIntermediateCA.csr -config ./openssl_config.cnf
openssl x509 -req -in testIntermediateCA.csr -CA testRootCA.crt -CAkey testRootCA.key -CAcreateserial -out testIntermediateCA.crt -days 1460 -sha256 -extensions v3_ca -extfile ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------
== Instructions on generating the Client Certificate
The `testClient.crt` end entity certificate is "issued" by the `testIntermediateCA.crt`.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl genrsa -out testClient.key 2048
openssl req -new -key testClient.key -subj "/CN=Elasticsearch Test Client/OU=Elasticsearch/O=org" -out testClient.csr -config ./openssl_config.cnf
openssl x509 -req -in testClient.csr -CA testIntermediateCA.crt -CAkey testIntermediateCA.key -CAcreateserial -out testClient.crt -days 1460 -sha256 -extensions usr_cert -extfile ./openssl_config.cnf
-----------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View File

@ -0,0 +1,185 @@
####################################################################
# CA Definition
[ ca ]
default_ca = CA_default # The default ca section
####################################################################
# Per the above, this is where we define CA values
[ CA_default ]
# By default we use "user certificate" extensions when signing
x509_extensions = usr_cert # The extentions to add to the cert
# Honor extensions requested of us
copy_extensions = copy
# Comment out the following two lines for the "traditional"
# (and highly broken) format.
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
# so this is commented out by default to leave a V1 CRL.
# crlnumber must also be commented out to leave a V1 CRL.
#crl_extensions = crl_ext
default_days = 1460 # how long to certify for
default_md = sha256 # which md to use.
preserve = no # keep passed DN ordering
# A few difference way of specifying how similar the request should look
# For type CA, the listed attributes must be the same, and the optional
# and supplied fields are just that :-)
policy = policy_anything
####################################################################
# The default policy for the CA when signing requests, requires some
# resemblence to the CA cert
#
[ policy_match ]
countryName = match # Must be the same as the CA
stateOrProvinceName = match # Must be the same as the CA
organizationName = match # Must be the same as the CA
organizationalUnitName = optional # not required
commonName = supplied # must be there, whatever it is
emailAddress = optional # not required
####################################################################
# An alternative policy not referred to anywhere in this file. Can
# be used by specifying '-policy policy_anything' to ca(8).
#
[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
####################################################################
# This is where we define how to generate CSRs
[ req ]
default_bits = 2048
default_keyfile = privkey.pem
distinguished_name = req_distinguished_name # where to get DN for reqs
attributes = req_attributes # req attributes
x509_extensions = v3_ca # The extentions to add to self signed certs
req_extensions = v3_req # The extensions to add to req's
# This sets a mask for permitted string types. There are several options.
# default: PrintableString, T61String, BMPString.
# pkix : PrintableString, BMPString.
# utf8only: only UTF8Strings.
# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
# MASK:XXXX a literal mask value.
# WARNING: current versions of Netscape crash on BMPStrings or UTF8Strings
# so use this option with caution!
string_mask = nombstr
####################################################################
# Per "req" section, this is where we define DN info
[ req_distinguished_name ]
0.organizationName = Organization Name (company)
0.organizationName_default = org
organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = elasticsearch
commonName = Common Name (hostname, IP, or your name)
commonName_default = Elasticsearch Test Certificate
commonName_max = 64
####################################################################
# We don't want these, but the section must exist
[ req_attributes ]
#challengePassword = A challenge password
#challengePassword_min = 4
#challengePassword_max = 20
#unstructuredName = An optional company name
####################################################################
# Extensions for when we sign normal certs (specified as default)
[ usr_cert ]
# User certs aren't CAs, by definition
basicConstraints=CA:false
# Here are some examples of the usage of nsCertType. If it is omitted
# the certificate can be used for anything *except* object signing.
# This is OK for an SSL server.
#nsCertType = server
# For an object signing certificate this would be used.
#nsCertType = objsign
# For normal client use this is typical
#nsCertType = client, email
# and for everything including object signing:
#nsCertType = client, email, objsign
# This is typical in keyUsage for a client certificate.
#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
#subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
#subjectAltName=email:move
####################################################################
# Extension for requests
[ v3_req ]
basicConstraints = CA:FALSE
# PKIX recommendation.
subjectKeyIdentifier = hash
subjectAltName = @alt_names
####################################################################
# An alternative section of extensions, not referred to anywhere
# else in the config. We'll use this via '-extensions v3_ca' when
# using ca(8) to sign another CA.
#
[ v3_ca ]
# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier = keyid,issuer
# This is what PKIX recommends but some broken software chokes on critical
# extensions.
#basicConstraints = critical,CA:true
# So we do this instead.
basicConstraints = CA:true
# Key usage: this is typical for a CA certificate. However since it will
# prevent it being used as an test self-signed certificate it is best
# left out by default.
# keyUsage = cRLSign, keyCertSign
# Some might want this also
# nsCertType = sslCA, emailCA
# Include email address in subject alt name: another PKIX recommendation
#subjectAltName=email:move
# Copy issuer details
#issuerAltName=issuer:copy
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
DNS.2 = localhost.localdomain
DNS.3 = localhost4
DNS.4 = localhost4.localdomain4
DNS.5 = localhost6
DNS.6 = localhost6.localdomain6
IP.1 = 127.0.0.1
IP.2 = ::1

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIJAIxTS7Qdho9jMA0GCSqGSIb3DQEBCwUAMFMxKzApBgNV
BAMTIkVsYXN0aWNzZWFyY2ggVGVzdCBJbnRlcm1lZGlhdGUgQ0ExFjAUBgNVBAsT
DUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzAeFw0xOTA3MTkxMzMzNDFaFw0y
MzA3MTgxMzMzNDFaMEoxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGll
bnQxFjAUBgNVBAsTDUVsYXN0aWNzZWFyY2gxDDAKBgNVBAoTA29yZzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANHgMX2aX8t0nj4sGLNuKISmmXIYCj9R
wRqS7L03l9Nng7kOKnhHu/nXDt7zMRJyHj+q6FAt5khlavYSVCQyrDybRuA5z31g
OdqXerrjs2OXS5HSHNvoDAnHFsaYX/5geMewVTtc/vqpd7Ph/QtaKfmG2FK0JNQo
0k24tcgCIcyMtBh6BA70yGBM0OT8GdOgd/d/mA7mRhaxIUMNYQzRYRsp4hMnnWoO
TkR5Q8KSO3MKw9dPSpPe8EnwtJE10S3s5aXmgytru/xQqrFycPBNj4KbKVmqMP0G
60CzXik5pr2LNvOFz3Qb6sYJtqeZF+JKgGWdaTC89m63+TEnUHqk0lcCAwEAAaNN
MEswCQYDVR0TBAIwADAdBgNVHQ4EFgQU/+aAD6Q4mFq1vpHorC25/OY5zjcwHwYD
VR0jBBgwFoAU8siFCiMiYZZm/95qFC75AG/LRE0wDQYJKoZIhvcNAQELBQADggEB
AIRpCgDLpvXcgDHUk10uhxev21mlIbU+VP46ANnCuj0UELhTrdTuWvO1PAI4z+Wb
DUxryQfOOXO9R6D0dE5yR56L/J7d+KayW34zU7yRDZM7+rXpocdQ1Ex8mjP9HJ/B
f56YZTBQJpXeDrKow4FvtkI3bcIMkqmbG16LHQXeG3RS4ds4S4wCnE2nA6vIn9y+
4R999q6y1VSBORrYULcDWxS54plHLEdiMr1vVallg82AGobS9GMcTL2U4Nx5IYZG
7sbTk3LrDxVpVg/S2wLofEdOEwqCeHug/iOihNLJBabEW6z4TDLJAVW5KCY1Dfhk
YlBfHn7vxKkfKoCUK/yLWWI=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0eAxfZpfy3SePiwYs24ohKaZchgKP1HBGpLsvTeX02eDuQ4q
eEe7+dcO3vMxEnIeP6roUC3mSGVq9hJUJDKsPJtG4DnPfWA52pd6uuOzY5dLkdIc
2+gMCccWxphf/mB4x7BVO1z++ql3s+H9C1op+YbYUrQk1CjSTbi1yAIhzIy0GHoE
DvTIYEzQ5PwZ06B393+YDuZGFrEhQw1hDNFhGyniEyedag5ORHlDwpI7cwrD109K
k97wSfC0kTXRLezlpeaDK2u7/FCqsXJw8E2PgpspWaow/QbrQLNeKTmmvYs284XP
dBvqxgm2p5kX4kqAZZ1pMLz2brf5MSdQeqTSVwIDAQABAoIBAQDAjP767Ioc4LZZ
9h0HafaUlUDMs4+bPkd7OPcoNnv+AceRHZULW0zz0EIdfGM2OCrWYNfYz/Op0hpK
/s/hkfgBdriU+ZUKwyDxEu8Pzd6EbYdwlqPRgdihk92qgJv5hsro8jeQSibJFHf1
Ok3tf2BpRTTs08fCOl2P3vowMPyPa5Ho9bf4lzP8IsR2BZvoaev3za9ZWR6ZDzE6
EWkBBNgIU4aPn1IJ6dz2+rVtN6+xXET0eYSBEac3xMQaPWLEX0EDBYPW1d+mUva/
3lJvTrs3g8oyiTyVu0l9Yxdgox1mtgmrqqwxJ6XuouzImuXMMDXaz0K/E/+u2yPF
V6kRvWuJAoGBAPOnEgBC3ezl+x+47cgbwpy97uZhZmV9HkMrSH9DKDwC+t57TdGX
ypt2S/IS/vbPupFv0aHaWmJ6SN/HyTN4znwuulV3kE8mEpQzIPbluWfgQzT6ukJe
+YFI/+IXwIRBLA7khtfo01LGHSmLTENsnd/aoRySY3K6zJz36Ys3vFdjAoGBANyC
7rF5YjPdgsAgOT7EboNGkc8UuW/Sh3xRp0c4Y+PBenf60yA5XkRJLYR4sZDjWTr0
aKBY7Y8r+59U+bBrwUuhhoW08JZ/SBWja05+4DhH0ToA3vtbPv9lRyQfkF1DdBkn
XpyM2vaJE5M454acwnKJ81AyoueYtZ8pD3Q7c219AoGAJ+F1wdMwDgGKvCOB0Boz
HYK9IrpYj04OcQIZqLLuV/xI4befAiptQEr5nVLcprtTl1CNKIfb+Xh4iyBhX2pr
qcngN/MNDNd3fQhtYdwyH72GYpqTeB+hiTbQo0ot+bfNJVbkd1ylkkvZJB6nyfVy
VdysOEgBvRq0OREfCemCi28CgYEAoF1EE6NQDKICTZDhsMkQCb5PmcbbmPwFdh63
xW64DlGNrCWoVt4BtS12wck4cUM1iE9oq3wgv6df5Z7ZuziSKVt9xk0xTnGgTcQ7
7KkOjT+FZGZvw2K3bOsNkrK1vW2pyAU+pCE3uGU17DJNBjOIod27Kk649C61ntsw
lvoJVs0CgYBLr9pzBRPyD5/lM9hm2EI7ITa+fVcu3V3bJfXENHKzpb0lB2fhl0PI
swpiU8RUEKWyjBuHsdQdxg7AgFi/7s+SX7KLo4cudDRd73iiXYdNGB7R0/MAG8Jl
/lMXn14noS4trA8fNGGg/2fANTBtLTbOX9i4s7clAo8ETywQ33owug==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEBTCCAu2gAwIBAgIJAIx9twpbtGkCMA0GCSqGSIb3DQEBCwUAMEsxIzAhBgNV
BAMTGkVsYXN0aWNzZWFyY2ggVGVzdCBSb290IENBMRYwFAYDVQQLEw1lbGFzdGlj
c2VhcmNoMQwwCgYDVQQKEwNvcmcwHhcNMTkwNzE5MTMzMjM0WhcNMjMwNzE4MTMz
MjM0WjBTMSswKQYDVQQDEyJFbGFzdGljc2VhcmNoIFRlc3QgSW50ZXJtZWRpYXRl
IENBMRYwFAYDVQQLEw1FbGFzdGljc2VhcmNoMQwwCgYDVQQKEwNvcmcwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnJ2KTJZnQzOt0uUf+5oLNcvDLnnWY
LzXZpOOX666Almwx+PVkDxkiGSe0QB9RWJqHSrsP1ryGIeCIzGMOctLt6QA7Peee
HdrKqOQgN620nDSd2EZ3s0Iddh1Ns/lfTtBJCP/03suaktm7j8EYKAyOlTIUhiKm
sTFlxPUSKjbtR4wR1ljnKN8X+j/ghr9mWhQrMR9rsGFObU8DQFho2Ti90C4HoMNU
dy4j+2G3VVpaq4he4/4CbPrWQQ3dKGpzVAngIuAv4eQ/y88EHAFwutxQZWAew4Va
5y3O112acSb9oC7g0NHQcBnos/WIChF5ki8V3LFnxN7jYvUUk9YxfA8hAgMBAAGj
geMwgeAwHQYDVR0OBBYEFPLIhQojImGWZv/eahQu+QBvy0RNMB8GA1UdIwQYMBaA
FM4SyNzpz82ihQ160zrLUVaWfI+1MAwGA1UdEwQFMAMBAf8wgY8GA1UdEQSBhzCB
hIIJbG9jYWxob3N0ghVsb2NhbGhvc3QubG9jYWxkb21haW6CCmxvY2FsaG9zdDSC
F2xvY2FsaG9zdDQubG9jYWxkb21haW40ggpsb2NhbGhvc3Q2ghdsb2NhbGhvc3Q2
LmxvY2FsZG9tYWluNocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0B
AQsFAAOCAQEAMkh4nUi2yt5TX+ryBWaaA4/2ZOsxSeec5E1EjemPMUWGzFipV1YY
k/mpv51E+BbPgtmGMG8Win/PETKYuX8D+zPauFEmJmyJmm5B4mr1406RWERqNDql
36sOw89G0mDT/wIB4tkNdh830ml+d75aRVVB4X5pFAE8ZzI3g4OW4YxT3ZfUEhDl
QeGVatobvIaX8KpNSevjFAFuQzSgj61VXI+2+UIRV4tJP2xEqu5ISuArHcGhvNlS
bU3vZ80tTCa0tHyJrVqaqtQ23MDBzYPj6wJ/pvBQWAgZKnC3qJgXlJ9des117I1g
J98AXCDGu5LBW/p2C9VpSktpnfzsX4NHqg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEApydikyWZ0MzrdLlH/uaCzXLwy551mC812aTjl+uugJZsMfj1
ZA8ZIhkntEAfUViah0q7D9a8hiHgiMxjDnLS7ekAOz3nnh3ayqjkIDettJw0ndhG
d7NCHXYdTbP5X07QSQj/9N7LmpLZu4/BGCgMjpUyFIYiprExZcT1Eio27UeMEdZY
5yjfF/o/4Ia/ZloUKzEfa7BhTm1PA0BYaNk4vdAuB6DDVHcuI/tht1VaWquIXuP+
Amz61kEN3Shqc1QJ4CLgL+HkP8vPBBwBcLrcUGVgHsOFWuctztddmnEm/aAu4NDR
0HAZ6LP1iAoReZIvFdyxZ8Te42L1FJPWMXwPIQIDAQABAoIBABp4z1C0dL6vpV5v
9Wn2AaMd3+qvZro6R9H3HiAyMAmnSO1FGz/EcFuJFlOikBMm8BobCLMCdAreFJw1
mj5wit0ouGOpcyQEYGEWDELZ7oWa825IESjl18OosA1dQlIIvk3Cwh56pk4NkbP1
mUQFG6/9CthbQeOaTlNqtNEypE5Bc+JGbQaUhRP6tF+Rxnpys2nIJt/Vp9khw0Du
K7Z6astunhfPDwLFGwHhflc6re1B+mxpLKTDHCcydJo2Kuh/LuuEtPkE5Ar4LwQk
D+/61iZHC4B8/4IkBlAsgCJ1B18L6JdTbSYeVlepkSkJML5t6z+cvt5VcObF7F8X
pPZn+kECgYEA2NaB0eshWNnHTMRv+sE92DCv0M7uV1eKtaopxOElAKJ/J2gpqcTh
GzdTVRg1M2LgVNk97ViL5bsXaVStRe085m8oA0bI9WbIoQRUFp40dRFRUjl+4TN0
pdxXL4VmQMWuwlO6p8/JY8sInnHVCT+2z8lek8P3bdtTQZV9OZQTn0kCgYEAxVe8
obJdnUSXuRDWg588TW35PNqOTJcerIU6eRKwafvCcrhMoX62Xbv6y6kKXndW/JuW
AbfSNiAOV+HGUbf8Xc54Xzk2mouoJA0S0tJ040jqOkFOaKIxYQudTU8y9bTXNsAk
oX3wOhlt2q9xffAK1gYffP5XPXnYnsb8qaMIeRkCgYBM9yaxOgJmJTbGmtscaEbp
W66sMScMPXhwruuQhFG7/fGgLSrMpaM5I9QiWitYB/qUY1/FxS4y5suSiYnPTjvV
lxLexttBr6/65yxpstHv06vHwby1dqwqyyDvLyxyRTiYpVuVgP18vG5cvw7c746W
BmXZkS9cAQN2Pfdq3pJwcQKBgEbCZd2owg5hCPIPyosZbpro4uRiDYIC8bm0b7n3
7I+j+R3/XWLOt382pv+dlh03N1aORyRIkDReHCaAywaELRZJsTmbnyudBeYfVe+I
DOduPqYywnWcKo58hqOw0Tnu5Pg5vyi0qo16jrxKCiy5BHmnamT8IbXmWbjc6r28
uo4JAoGAfAPvPJ2fV5vpzr4LPoVyaSiFj414D+5XYxX6CWpdTryelpP2Rs1VfJ1a
7EusUtWs26pAKwttDY4yoTvog7rrskgtXzisaoNMDbH/PfsoqjMnnIgakvKmHpUM
l6E1ecWFExEg5v6yvmxFC7JIUzIYOoysWu3X44G8rQ+vDQNRFZQ=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID/TCCAuWgAwIBAgIJAIAPVUXOUQDNMA0GCSqGSIb3DQEBCwUAMEsxIzAhBgNV
BAMTGkVsYXN0aWNzZWFyY2ggVGVzdCBSb290IENBMRYwFAYDVQQLEw1lbGFzdGlj
c2VhcmNoMQwwCgYDVQQKEwNvcmcwHhcNMTkwNzE5MTMzMjIwWhcNMjMwNzE4MTMz
MjIwWjBLMSMwIQYDVQQDExpFbGFzdGljc2VhcmNoIFRlc3QgUm9vdCBDQTEWMBQG
A1UECxMNZWxhc3RpY3NlYXJjaDEMMAoGA1UEChMDb3JnMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAzIgn8r2kirt90id0uoi6YEGBPx+XDzthLbLsN+M0
nXhj40OVcGPiww+cre14bJr0M6MG4CvFjRJc92RoVrE8+7XOKt0bgiHeVM+b0LEh
wVMH9koararPVMo0CjCMN4ChHMOWKBPUNZswvk+pFC+QbTcfgQLycqh+lTB1O6l3
hPnmunEqhLIj9ke3FwA326igdb+16EbKYVL2c5unNoC5ZMc5Z9bnn4/GNXptkHhy
+SvG7IZKW2pAzei3Df/n47ZhJfQKERUCe9eO7b/ZmTEzAzYj9xucE5lYcpkOZd6g
IMU3vXe4FeD/BM4sOLkKTtMejiElEecxw8cLI9Nji/0y1wIDAQABo4HjMIHgMB0G
A1UdDgQWBBTOEsjc6c/NooUNetM6y1FWlnyPtTAfBgNVHSMEGDAWgBTOEsjc6c/N
ooUNetM6y1FWlnyPtTAMBgNVHRMEBTADAQH/MIGPBgNVHREEgYcwgYSCCWxvY2Fs
aG9zdIIVbG9jYWxob3N0LmxvY2FsZG9tYWluggpsb2NhbGhvc3Q0ghdsb2NhbGhv
c3Q0LmxvY2FsZG9tYWluNIIKbG9jYWxob3N0NoIXbG9jYWxob3N0Ni5sb2NhbGRv
bWFpbjaHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEB
ACHjwoDJILv77sQ5QN6SoAp6GYqiC9/doDIzDFCd/WP7G8EbaosHM6jM7NbrlK3g
PNTzuY1pLPoI3YJSO4Al/UfzEffaYSbZC2QZG9F6fUSWhvR+nxzPSXWkjzIInv1j
pPMgnUl6oJaUbsSR/evtvWNSxrM3LewkRTOoktkXM6SjTUHjdP6ikrkrarrWZgzr
K30BqGL6kDSv9LkyXe6RSgQDtQe51Yut+lKGCcy8AoEwG/3cjb7XnrWcFsJXjYbf
4m3QsS8yHU/O/xgyvVHOfki+uGVepzSjdzDMLE1GBkju05NR2eJZ8omj/QiJa0+z
1d/AOKExvWvo1yQ28ORcwo4=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAzIgn8r2kirt90id0uoi6YEGBPx+XDzthLbLsN+M0nXhj40OV
cGPiww+cre14bJr0M6MG4CvFjRJc92RoVrE8+7XOKt0bgiHeVM+b0LEhwVMH9koa
rarPVMo0CjCMN4ChHMOWKBPUNZswvk+pFC+QbTcfgQLycqh+lTB1O6l3hPnmunEq
hLIj9ke3FwA326igdb+16EbKYVL2c5unNoC5ZMc5Z9bnn4/GNXptkHhy+SvG7IZK
W2pAzei3Df/n47ZhJfQKERUCe9eO7b/ZmTEzAzYj9xucE5lYcpkOZd6gIMU3vXe4
FeD/BM4sOLkKTtMejiElEecxw8cLI9Nji/0y1wIDAQABAoIBAQC6LMnoPFW1brs1
+3JWhTTZf2btlYzEcbGgjnhU2v0+xaJu8UrrFhEIq4JcE4gFm/rjsecFUPKu2eND
0eLj3st699+lxsRObRPbMWtMyJ/IQRNDTesA4DV/odtC1zQbJXwCGcrpyjrlXNE+
unZWiIE32PBVV+BnHBa1KHneCAFiSRLrySAiDAnTIJxB6ufweoxevLoJPPNLlbo7
H2jv6g1Som/Imjhof4KhD/1Q04Sed2wScSS/7Bz38eO68HG4NMFY+M2/cLzrbflg
QdeKHNhoIGnSFMEW5TCVlI4qrP8zvPPdZmLOMBT+Ocm3pc5xDAPwFYCe8wH1DVn+
b3sVpwu5AoGBAOhFA7gUDZjRBkNAqJfbUdhdWSslePQsjeTKsu5rc4gk2aiL4bZ4
fxG0Dq1hX7FjAmYrGqnsXsbxxDnCkhXGH1lY73kF0Zzwr2Pg1yRHyn1nCinhD4g4
G2vBr37QtWn4wS/L7V//D3xrcCTG3QgAmvZZ99tYgqlmnUzmawdZ8kQ7AoGBAOFt
qg7sTSNWVpKkfkyX2NXvBMt5e3Qcwnge2pX+SBgljwjNUwSSMLwxdBDSyDXIhk8W
s4pJLtMDJsT/2WBKC9WJm9m3gc7yYZznLJ+5YPcieXHGGNXCRldPePhTIjnL591H
CSXoc3BZ2iKK745BYuPqSuLb2XfE3/hwoaFR4S4VAoGAQ6ywG7dECu2ELJ4vQSe2
3hq8u1SMvGAq66mfntYR8G4EORagqkDLjUXwLNY9Qnr9nPUcLLxhFQgmS0oEtHFo
eujtxU5Lt7Vs9OXy6XA9cHJQRMl9dAwc+TWSw5ld8kV3TEzXmevAAFlxcFW82vMK
M5MdI3zTfTYXyOst7hNoAjcCgYAhz/cgAeWYFU0q9a1UA7qsbAuGEZSo1997cPVM
ZjWeGZQYt+Np3hudPrWwCE2rc4Zhun/3j/6L+/8GsXGDddfMkbVktJet2ME3bZ1N
39phdzRMEnCLL3aphewZIy8RCDqhABSpMPKPuYp0f+5qofgZQ300BdHamxcVBp/X
uJZT+QKBgQDdJQd+QxfCb8BZ11fWtyWJWQWZMmyX2EEbAIMvYQP3xh8PHmw2JoiQ
VQ103bCkegJ1S7ubrGltdt8pyjN4rrByXJmxCe1Y/LSHIp9w8D3jaiLCRSk1EmBw
jXjnZoiJn3GV5jmbV10hzrn7jqRcwhYA5zuoE7qb604V7cPZLzHtog==
-----END RSA PRIVATE KEY-----

View File

@ -15,5 +15,5 @@ setup:
# This is fragile - it needs to be updated every time we add a new cluster/index privilege
# I would much prefer we could just check that specific entries are in the array, but we don't have
# an assertion for that
- length: { "cluster" : 28 }
- length: { "cluster" : 29 }
- length: { "index" : 16 }