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

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

Merged
merged 5 commits into from
Sep 14, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -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:
- `realm` will be set to Kerberos realm name.
- `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 @@ -151,8 +152,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 +171,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 +190,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("realm", realmName);
metadata.put("user_principal_name", userPrincipalName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these need to be prefixed with kerberos_ (or similar). In particular, adding "realm" metadata that refers to a Kerberos realm rather than an ES realm feels like a problem.

There's precedent here - the LDAP realm uses ldap_dn and ldap_groups.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think the naming was too generic will change it. Thank you.

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("realm", realmName(username));
metadata.put("user_principal_name", 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("realm", realmName(username));
metadata.put("user_principal_name", 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("realm", realmName(authNUsername));
metadata.put("user_principal_name", 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("realm", realmName(username));
metadata.put("user_principal_name", 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 @@ -155,6 +155,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 @@ -177,4 +178,17 @@ 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;
}
}
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("realm", realmName(username));
metadata.put("user_principal_name", 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