Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pki_delegatee to User metadata #44873

Original file line number Diff line number Diff line change
@@ -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 -> {
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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;

@@ -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
@@ -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
@@ -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;
@@ -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 {
@@ -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");
@@ -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,
Original file line number Diff line number Diff line change
@@ -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;
@@ -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 {
@@ -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();
@@ -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();