Skip to content

Commit 27e874b

Browse files
bizybotYogesh Gaikwad
authored andcommitted
[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.
1 parent c6f55b2 commit 27e874b

File tree

6 files changed

+70
-29
lines changed

6 files changed

+70
-29
lines changed

x-pack/docs/en/security/authentication/configuring-kerberos-realm.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ POST _xpack/security/role_mapping/kerbrolemapping
165165
--------------------------------------------------
166166
// CONSOLE
167167

168+
In case you want to support Kerberos cross realm authentication you may
169+
need to map roles based on the Kerberos realm name. For such scenarios
170+
following are the additional user metadata available for role mapping:
171+
- `kerberos_realm` will be set to Kerberos realm name.
172+
- `kerberos_user_principal_name` will be set to user principal name from the Kerberos ticket.
173+
168174
For more information, see {stack-ov}/mapping-roles.html[Mapping users and groups to roles].
169175

170176
NOTE: The Kerberos realm supports

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.nio.file.Files;
3131
import java.nio.file.Path;
3232
import java.util.Collections;
33+
import java.util.HashMap;
3334
import java.util.List;
3435
import java.util.Map;
3536

@@ -58,6 +59,9 @@
5859
*/
5960
public final class KerberosRealm extends Realm implements CachingRealm {
6061

62+
public static final String KRB_METADATA_REALM_NAME_KEY = "kerberos_realm";
63+
public static final String KRB_METADATA_UPN_KEY = "kerberos_user_principal_name";
64+
6165
private final Cache<String, User> userPrincipalNameToUserCache;
6266
private final NativeRoleMappingStore userRoleMapper;
6367
private final KerberosTicketValidator kerberosTicketValidator;
@@ -151,8 +155,7 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
151155
kerberosTicketValidator.validateTicket((byte[]) kerbAuthnToken.credentials(), keytabPath, enableKerberosDebug,
152156
ActionListener.wrap(userPrincipalNameOutToken -> {
153157
if (userPrincipalNameOutToken.v1() != null) {
154-
final String username = maybeRemoveRealmName(userPrincipalNameOutToken.v1());
155-
resolveUser(username, userPrincipalNameOutToken.v2(), listener);
158+
resolveUser(userPrincipalNameOutToken.v1(), userPrincipalNameOutToken.v2(), listener);
156159
} else {
157160
/**
158161
* This is when security context could not be established may be due to ongoing
@@ -171,23 +174,8 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
171174
}, e -> handleException(e, listener)));
172175
}
173176

174-
/**
175-
* Usually principal names are in the form 'user/instance@REALM'. This method
176-
* removes '@REALM' part from the principal name if
177-
* {@link KerberosRealmSettings#SETTING_REMOVE_REALM_NAME} is {@code true} else
178-
* will return the input string.
179-
*
180-
* @param principalName user principal name
181-
* @return username after removal of realm
182-
*/
183-
protected String maybeRemoveRealmName(final String principalName) {
184-
if (this.removeRealmName) {
185-
int foundAtIndex = principalName.indexOf('@');
186-
if (foundAtIndex > 0) {
187-
return principalName.substring(0, foundAtIndex);
188-
}
189-
}
190-
return principalName;
177+
private String[] splitUserPrincipalName(final String userPrincipalName) {
178+
return userPrincipalName.split("@");
191179
}
192180

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

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

203+
final String[] userAndRealmName = splitUserPrincipalName(userPrincipalName);
204+
/*
205+
* Usually principal names are in the form 'user/instance@REALM'. If
206+
* KerberosRealmSettings#SETTING_REMOVE_REALM_NAME is true then remove
207+
* '@REALM' part from the user principal name to get username.
208+
*/
209+
final String username = (this.removeRealmName) ? userAndRealmName[0] : userPrincipalName;
210+
215211
if (delegatedRealms.hasDelegation()) {
216212
delegatedRealms.resolve(username, listener);
217213
} else {
218214
final User user = (userPrincipalNameToUserCache != null) ? userPrincipalNameToUserCache.get(username) : null;
219215
if (user != null) {
220216
listener.onResponse(AuthenticationResult.success(user));
221217
} else {
222-
buildUser(username, listener);
218+
final String realmName = (userAndRealmName.length > 1) ? userAndRealmName[1] : null;
219+
final Map<String, Object> metadata = new HashMap<>();
220+
metadata.put(KRB_METADATA_REALM_NAME_KEY, realmName);
221+
metadata.put(KRB_METADATA_UPN_KEY, userPrincipalName);
222+
buildUser(username, metadata, listener);
223223
}
224224
}
225225
}
226226

227-
private void buildUser(final String username, final ActionListener<AuthenticationResult> listener) {
228-
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(username, null, Collections.emptySet(), null, this.config);
227+
private void buildUser(final String username, final Map<String, Object> metadata, final ActionListener<AuthenticationResult> listener) {
228+
final UserRoleMapper.UserData userData = new UserRoleMapper.UserData(username, null, Collections.emptySet(), metadata, this.config);
229229
userRoleMapper.resolveRoles(userData, ActionListener.wrap(roles -> {
230-
final User computedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true);
230+
final User computedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, userData.getMetadata(), true);
231231
if (userPrincipalNameToUserCache != null) {
232232
userPrincipalNameToUserCache.put(username, computedUser);
233233
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmAuthenticateFailedTests.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import java.nio.charset.StandardCharsets;
2626
import java.nio.file.Path;
2727
import java.util.Collections;
28+
import java.util.HashMap;
2829
import java.util.List;
30+
import java.util.Map;
2931

3032
import javax.security.auth.login.LoginException;
3133

@@ -86,7 +88,10 @@ public void testAuthenticateDifferentFailureScenarios() throws LoginException, G
8688
assertThat(result, is(notNullValue()));
8789
if (validTicket) {
8890
final String expectedUsername = maybeRemoveRealmName(username);
89-
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
91+
final Map<String, Object> metadata = new HashMap<>();
92+
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
93+
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
94+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
9095
assertSuccessAuthenticationResult(expectedUser, outToken, result);
9196
} else {
9297
assertThat(result.getStatus(), is(equalTo(AuthenticationResult.Status.TERMINATE)));

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import java.io.IOException;
1919
import java.nio.file.Path;
2020
import java.util.Arrays;
21+
import java.util.HashMap;
2122
import java.util.List;
23+
import java.util.Map;
2224

2325
import javax.security.auth.login.LoginException;
2426

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

4244
final String expectedUsername = maybeRemoveRealmName(username);
43-
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
45+
final Map<String, Object> metadata = new HashMap<>();
46+
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
47+
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
48+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
4449
final byte[] decodedTicket = randomByteArrayOfLength(10);
4550
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
4651
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
@@ -72,7 +77,10 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException
7277
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
7378
mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(authNUsername, outToken), null);
7479
final String expectedUsername = maybeRemoveRealmName(authNUsername);
75-
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
80+
final Map<String, Object> metadata = new HashMap<>();
81+
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(authNUsername));
82+
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, authNUsername);
83+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
7684

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

112120
final String expectedUsername = maybeRemoveRealmName(username);
113-
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
121+
final Map<String, Object> metadata = new HashMap<>();
122+
metadata.put(KerberosRealm.KRB_METADATA_REALM_NAME_KEY, realmName(username));
123+
metadata.put(KerberosRealm.KRB_METADATA_UPN_KEY, username);
124+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, metadata, true);
114125
final byte[] decodedTicket = randomByteArrayOfLength(10);
115126
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
116127
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ protected String randomPrincipalName() {
160160
if (withInstance) {
161161
principalName.append("/").append(randomAlphaOfLength(5));
162162
}
163+
principalName.append("@");
163164
principalName.append(randomAlphaOfLength(5).toUpperCase(Locale.ROOT));
164165
return principalName.toString();
165166
}
@@ -183,6 +184,19 @@ protected String maybeRemoveRealmName(final String principalName) {
183184
return principalName;
184185
}
185186

187+
/**
188+
* Extracts and returns realm part from the principal name.
189+
* @param principalName user principal name
190+
* @return realm name if found else returns {@code null}
191+
*/
192+
protected String realmName(final String principalName) {
193+
String[] values = principalName.split("@");
194+
if (values.length > 1) {
195+
return values[1];
196+
}
197+
return null;
198+
}
199+
186200
/**
187201
* Write content to provided keytab file.
188202
*

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
import java.util.Arrays;
3939
import java.util.Collections;
4040
import java.util.EnumSet;
41+
import java.util.HashMap;
4142
import java.util.Locale;
43+
import java.util.Map;
4244
import java.util.Set;
4345

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

0 commit comments

Comments
 (0)