[Kerberos] Add realm name & UPN to user metadata (#33338)

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.
This commit is contained in:
Yogesh Gaikwad 2018-09-14 17:17:53 +10:00 committed by GitHub
parent 8ae1eeb303
commit d810f1b094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 70 additions and 29 deletions

View File

@ -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

View File

@ -30,6 +30,7 @@ import org.ietf.jgss.GSSException;
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;
@ -58,6 +59,9 @@ import static org.elasticsearch.xpack.security.authc.kerberos.KerberosAuthentica
*/
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;
@ -151,8 +155,7 @@ public final class KerberosRealm extends Realm implements CachingRealm {
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
@ -171,23 +174,8 @@ public final class KerberosRealm extends Realm implements CachingRealm {
}, 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) {
@ -205,13 +193,21 @@ public final class KerberosRealm extends Realm implements CachingRealm {
}
}
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 {
@ -219,15 +215,19 @@ public final class KerberosRealm extends Realm implements CachingRealm {
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);
}

View File

@ -25,7 +25,9 @@ import org.ietf.jgss.GSSException;
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;
@ -86,7 +88,10 @@ public class KerberosRealmAuthenticateFailedTests extends KerberosRealmTestCase
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)));

View File

@ -18,7 +18,9 @@ import org.ietf.jgss.GSSException;
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;
@ -40,7 +42,10 @@ public class KerberosRealmCacheTests extends KerberosRealmTestCase {
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());
@ -72,7 +77,10 @@ public class KerberosRealmCacheTests extends KerberosRealmTestCase {
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);
@ -110,7 +118,10 @@ public class KerberosRealmCacheTests extends KerberosRealmTestCase {
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());

View File

@ -160,6 +160,7 @@ public abstract class KerberosRealmTestCase extends ESTestCase {
if (withInstance) {
principalName.append("/").append(randomAlphaOfLength(5));
}
principalName.append("@");
principalName.append(randomAlphaOfLength(5).toUpperCase(Locale.ROOT));
return principalName.toString();
}
@ -183,6 +184,19 @@ public abstract class KerberosRealmTestCase extends ESTestCase {
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.
*

View File

@ -38,7 +38,9 @@ import java.nio.file.attribute.PosixFilePermissions;
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;
@ -71,7 +73,10 @@ public class KerberosRealmTests extends KerberosRealmTestCase {
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());