Skip to content

Commit 375954f

Browse files
authored
[Kerberos] Remove realm from principal name (#31928)
This commit adds support for removing realm name from the Kerberos principal name. The principal names in Kerberos are in the form primary/instance@realm. Since we will be supporting user lookups and depending on the scenario we may want to remove the REALM part and use the username for lookup or role mapping. This change adds a new setting with the default value false to control removing of realm name. Modified tests to randomly use this setting during testing.
1 parent 61e349f commit 375954f

File tree

8 files changed

+104
-28
lines changed

8 files changed

+104
-28
lines changed

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public final class KerberosRealmSettings {
2727
Setting.simpleString("keytab.path", Property.NodeScope);
2828
public static final Setting<Boolean> SETTING_KRB_DEBUG_ENABLE =
2929
Setting.boolSetting("krb.debug", Boolean.FALSE, Property.NodeScope);
30+
public static final Setting<Boolean> SETTING_REMOVE_REALM_NAME =
31+
Setting.boolSetting("remove_realm_name", Boolean.FALSE, Property.NodeScope);
3032

3133
// Cache
3234
private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20);
@@ -42,6 +44,7 @@ private KerberosRealmSettings() {
4244
* @return the valid set of {@link Setting}s for a {@value #TYPE} realm
4345
*/
4446
public static Set<Setting<?>> getSettings() {
45-
return Sets.newHashSet(HTTP_SERVICE_KEYTAB_PATH, CACHE_TTL_SETTING, CACHE_MAX_USERS_SETTING, SETTING_KRB_DEBUG_ENABLE);
47+
return Sets.newHashSet(HTTP_SERVICE_KEYTAB_PATH, CACHE_TTL_SETTING, CACHE_MAX_USERS_SETTING, SETTING_KRB_DEBUG_ENABLE,
48+
SETTING_REMOVE_REALM_NAME);
4649
}
4750
}

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public final class KerberosRealm extends Realm implements CachingRealm {
6262
private final ThreadPool threadPool;
6363
private final Path keytabPath;
6464
private final boolean enableKerberosDebug;
65+
private final boolean removeRealmName;
6566

6667
public KerberosRealm(final RealmConfig config, final NativeRoleMappingStore nativeRoleMappingStore, final ThreadPool threadPool) {
6768
this(config, nativeRoleMappingStore, new KerberosTicketValidator(), threadPool, null);
@@ -88,6 +89,7 @@ public KerberosRealm(final RealmConfig config, final NativeRoleMappingStore nati
8889
this.threadPool = threadPool;
8990
this.keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
9091
this.enableKerberosDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
92+
this.removeRealmName = KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(config.settings());
9193
}
9294

9395
@Override
@@ -126,7 +128,8 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
126128
kerberosTicketValidator.validateTicket((byte[]) kerbAuthnToken.credentials(), keytabPath, enableKerberosDebug,
127129
ActionListener.wrap(userPrincipalNameOutToken -> {
128130
if (userPrincipalNameOutToken.v1() != null) {
129-
buildUser(userPrincipalNameOutToken.v1(), userPrincipalNameOutToken.v2(), listener);
131+
final String username = maybeRemoveRealmName(userPrincipalNameOutToken.v1());
132+
buildUser(username, userPrincipalNameOutToken.v2(), listener);
130133
} else {
131134
/**
132135
* This is when security context could not be established may be due to ongoing
@@ -145,6 +148,25 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
145148
}, e -> handleException(e, listener)));
146149
}
147150

151+
/**
152+
* Usually principal names are in the form 'user/instance@REALM'. This method
153+
* removes '@REALM' part from the principal name if
154+
* {@link KerberosRealmSettings#SETTING_REMOVE_REALM_NAME} is {@code true} else
155+
* will return the input string.
156+
*
157+
* @param principalName user principal name
158+
* @return username after removal of realm
159+
*/
160+
protected String maybeRemoveRealmName(final String principalName) {
161+
if (this.removeRealmName) {
162+
int foundAtIndex = principalName.indexOf('@');
163+
if (foundAtIndex > 0) {
164+
return principalName.substring(0, foundAtIndex);
165+
}
166+
}
167+
return principalName;
168+
}
169+
148170
private void handleException(Exception e, final ActionListener<AuthenticationResult> listener) {
149171
if (e instanceof LoginException) {
150172
listener.onResponse(AuthenticationResult.terminate("failed to authenticate user, service login failure",

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void testAuthenticateWithNonKerberosAuthenticationToken() {
4242
}
4343

4444
public void testAuthenticateDifferentFailureScenarios() throws LoginException, GSSException {
45-
final String username = randomAlphaOfLength(5);
45+
final String username = randomPrincipalName();
4646
final String outToken = randomAlphaOfLength(10);
4747
final KerberosRealm kerberosRealm = createKerberosRealm(username);
4848
final boolean validTicket = rarely();
@@ -76,7 +76,9 @@ public void testAuthenticateDifferentFailureScenarios() throws LoginException, G
7676
assertThat(result.getStatus(), is(equalTo(AuthenticationResult.Status.CONTINUE)));
7777
} else {
7878
if (validTicket) {
79-
final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true);
79+
final String expectedUsername = maybeRemoveRealmName(username);
80+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null,
81+
true);
8082
assertSuccessAuthenticationResult(expectedUser, outToken, result);
8183
} else {
8284
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: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@
3636
public class KerberosRealmCacheTests extends KerberosRealmTestCase {
3737

3838
public void testAuthenticateWithCache() throws LoginException, GSSException {
39-
final String username = randomAlphaOfLength(5);
39+
final String username = randomPrincipalName();
4040
final String outToken = randomAlphaOfLength(10);
4141
final KerberosRealm kerberosRealm = createKerberosRealm(username);
4242

43-
final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true);
43+
final String expectedUsername = maybeRemoveRealmName(username);
44+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
4445
final byte[] decodedTicket = randomByteArrayOfLength(10);
4546
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
4647
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
@@ -62,7 +63,7 @@ public void testAuthenticateWithCache() throws LoginException, GSSException {
6263

6364
public void testCacheInvalidationScenarios() throws LoginException, GSSException {
6465
final String outToken = randomAlphaOfLength(10);
65-
final List<String> userNames = Arrays.asList(randomAlphaOfLength(5) + "@REALM", randomAlphaOfLength(5) + "@REALM");
66+
final List<String> userNames = Arrays.asList(randomPrincipalName(), randomPrincipalName());
6667
final KerberosRealm kerberosRealm = createKerberosRealm(userNames.toArray(new String[0]));
6768
verify(mockNativeRoleMappingStore).refreshRealmOnChange(kerberosRealm);
6869

@@ -71,7 +72,8 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException
7172
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
7273
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
7374
mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(authNUsername, outToken), null);
74-
final User expectedUser = new User(authNUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
75+
final String expectedUsername = maybeRemoveRealmName(authNUsername);
76+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
7577

7678
final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken(decodedTicket);
7779
final User user1 = authenticateAndAssertResult(kerberosRealm, expectedUser, kerberosAuthenticationToken, outToken);
@@ -81,7 +83,7 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException
8183
if (expireAll) {
8284
kerberosRealm.expireAll();
8385
} else {
84-
kerberosRealm.expire(expireThisUser);
86+
kerberosRealm.expire(maybeRemoveRealmName(expireThisUser));
8587
}
8688

8789
final User user2 = authenticateAndAssertResult(kerberosRealm, expectedUser, kerberosAuthenticationToken, outToken);
@@ -100,14 +102,16 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException
100102

101103
public void testAuthenticateWithValidTicketSucessAuthnWithUserDetailsWhenCacheDisabled()
102104
throws LoginException, GSSException, IOException {
103-
final String username = randomAlphaOfLength(5);
104-
final String outToken = randomAlphaOfLength(10);
105105
// if cache.ttl <= 0 then the cache is disabled
106106
settings = KerberosTestCase.buildKerberosRealmSettings(
107-
KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), randomAlphaOfLength(4)).toString(), 100, "0m", true);
107+
KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), randomAlphaOfLength(4)).toString(), 100, "0m", true,
108+
randomBoolean());
109+
final String username = randomPrincipalName();
110+
final String outToken = randomAlphaOfLength(10);
108111
final KerberosRealm kerberosRealm = createKerberosRealm(username);
109112

110-
final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true);
113+
final String expectedUsername = maybeRemoveRealmName(username);
114+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
111115
final byte[] decodedTicket = randomByteArrayOfLength(10);
112116
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
113117
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ public void testKerberosRealmSettings() throws IOException {
3131
KerberosTestCase.writeKeyTab(dir.resolve(keytabPathConfig), null);
3232
final Integer maxUsers = randomInt();
3333
final String cacheTTL = randomLongBetween(10L, 100L) + "m";
34-
final Settings settings = KerberosTestCase.buildKerberosRealmSettings(keytabPathConfig, maxUsers, cacheTTL, true);
34+
final boolean enableDebugLogs = randomBoolean();
35+
final boolean removeRealmName = randomBoolean();
36+
final Settings settings = KerberosTestCase.buildKerberosRealmSettings(keytabPathConfig, maxUsers, cacheTTL, enableDebugLogs,
37+
removeRealmName);
3538

3639
assertThat(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(settings), equalTo(keytabPathConfig));
3740
assertThat(KerberosRealmSettings.CACHE_TTL_SETTING.get(settings),
3841
equalTo(TimeValue.parseTimeValue(cacheTTL, KerberosRealmSettings.CACHE_TTL_SETTING.getKey())));
3942
assertThat(KerberosRealmSettings.CACHE_MAX_USERS_SETTING.get(settings), equalTo(maxUsers));
40-
assertThat(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(settings), is(true));
43+
assertThat(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(settings), is(enableDebugLogs));
44+
assertThat(KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(settings), is(removeRealmName));
4145
}
4246

4347
}

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.watcher.ResourceWatcherService;
2121
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
2222
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
23+
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;
2324
import org.elasticsearch.xpack.core.security.support.Exceptions;
2425
import org.elasticsearch.xpack.core.security.user.User;
2526
import org.elasticsearch.xpack.security.authc.kerberos.support.KerberosTestCase;
@@ -33,8 +34,10 @@
3334
import java.nio.file.Path;
3435
import java.util.Arrays;
3536
import java.util.List;
37+
import java.util.Locale;
3638
import java.util.Map;
3739
import java.util.Set;
40+
import java.util.stream.Collectors;
3841

3942
import static org.hamcrest.Matchers.equalTo;
4043
import static org.hamcrest.Matchers.is;
@@ -67,7 +70,8 @@ public void setup() throws Exception {
6770
resourceWatcherService = new ResourceWatcherService(Settings.EMPTY, threadPool);
6871
dir = createTempDir();
6972
globalSettings = Settings.builder().put("path.home", dir).build();
70-
settings = KerberosTestCase.buildKerberosRealmSettings(KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), "asa").toString());
73+
settings = KerberosTestCase.buildKerberosRealmSettings(KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), "asa").toString(),
74+
100, "10m", true, randomBoolean());
7175
}
7276

7377
@After
@@ -111,7 +115,8 @@ protected KerberosRealm createKerberosRealm(final String... userForRoleMapping)
111115
}
112116

113117
@SuppressWarnings("unchecked")
114-
protected NativeRoleMappingStore roleMappingStore(final List<String> expectedUserNames) {
118+
protected NativeRoleMappingStore roleMappingStore(final List<String> userNames) {
119+
final List<String> expectedUserNames = userNames.stream().map(this::maybeRemoveRealmName).collect(Collectors.toList());
115120
final Client mockClient = mock(Client.class);
116121
when(mockClient.threadPool()).thenReturn(threadPool);
117122
when(mockClient.settings()).thenReturn(settings);
@@ -133,4 +138,34 @@ protected NativeRoleMappingStore roleMappingStore(final List<String> expectedUse
133138

134139
return roleMapper;
135140
}
141+
142+
protected String randomPrincipalName() {
143+
final StringBuilder principalName = new StringBuilder();
144+
principalName.append(randomAlphaOfLength(5));
145+
final boolean withInstance = randomBoolean();
146+
if (withInstance) {
147+
principalName.append("/").append(randomAlphaOfLength(5));
148+
}
149+
principalName.append(randomAlphaOfLength(5).toUpperCase(Locale.ROOT));
150+
return principalName.toString();
151+
}
152+
153+
/**
154+
* Usually principal names are in the form 'user/instance@REALM'. This method
155+
* removes '@REALM' part from the principal name if
156+
* {@link KerberosRealmSettings#SETTING_REMOVE_REALM_NAME} is {@code true} else
157+
* will return the input string.
158+
*
159+
* @param principalName user principal name
160+
* @return username after removal of realm
161+
*/
162+
protected String maybeRemoveRealmName(final String principalName) {
163+
if (KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(settings)) {
164+
int foundAtIndex = principalName.indexOf('@');
165+
if (foundAtIndex > 0) {
166+
return principalName.substring(0, foundAtIndex);
167+
}
168+
}
169+
return principalName;
170+
}
136171
}

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,14 @@ public void testSupports() {
4848
}
4949

5050
public void testAuthenticateWithValidTicketSucessAuthnWithUserDetails() throws LoginException, GSSException {
51-
final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM");
52-
53-
final User expectedUser = new User("test@REALM", roles.toArray(new String[roles.size()]), null, null, null, true);
51+
final String username = randomPrincipalName();
52+
final KerberosRealm kerberosRealm = createKerberosRealm(username);
53+
final String expectedUsername = maybeRemoveRealmName(username);
54+
final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true);
5455
final byte[] decodedTicket = "base64encodedticket".getBytes(StandardCharsets.UTF_8);
5556
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
5657
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
57-
mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>("test@REALM", "out-token"), null);
58+
mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(username, "out-token"), null);
5859
final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken(decodedTicket);
5960

6061
final PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
@@ -69,7 +70,8 @@ public void testAuthenticateWithValidTicketSucessAuthnWithUserDetails() throws L
6970
}
7071

7172
public void testFailedAuthorization() throws LoginException, GSSException {
72-
final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM");
73+
final String username = randomPrincipalName();
74+
final KerberosRealm kerberosRealm = createKerberosRealm(username);
7375
final byte[] decodedTicket = "base64encodedticket".getBytes(StandardCharsets.UTF_8);
7476
final Path keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
7577
final boolean krbDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
@@ -80,13 +82,15 @@ public void testFailedAuthorization() throws LoginException, GSSException {
8082

8183
ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, future::actionGet);
8284
assertThat(e.status(), is(RestStatus.FORBIDDEN));
83-
assertThat(e.getMessage(), equalTo("Expected UPN '" + Arrays.asList("test@REALM") + "' but was 'does-not-exist@REALM'"));
85+
assertThat(e.getMessage(), equalTo("Expected UPN '" + Arrays.asList(maybeRemoveRealmName(username)) + "' but was '"
86+
+ maybeRemoveRealmName("does-not-exist@REALM") + "'"));
8487
}
8588

8689
public void testLookupUser() {
87-
final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM");
90+
final String username = randomPrincipalName();
91+
final KerberosRealm kerberosRealm = createKerberosRealm(username);
8892
final PlainActionFuture<User> future = new PlainActionFuture<>();
89-
kerberosRealm.lookupUser("test@REALM", future);
93+
kerberosRealm.lookupUser(username, future);
9094
assertThat(future.actionGet(), is(nullValue()));
9195
}
9296

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public static Path writeKeyTab(final Path keytabPath, final String content) thro
196196
* @return {@link Settings} for kerberos realm
197197
*/
198198
public static Settings buildKerberosRealmSettings(final String keytabPath) {
199-
return buildKerberosRealmSettings(keytabPath, 100, "10m", true);
199+
return buildKerberosRealmSettings(keytabPath, 100, "10m", true, false);
200200
}
201201

202202
/**
@@ -206,14 +206,16 @@ public static Settings buildKerberosRealmSettings(final String keytabPath) {
206206
* @param maxUsersInCache max users to be maintained in cache
207207
* @param cacheTTL time to live for cached entries
208208
* @param enableDebugging for krb5 logs
209+
* @param removeRealmName {@code true} if we want to remove realm name from the username of form 'user@REALM'
209210
* @return {@link Settings} for kerberos realm
210211
*/
211212
public static Settings buildKerberosRealmSettings(final String keytabPath, final int maxUsersInCache, final String cacheTTL,
212-
final boolean enableDebugging) {
213+
final boolean enableDebugging, final boolean removeRealmName) {
213214
final Settings.Builder builder = Settings.builder().put(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.getKey(), keytabPath)
214215
.put(KerberosRealmSettings.CACHE_MAX_USERS_SETTING.getKey(), maxUsersInCache)
215216
.put(KerberosRealmSettings.CACHE_TTL_SETTING.getKey(), cacheTTL)
216-
.put(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.getKey(), enableDebugging);
217+
.put(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.getKey(), enableDebugging)
218+
.put(KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.getKey(), removeRealmName);
217219
return builder.build();
218220
}
219221

0 commit comments

Comments
 (0)