Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -27,6 +27,8 @@ public final class KerberosRealmSettings {
Setting.simpleString("keytab.path", Property.NodeScope);
public static final Setting<Boolean> SETTING_KRB_DEBUG_ENABLE =
Setting.boolSetting("krb.debug", Boolean.FALSE, Property.NodeScope);
public static final Setting<Boolean> SETTING_REMOVE_REALM_NAME =
Setting.boolSetting("remove_realm_name", Boolean.FALSE, Property.NodeScope);

// Cache
private static final TimeValue DEFAULT_TTL = TimeValue.timeValueMinutes(20);
Expand All @@ -42,6 +44,7 @@ private KerberosRealmSettings() {
* @return the valid set of {@link Setting}s for a {@value #TYPE} realm
*/
public static Set<Setting<?>> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -126,7 +128,8 @@ public void authenticate(final AuthenticationToken token, final ActionListener<A
kerberosTicketValidator.validateTicket((byte[]) kerbAuthnToken.credentials(), keytabPath, enableKerberosDebug,
ActionListener.wrap(userPrincipalNameOutToken -> {
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
Expand All @@ -145,6 +148,25 @@ 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 void handleException(Exception e, final ActionListener<AuthenticationResult> listener) {
if (e instanceof LoginException) {
listener.onResponse(AuthenticationResult.terminate("failed to authenticate user, service login failure",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -62,7 +63,7 @@ public void testAuthenticateWithCache() throws LoginException, GSSException {

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

Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -111,7 +115,8 @@ protected KerberosRealm createKerberosRealm(final String... userForRoleMapping)
}

@SuppressWarnings("unchecked")
protected NativeRoleMappingStore roleMappingStore(final List<String> expectedUserNames) {
protected NativeRoleMappingStore roleMappingStore(final List<String> userNames) {
final List<String> 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);
Expand All @@ -133,4 +138,34 @@ protected NativeRoleMappingStore roleMappingStore(final List<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<AuthenticationResult> future = new PlainActionFuture<>();
Expand All @@ -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());
Expand All @@ -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<User> future = new PlainActionFuture<>();
kerberosRealm.lookupUser("test@REALM", future);
kerberosRealm.lookupUser(username, future);
assertThat(future.actionGet(), is(nullValue()));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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();
}

Expand Down