Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9758afb
REST handler & javadoc
albertzaharovits Jul 18, 2019
9d6eac1
@code not @link between projects
albertzaharovits Jul 18, 2019
b178f63
Nits
albertzaharovits Jul 18, 2019
05b5209
X509 chain
albertzaharovits Jul 19, 2019
48e439c
Delegate PKI request and response
albertzaharovits Jul 19, 2019
56591f5
DelegatePkiAuthenticationRequestTests
albertzaharovits Jul 19, 2019
d18b34f
PkiAuthDelegationIntegTests
albertzaharovits Jul 19, 2019
f251bb2
PkiAuthnDeleg nits
albertzaharovits Jul 20, 2019
b5a52ed
Rest high level WIP
albertzaharovits Jul 22, 2019
91a034a
WIP
albertzaharovits Jul 22, 2019
e9df4a4
DelegatePkiAuthenticationRequestTests
albertzaharovits Jul 22, 2019
edac26a
DelegatePkiAuthenticationResponseTests
albertzaharovits Jul 22, 2019
1ec439c
Remaining SecurityDocumentationIT
albertzaharovits Jul 22, 2019
47931a9
Checkstyle
albertzaharovits Jul 22, 2019
6592b08
Checkstyle
albertzaharovits Jul 22, 2019
bd85324
Mark SecurityClient javadocs as TODO
albertzaharovits Jul 22, 2019
62765e4
SecurityDocumentationIT WIP
albertzaharovits Jul 23, 2019
6e09785
SecurityDocumentationIT and Bootstrap realm
albertzaharovits Jul 23, 2019
706c7e2
SecurityClient javadocs
albertzaharovits Jul 23, 2019
730f8d9
Delete srl files
albertzaharovits Jul 23, 2019
5f5eac7
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 23, 2019
b0b7020
Adjustment after Streamable is gone
albertzaharovits Jul 23, 2019
0686267
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 23, 2019
20ac395
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 24, 2019
49d3ffa
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 24, 2019
be88872
Update docs/java-rest/high-level/security/delegate-pki-authentication…
albertzaharovits Jul 29, 2019
adbc1ed
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 29, 2019
84798ec
Review
albertzaharovits Jul 29, 2019
6ecddd2
Tim's review
albertzaharovits Jul 31, 2019
db63b86
Nit
albertzaharovits Jul 31, 2019
3423df7
Test
albertzaharovits Jul 31, 2019
12e9d58
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Jul 31, 2019
faa81db
Merge branch 'proxied-pki' into security-pki-delegation-add-rest-hand…
albertzaharovits Aug 1, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions client/rest-high-level/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ processTestResources {
from({ zipTree(configurations.restSpec.singleFile) }) {
include 'rest-api-spec/api/**'
}
from(project(':client:rest-high-level').file('src/test/resources'))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added a certificate chain ( testClient.crt, testIntermediateCA.crt and testRootCA.crt files) as resources to these two projects.

}

dependencyLicenses {
Expand Down Expand Up @@ -115,6 +116,7 @@ if (isEclipse) {

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')
Expand All @@ -132,11 +134,18 @@ 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'),
password: System.getProperty('tests.rest.cluster.password', 'test-password')

extraConfigFile nodeCert.name, nodeCert
extraConfigFile nodeTrustStore.name, nodeTrustStore
extraConfigFile pkiTrustCert.name, pkiTrustCert
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
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;
Expand Down Expand Up @@ -969,4 +971,38 @@ public void invalidateApiKeyAsync(final InvalidateApiKeyRequest request, final R
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -221,6 +222,12 @@ static Request createToken(CreateTokenRequest createTokenRequest) throws IOExcep
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));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -58,6 +59,7 @@
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;
Expand All @@ -67,6 +69,8 @@

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 {

Expand Down Expand Up @@ -304,6 +308,18 @@ public void testCreateTokenWithClientCredentialsGrant() throws Exception {
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);
Expand Down
Loading