Skip to content

Commit

Permalink
[Kerberos] Add realm name & UPN to user metadata (#33338)
Browse files Browse the repository at this point in the history
We have a Kerberos setting to remove realm part from the user
principal name (remove_realm_name). If this is true then
the realm name is removed to form username but in the process,
the realm name is lost. For scenarios like Kerberos cross-realm
authentication, one could make use of the realm name to determine
role mapping for users coming from different realms.
This commit adds user metadata for kerberos_realm and
kerberos_user_principal_name.
  • Loading branch information
bizybot authored Sep 14, 2018
1 parent 8ae1eeb commit d810f1b
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ POST _xpack/security/role_mapping/kerbrolemapping
--------------------------------------------------
// CONSOLE

In case you want to support Kerberos cross realm authentication you may
need to map roles based on the Kerberos realm name. For such scenarios
following are the additional user metadata available for role mapping:
- `kerberos_realm` will be set to Kerberos realm name.
- `kerberos_user_principal_name` will be set to user principal name from the Kerberos ticket.

For more information, see {stack-ov}/mapping-roles.html[Mapping users and groups to roles].

NOTE: The Kerberos realm supports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -58,6 +59,9 @@
*/
public final class KerberosRealm extends Realm implements CachingRealm {

public static final String KRB_METADATA_REALM_NAME_KEY = "kerberos_realm";
public static final String KRB_METADATA_UPN_KEY = "kerberos_user_principal_name";

private final Cache<String, User> userPrincipalNameToUserCache;
private final NativeRoleMappingStore userRoleMapper;
private final KerberosTicketValidator kerberosTicketValidator;
Expand Down Expand Up @@ -151,8 +155,7 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
kerberosTicketValidator.validateTicket((byte[]) kerbAuthnToken.credentials(), keytabPath, enableKerberosDebug,
ActionListener.wrap(userPrincipalNameOutToken -> {
if (userPrincipalNameOutToken.v1() != null) {
final String username = maybeRemoveRealmName(userPrincipalNameOutToken.v1());
resolveUser(username, userPrincipalNameOutToken.v2(), listener);
resolveUser(userPrincipalNameOutToken.v1(), userPrincipalNameOutToken.v2(), listener);
} else {
/**
* This is when security context could not be established may be due to ongoing
Expand All @@ -171,23 +174,8 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
}, e -> handleException(e, listener)));
}

/**
* Usually principal names are in the form 'user/instance@REALM'. This method
* removes '@REALM' part from the principal name if
* {@link KerberosRealmSettings#SETTING_REMOVE_REALM_NAME} is {@code true} else
* will return the input string.
*
* @param principalName user principal name
* @return username after removal of realm
*/
protected String maybeRemoveRealmName(final String principalName) {
if (this.removeRealmName) {
int foundAtIndex = principalName.indexOf('@');
if (foundAtIndex > 0) {
return principalName.substring(0, foundAtIndex);
}
}
return principalName;
private String[] splitUserPrincipalName(final String userPrincipalName) {
return userPrincipalName.split("@");
}

private void handleException(Exception e, final ActionListener<AuthenticationResult> listener) {
Expand All @@ -205,29 +193,41 @@ private void handleException(Exception e, final ActionListener<AuthenticationRes
}
}

private void resolveUser(final String username, final String outToken, final ActionListener<AuthenticationResult> listener) {
private void resolveUser(final String userPrincipalName, final String outToken, final ActionListener<AuthenticationResult> listener) {
// if outToken is present then it needs to be communicated with peer, add it to
// response header in thread context.
if (Strings.hasText(outToken)) {
threadPool.getThreadContext().addResponseHeader(WWW_AUTHENTICATE, NEGOTIATE_AUTH_HEADER_PREFIX + outToken);
}

final String[] userAndRealmName = splitUserPrincipalName(userPrincipalName);
/*
* Usually principal names are in the form 'user/instance@REALM'. If
* KerberosRealmSettings#SETTING_REMOVE_REALM_NAME is true then remove
* '@REALM' part from the user principal name to get username.
*/
final String username = (this.removeRealmName) ? userAndRealmName[0] : userPrincipalName;

if (delegatedRealms.hasDelegation()) {
delegatedRealms.resolve(username, listener);
} else {
final User user = (userPrincipalNameToUserCache != null) ? userPrincipalNameToUserCache.get(username) : null;
if (user != null) {
listener.onResponse(AuthenticationResult.success(user));
} else {
buildUser(username, listener);
final String realmName = (userAndRealmName.length > 1) ? userAndRealmName[1] : null;
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KRB_METADATA_REALM_NAME_KEY, realmName);
metadata.put(KRB_METADATA_UPN_KEY, userPrincipalName);
buildUser(username, metadata, listener);
}
}
}

private void buildUser(final String username, final ActionListener<AuthenticationResult> listener) {
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(username, null, Collections.emptySet(), null, this.config);
private void buildUser(final String username, final Map<String, Object> metadata, final ActionListener<AuthenticationResult> listener) {
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(username, null, Collections.emptySet(), metadata, this.config);
userRoleMapper.resolveRoles(userData, ActionListener.wrap(roles -> {
final User computedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true);
final User computedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, userData.getMetadata(), true);
if (userPrincipalNameToUserCache != null) {
userPrincipalNameToUserCache.put(username, computedUser);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginException;

Expand Down Expand Up @@ -86,7 +88,10 @@ public void testAuthenticateDifferentFailureScenarios() throws LoginException, G
assertThat(result, is(notNullValue()));
if (validTicket) {
final String expectedUsername = maybeRemoveRealmName(username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
assertSuccessAuthenticationResult(expectedUser, outToken, result);
} else {
assertThat(result.getStatus(), is(equalTo(AuthenticationResult.Status.TERMINATE)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginException;

Expand All @@ -40,7 +42,10 @@ public void testAuthenticateWithCache() throws LoginException, GSSException {
final KerberosRealm kerberosRealm = createKerberosRealm(username);

final String expectedUsername = maybeRemoveRealmName(username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
final byte[] decodedTicket = randomByteArrayOfLength(10);
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
Expand Down Expand Up @@ -72,7 +77,10 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(authNUsername, outToken), null);
final String expectedUsername = maybeRemoveRealmName(authNUsername);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(authNUsername));
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, authNUsername);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);

final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken(decodedTicket);
final User user1 = authenticateAndAssertResult(kerberosRealm, expectedUser, kerberosAuthenticationToken, outToken);
Expand Down Expand Up @@ -110,7 +118,10 @@ public void testAuthenticateWithValidTicketSucessAuthnWithUserDetailsWhenCacheDi
final KerberosRealm kerberosRealm = createKerberosRealm(username);

final String expectedUsername = maybeRemoveRealmName(username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
final byte[] decodedTicket = randomByteArrayOfLength(10);
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ protected String randomPrincipalName() {
if (withInstance) {
principalName.append("/").append(randomAlphaOfLength(5));
}
principalName.append("@");
principalName.append(randomAlphaOfLength(5).toUpperCase(Locale.ROOT));
return principalName.toString();
}
Expand All @@ -183,6 +184,19 @@ protected String maybeRemoveRealmName(final String principalName) {
return principalName;
}

/**
* Extracts and returns realm part from the principal name.
* @param principalName user principal name
* @return realm name if found else returns {@code null}
*/
protected String realmName(final String principalName) {
String[] values = principalName.split("@");
if (values.length > 1) {
return values[1];
}
return null;
}

/**
* Write content to provided keytab file.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.security.auth.login.LoginException;
Expand Down Expand Up @@ -71,7 +73,10 @@ public void testAuthenticateWithValidTicketSucessAuthnWithUserDetails() throws L
final String username = randomPrincipalName();
final KerberosRealm kerberosRealm = createKerberosRealm(username);
final String expectedUsername = maybeRemoveRealmName(username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
final Map<String, Object> metadata = new HashMap<>();
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
final byte[] decodedTicket = "base64encodedticket".getBytes(StandardCharsets.UTF_8);
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
Expand Down

0 comments on commit d810f1b

Please sign in to comment.