diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/kerberos/KerberosRealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/kerberos/KerberosRealmSettings.java index a3dfb501928ee..7524ef08c1e72 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/kerberos/KerberosRealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/kerberos/KerberosRealmSettings.java @@ -27,6 +27,8 @@ public final class KerberosRealmSettings { Setting.simpleString("keytab.path", Property.NodeScope); public static final Setting SETTING_KRB_DEBUG_ENABLE = Setting.boolSetting("krb.debug", Boolean.FALSE, Property.NodeScope); + public static final Setting SETTING_REMOVE_REALM_NAME = + Setting.boolSetting("remove_realm_name", Boolean.FALSE, Property.NodeScope); // Cache private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20); @@ -42,6 +44,7 @@ private KerberosRealmSettings() { * @return the valid set of {@link Setting}s for a {@value #TYPE} realm */ public static Set> getSettings() { - return Sets.newHashSet(HTTP_SERVICE_KEYTAB_PATH, CACHE_TTL_SETTING, CACHE_MAX_USERS_SETTING, SETTING_KRB_DEBUG_ENABLE); + return Sets.newHashSet(HTTP_SERVICE_KEYTAB_PATH, CACHE_TTL_SETTING, CACHE_MAX_USERS_SETTING, SETTING_KRB_DEBUG_ENABLE, + SETTING_REMOVE_REALM_NAME); } } 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 81daa07fe7760..20c5d21c192ab 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 @@ -62,6 +62,7 @@ public final class KerberosRealm extends Realm implements CachingRealm { private final ThreadPool threadPool; private final Path keytabPath; private final boolean enableKerberosDebug; + private final boolean removeRealmName; public KerberosRealm(final RealmConfig config, final NativeRoleMappingStore nativeRoleMappingStore, final ThreadPool threadPool) { this(config, nativeRoleMappingStore, new KerberosTicketValidator(), threadPool, null); @@ -88,6 +89,7 @@ public KerberosRealm(final RealmConfig config, final NativeRoleMappingStore nati this.threadPool = threadPool; this.keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings())); this.enableKerberosDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings()); + this.removeRealmName = KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(config.settings()); } @Override @@ -126,7 +128,8 @@ public void authenticate(final AuthenticationToken token, final ActionListener { if (userPrincipalNameOutToken.v1() != null) { - buildUser(userPrincipalNameOutToken.v1(), userPrincipalNameOutToken.v2(), listener); + final String username = maybeRemoveRealmName(userPrincipalNameOutToken.v1()); + buildUser(username, userPrincipalNameOutToken.v2(), listener); } else { /** * 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 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 void handleException(Exception e, final ActionListener listener) { if (e instanceof LoginException) { listener.onResponse(AuthenticationResult.terminate("failed to authenticate user, service login failure", 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 144bec3d58315..7853e18a01b87 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 @@ -42,7 +42,7 @@ public void testAuthenticateWithNonKerberosAuthenticationToken() { } public void testAuthenticateDifferentFailureScenarios() throws LoginException, GSSException { - final String username = randomAlphaOfLength(5); + final String username = randomPrincipalName(); final String outToken = randomAlphaOfLength(10); final KerberosRealm kerberosRealm = createKerberosRealm(username); final boolean validTicket = rarely(); @@ -76,7 +76,9 @@ public void testAuthenticateDifferentFailureScenarios() throws LoginException, G assertThat(result.getStatus(), is(equalTo(AuthenticationResult.Status.CONTINUE))); } else { if (validTicket) { - final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true); + final String expectedUsername = maybeRemoveRealmName(username); + final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, + 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 24712e53b504e..c6d114de93b24 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 @@ -36,11 +36,12 @@ public class KerberosRealmCacheTests extends KerberosRealmTestCase { public void testAuthenticateWithCache() throws LoginException, GSSException { - final String username = randomAlphaOfLength(5); + final String username = randomPrincipalName(); final String outToken = randomAlphaOfLength(10); final KerberosRealm kerberosRealm = createKerberosRealm(username); - final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true); + final String expectedUsername = maybeRemoveRealmName(username); + final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, 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()); @@ -62,7 +63,7 @@ public void testAuthenticateWithCache() throws LoginException, GSSException { public void testCacheInvalidationScenarios() throws LoginException, GSSException { final String outToken = randomAlphaOfLength(10); - final List userNames = Arrays.asList(randomAlphaOfLength(5) + "@REALM", randomAlphaOfLength(5) + "@REALM"); + final List userNames = Arrays.asList(randomPrincipalName(), randomPrincipalName()); final KerberosRealm kerberosRealm = createKerberosRealm(userNames.toArray(new String[0])); verify(mockNativeRoleMappingStore).refreshRealmOnChange(kerberosRealm); @@ -71,7 +72,8 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException 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()); mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(authNUsername, outToken), null); - final User expectedUser = new User(authNUsername, roles.toArray(new String[roles.size()]), null, null, null, true); + final String expectedUsername = maybeRemoveRealmName(authNUsername); + final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, true); final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken(decodedTicket); final User user1 = authenticateAndAssertResult(kerberosRealm, expectedUser, kerberosAuthenticationToken, outToken); @@ -81,7 +83,7 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException if (expireAll) { kerberosRealm.expireAll(); } else { - kerberosRealm.expire(expireThisUser); + kerberosRealm.expire(maybeRemoveRealmName(expireThisUser)); } final User user2 = authenticateAndAssertResult(kerberosRealm, expectedUser, kerberosAuthenticationToken, outToken); @@ -100,14 +102,16 @@ public void testCacheInvalidationScenarios() throws LoginException, GSSException public void testAuthenticateWithValidTicketSucessAuthnWithUserDetailsWhenCacheDisabled() throws LoginException, GSSException, IOException { - final String username = randomAlphaOfLength(5); - final String outToken = randomAlphaOfLength(10); // if cache.ttl <= 0 then the cache is disabled settings = KerberosTestCase.buildKerberosRealmSettings( - KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), randomAlphaOfLength(4)).toString(), 100, "0m", true); + KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), randomAlphaOfLength(4)).toString(), 100, "0m", true, + randomBoolean()); + final String username = randomPrincipalName(); + final String outToken = randomAlphaOfLength(10); final KerberosRealm kerberosRealm = createKerberosRealm(username); - final User expectedUser = new User(username, roles.toArray(new String[roles.size()]), null, null, null, true); + final String expectedUsername = maybeRemoveRealmName(username); + final User expectedUser = new User(expectedUsername, roles.toArray(new String[roles.size()]), null, null, null, 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/KerberosRealmSettingsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmSettingsTests.java index 876ebdc574136..c536566a73f60 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmSettingsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmSettingsTests.java @@ -31,13 +31,17 @@ public void testKerberosRealmSettings() throws IOException { KerberosTestCase.writeKeyTab(dir.resolve(keytabPathConfig), null); final Integer maxUsers = randomInt(); final String cacheTTL = randomLongBetween(10L, 100L) + "m"; - final Settings settings = KerberosTestCase.buildKerberosRealmSettings(keytabPathConfig, maxUsers, cacheTTL, true); + final boolean enableDebugLogs = randomBoolean(); + final boolean removeRealmName = randomBoolean(); + final Settings settings = KerberosTestCase.buildKerberosRealmSettings(keytabPathConfig, maxUsers, cacheTTL, enableDebugLogs, + removeRealmName); assertThat(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(settings), equalTo(keytabPathConfig)); assertThat(KerberosRealmSettings.CACHE_TTL_SETTING.get(settings), equalTo(TimeValue.parseTimeValue(cacheTTL, KerberosRealmSettings.CACHE_TTL_SETTING.getKey()))); assertThat(KerberosRealmSettings.CACHE_MAX_USERS_SETTING.get(settings), equalTo(maxUsers)); - assertThat(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(settings), is(true)); + assertThat(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(settings), is(enableDebugLogs)); + assertThat(KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(settings), is(removeRealmName)); } } 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 85e0d88707216..1a0ab149035c6 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 @@ -20,6 +20,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings; import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.kerberos.support.KerberosTestCase; @@ -33,8 +34,10 @@ import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -67,7 +70,8 @@ public void setup() throws Exception { resourceWatcherService = new ResourceWatcherService(Settings.EMPTY, threadPool); dir = createTempDir(); globalSettings = Settings.builder().put("path.home", dir).build(); - settings = KerberosTestCase.buildKerberosRealmSettings(KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), "asa").toString()); + settings = KerberosTestCase.buildKerberosRealmSettings(KerberosTestCase.writeKeyTab(dir.resolve("key.keytab"), "asa").toString(), + 100, "10m", true, randomBoolean()); } @After @@ -111,7 +115,8 @@ protected KerberosRealm createKerberosRealm(final String... userForRoleMapping) } @SuppressWarnings("unchecked") - protected NativeRoleMappingStore roleMappingStore(final List expectedUserNames) { + protected NativeRoleMappingStore roleMappingStore(final List userNames) { + final List expectedUserNames = userNames.stream().map(this::maybeRemoveRealmName).collect(Collectors.toList()); final Client mockClient = mock(Client.class); when(mockClient.threadPool()).thenReturn(threadPool); when(mockClient.settings()).thenReturn(settings); @@ -133,4 +138,34 @@ protected NativeRoleMappingStore roleMappingStore(final List expectedUse return roleMapper; } + + protected String randomPrincipalName() { + final StringBuilder principalName = new StringBuilder(); + principalName.append(randomAlphaOfLength(5)); + final boolean withInstance = randomBoolean(); + if (withInstance) { + principalName.append("/").append(randomAlphaOfLength(5)); + } + principalName.append(randomAlphaOfLength(5).toUpperCase(Locale.ROOT)); + return principalName.toString(); + } + + /** + * 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 (KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(settings)) { + int foundAtIndex = principalName.indexOf('@'); + if (foundAtIndex > 0) { + return principalName.substring(0, foundAtIndex); + } + } + return principalName; + } } 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 c504f79233b22..43536abaf29e1 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 @@ -48,13 +48,14 @@ public void testSupports() { } public void testAuthenticateWithValidTicketSucessAuthnWithUserDetails() throws LoginException, GSSException { - final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM"); - - final User expectedUser = new User("test@REALM", roles.toArray(new String[roles.size()]), null, null, null, true); + 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 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()); - mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>("test@REALM", "out-token"), null); + mockKerberosTicketValidator(decodedTicket, keytabPath, krbDebug, new Tuple<>(username, "out-token"), null); final KerberosAuthenticationToken kerberosAuthenticationToken = new KerberosAuthenticationToken(decodedTicket); final PlainActionFuture future = new PlainActionFuture<>(); @@ -69,7 +70,8 @@ public void testAuthenticateWithValidTicketSucessAuthnWithUserDetails() throws L } public void testFailedAuthorization() throws LoginException, GSSException { - final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM"); + final String username = randomPrincipalName(); + final KerberosRealm kerberosRealm = createKerberosRealm(username); 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()); @@ -80,13 +82,15 @@ public void testFailedAuthorization() throws LoginException, GSSException { ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, future::actionGet); assertThat(e.status(), is(RestStatus.FORBIDDEN)); - assertThat(e.getMessage(), equalTo("Expected UPN '" + Arrays.asList("test@REALM") + "' but was 'does-not-exist@REALM'")); + assertThat(e.getMessage(), equalTo("Expected UPN '" + Arrays.asList(maybeRemoveRealmName(username)) + "' but was '" + + maybeRemoveRealmName("does-not-exist@REALM") + "'")); } public void testLookupUser() { - final KerberosRealm kerberosRealm = createKerberosRealm("test@REALM"); + final String username = randomPrincipalName(); + final KerberosRealm kerberosRealm = createKerberosRealm(username); final PlainActionFuture future = new PlainActionFuture<>(); - kerberosRealm.lookupUser("test@REALM", future); + kerberosRealm.lookupUser(username, future); assertThat(future.actionGet(), is(nullValue())); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/support/KerberosTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/support/KerberosTestCase.java index 865688e2b05f5..4e7b34a9e8b3e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/support/KerberosTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/support/KerberosTestCase.java @@ -196,7 +196,7 @@ public static Path writeKeyTab(final Path keytabPath, final String content) thro * @return {@link Settings} for kerberos realm */ public static Settings buildKerberosRealmSettings(final String keytabPath) { - return buildKerberosRealmSettings(keytabPath, 100, "10m", true); + return buildKerberosRealmSettings(keytabPath, 100, "10m", true, false); } /** @@ -206,14 +206,16 @@ public static Settings buildKerberosRealmSettings(final String keytabPath) { * @param maxUsersInCache max users to be maintained in cache * @param cacheTTL time to live for cached entries * @param enableDebugging for krb5 logs + * @param removeRealmName {@code true} if we want to remove realm name from the username of form 'user@REALM' * @return {@link Settings} for kerberos realm */ public static Settings buildKerberosRealmSettings(final String keytabPath, final int maxUsersInCache, final String cacheTTL, - final boolean enableDebugging) { + final boolean enableDebugging, final boolean removeRealmName) { final Settings.Builder builder = Settings.builder().put(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.getKey(), keytabPath) .put(KerberosRealmSettings.CACHE_MAX_USERS_SETTING.getKey(), maxUsersInCache) .put(KerberosRealmSettings.CACHE_TTL_SETTING.getKey(), cacheTTL) - .put(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.getKey(), enableDebugging); + .put(KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.getKey(), enableDebugging) + .put(KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.getKey(), removeRealmName); return builder.build(); }