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:
parent
b249e25bb4
commit
1ebee5bf9b
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
-----------------------------------------------------------------------------------------------------------
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
||||
|
|
|
@ -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[]
|
||||
|
|
|
@ -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/]
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
-----------------------------------------------------------------------------------------------------------
|
|
@ -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-----
|
|
@ -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
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue