Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ protected void doExecute(Task task, DelegatePkiAuthenticationRequest request,
ActionListener<DelegatePkiAuthenticationResponse> listener) {
final ThreadContext threadContext = threadPool.getThreadContext();
Authentication delegateeAuthentication = Authentication.getAuthentication(threadContext);
final X509AuthenticationToken x509DelegatedToken = new X509AuthenticationToken(
request.getCertificateChain().toArray(new X509Certificate[0]), true);
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(ACTION_NAME, request, x509DelegatedToken, ActionListener.wrap(authentication -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,14 @@ public void authenticate(AuthenticationToken authToken, ActionListener<Authentic
}

private void buildUser(X509AuthenticationToken token, String principal, ActionListener<AuthenticationResult> listener) {
final Map<String, Object> metadata = Map.of("pki_dn", token.dn());
final Map<String, Object> metadata;
if (token.isDelegated()) {
metadata = Map.of("pki_dn", token.dn(),
"pki_delegated_by_user", token.getDelegateeAuthentication().getUser().principal(),
"pki_delegated_by_realm", token.getDelegateeAuthentication().getAuthenticatedBy().getName());
} else {
metadata = Map.of("pki_dn", token.dn());
}
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(principal, token.dn(), Set.of(), metadata, config);
roleMapper.resolveRoles(userData, ActionListener.wrap(roles -> {
final User computedUser = new User(principal, roles.toArray(new String[roles.size()]), null, null, metadata, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
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;

Expand All @@ -16,21 +17,26 @@ public class X509AuthenticationToken implements AuthenticationToken {

private final String dn;
private final X509Certificate[] credentials;
private final boolean isDelegated;
private final Authentication delegateeAuthentication;
private String principal;

public X509AuthenticationToken(X509Certificate[] certificates) {
this(certificates, false);
this(certificates, null);
}

public X509AuthenticationToken(X509Certificate[] certificates, boolean isDelegated) {
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.isDelegated = isDelegated;
this.delegateeAuthentication = delegateeAuthentication;
}

public static X509AuthenticationToken delegated(X509Certificate[] certificates, Authentication delegateeAuthentication) {
Objects.requireNonNull(delegateeAuthentication);
return new X509AuthenticationToken(certificates, delegateeAuthentication);
}

@Override
Expand All @@ -57,6 +63,10 @@ public void clearCredentials() {
}

public boolean isDelegated() {
return isDelegated;
return delegateeAuthentication != null;
}

public Authentication getDelegateeAuthentication() {
return delegateeAuthentication;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
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.user.User;
Expand All @@ -27,11 +31,14 @@
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 {
Expand Down Expand Up @@ -96,13 +103,74 @@ public void testDelegatePki() throws Exception {
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"));
// 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"));
}
}

public void testDelegatePkiWithRoleMapping() throws Exception {
X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
X509Certificate rootCA = readCertForPkiDelegation("testRootCA.crt");
DelegatePkiAuthenticationRequest delegatePkiRequest;
if (randomBoolean()) {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA));
} else {
delegatePkiRequest = new DelegatePkiAuthenticationRequest(Arrays.asList(clientCertificate, intermediateCA, rootCA));
}
final RequestOptions testUserOptions = RequestOptions.DEFAULT.toBuilder()
.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME,
new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray())))
.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 testDelegatePkiFailure() throws Exception {
X509Certificate clientCertificate = readCertForPkiDelegation("testClient.crt");
X509Certificate intermediateCA = readCertForPkiDelegation("testIntermediateCA.crt");
Expand All @@ -117,14 +185,12 @@ public void testDelegatePkiFailure() throws Exception {
() -> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
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;
Expand Down Expand Up @@ -81,7 +83,10 @@ public void testTokenSupport() {

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], randomBoolean())), 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 {
Expand Down Expand Up @@ -286,7 +291,15 @@ public void testAuthenticationDelegationFailsWithoutTruststore() throws Exceptio

public void testAuthenticationDelegationSuccess() throws Exception {
X509Certificate certificate = readCert(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"));
X509AuthenticationToken delegatedToken = new X509AuthenticationToken(new X509Certificate[] { certificate }, true);
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();
Expand All @@ -306,11 +319,14 @@ public void testAuthenticationDelegationSuccess() throws Exception {
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 = new X509AuthenticationToken(new X509Certificate[] { certificate }, true);
X509AuthenticationToken delegatedToken = X509AuthenticationToken.delegated(new X509Certificate[] { certificate },
mock(Authentication.class));

UserRoleMapper roleMapper = buildRoleMapper();
MockSecureSettings secureSettings = new MockSecureSettings();
Expand Down