From d810f1b094e7629332d282ba3e403187ab7b17b3 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Fri, 14 Sep 2018 17:17:53 +1000 Subject: [PATCH] [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. --- .../configuring-kerberos-realm.asciidoc | 6 +++ .../authc/kerberos/KerberosRealm.java | 48 +++++++++---------- .../KerberosRealmAuthenticateFailedTests.java | 7 ++- .../kerberos/KerberosRealmCacheTests.java | 17 +++++-- .../authc/kerberos/KerberosRealmTestCase.java | 14 ++++++ .../authc/kerberos/KerberosRealmTests.java | 7 ++- 6 files changed, 70 insertions(+), 29 deletions(-) diff --git a/x-pack/docs/en/security/authentication/configuring-kerberos-realm.asciidoc b/x-pack/docs/en/security/authentication/configuring-kerberos-realm.asciidoc index 9e7ed476272..cc0863112c7 100644 --- a/x-pack/docs/en/security/authentication/configuring-kerberos-realm.asciidoc +++ b/x-pack/docs/en/security/authentication/configuring-kerberos-realm.asciidoc @@ -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 diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java index 9c531d3159f..0f47b6032f5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java @@ -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 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 listener) { @@ -205,13 +193,21 @@ public final class KerberosRealm extends Realm implements CachingRealm { } } - private void resolveUser(final String username, final String outToken, final ActionListener listener) { + private void resolveUser(final String userPrincipalName, final String outToken, final ActionListener 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 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 listener) { - final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(username, null, Collections.emptySet(), null, this.config); + private void buildUser(final String username, final Map metadata, final ActionListener 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); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmAuthenticateFailedTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmAuthenticateFailedTests.java index 7c5904d048a..dcb087ff147 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmAuthenticateFailedTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmAuthenticateFailedTests.java @@ -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 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))); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java index ee2e2675e18..2bef16883bb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java @@ -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 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 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 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()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java index 8f959a26bb8..4c0b77e320a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java @@ -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. * diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java index 1166e929341..3c7c3d3473f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java @@ -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 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());