security: allow the reserved realm to be disabled

This change allows the reserved realm to be disabled via a setting that is undocumented.

Closes elastic/elasticsearch#3399

Original commit: elastic/x-pack-elasticsearch@3c6c93d7eb
This commit is contained in:
jaymode 2016-09-09 09:09:53 -04:00
parent 2c3a63816c
commit 7a321534ea
6 changed files with 109 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
@ -41,16 +42,21 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
private final NativeUsersStore nativeUsersStore;
private final AnonymousUser anonymousUser;
private final boolean anonymousEnabled;
private final boolean enabled;
public ReservedRealm(Environment env, Settings settings, NativeUsersStore nativeUsersStore, AnonymousUser anonymousUser) {
super(TYPE, new RealmConfig(TYPE, Settings.EMPTY, settings, env));
this.nativeUsersStore = nativeUsersStore;
this.enabled = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings);
this.anonymousUser = anonymousUser;
this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
}
@Override
protected User doAuthenticate(UsernamePasswordToken token) {
if (enabled == false) {
return null;
}
if (isReserved(token.principal(), config.globalSettings()) == false) {
return null;
}
@ -73,6 +79,13 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
@Override
protected User doLookupUser(String username) {
if (enabled == false) {
if (anonymousEnabled && AnonymousUser.isAnonymousUsername(username, config.globalSettings())) {
return anonymousUser;
}
return null;
}
if (isReserved(username, config.globalSettings()) == false) {
return null;
}
@ -99,13 +112,13 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
switch (username) {
case ElasticUser.NAME:
case KibanaUser.NAME:
return true;
return XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings);
default:
return AnonymousUser.isAnonymousUsername(username, settings);
}
}
User getUser(String username, ReservedUserInfo userInfo) {
private User getUser(String username, ReservedUserInfo userInfo) {
assert username != null;
switch (username) {
case ElasticUser.NAME:
@ -121,7 +134,7 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
public Collection<User> users() {
if (nativeUsersStore.started() == false) {
if (nativeUsersStore.started() == false || enabled == false) {
return anonymousEnabled ? Collections.singletonList(anonymousUser) : Collections.emptyList();
}

View File

@ -16,6 +16,7 @@ import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.support.RefreshListener;
@ -133,6 +134,7 @@ public class FileUserPasswdStore {
Map<String, char[]> users = new HashMap<>();
final boolean allowReserved = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings) == false;
int lineNr = 0;
for (String line : lines) {
lineNr++;
@ -149,7 +151,7 @@ public class FileUserPasswdStore {
continue;
}
String username = line.substring(0, i);
Validation.Error validationError = Users.validateUsername(username, false, settings);
Validation.Error validationError = Users.validateUsername(username, allowReserved, settings);
if (validationError != null) {
logger.error("invalid username [{}] in users file [{}], skipping... ({})", username, path.toAbsolutePath(),
validationError);

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.file.FileUserPasswdStore;
import org.elasticsearch.xpack.security.authc.file.FileUserRolesStore;
import org.elasticsearch.xpack.security.authc.support.Hasher;
@ -85,8 +86,10 @@ public class UsersTool extends MultiCommand {
@Override
protected void execute(Terminal terminal, OptionSet options, Map<String, String> settings) throws Exception {
Environment env = InternalSettingsPreparer.prepareEnvironment(Settings.EMPTY, terminal, settings);
String username = parseUsername(arguments.values(options), env.settings());
Validation.Error validationError = Users.validateUsername(username, false, Settings.EMPTY);
final boolean allowReserved = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(env.settings()) == false;
Validation.Error validationError = Users.validateUsername(username, allowReserved, env.settings());
if (validationError != null) {
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
}
@ -395,7 +398,8 @@ public class UsersTool extends MultiCommand {
throw new UserException(ExitCodes.USAGE, "Expected a single username argument, found extra: " + args.toString());
}
String username = args.get(0);
Validation.Error validationError = Users.validateUsername(username, false, settings);
final boolean allowReserved = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings) == false;
Validation.Error validationError = Users.validateUsername(username, allowReserved, settings);
if (validationError != null) {
throw new UserException(ExitCodes.DATA_ERROR, "Invalid username [" + username + "]... " + validationError);
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authc.esnative;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.support.SecuredString;
@ -19,14 +20,17 @@ import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
@ -76,6 +80,21 @@ public class ReservedRealmTests extends ESTestCase {
verifyNoMoreInteractions(usersStore);
}
public void testAuthenticationDisabled() throws Throwable {
Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build();
final boolean securityIndexExists = randomBoolean();
if (securityIndexExists) {
when(usersStore.securityIndexExists()).thenReturn(true);
}
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings));
final User expected = randomFrom(new ElasticUser(true), new KibanaUser(true));
final String principal = expected.principal();
final User authenticated = reservedRealm.doAuthenticate(new UsernamePasswordToken(principal, DEFAULT_PASSWORD));
assertNull(authenticated);
verifyZeroInteractions(usersStore);
}
public void testAuthenticationWithStoredPassword() throws Throwable {
final ReservedRealm reservedRealm =
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
@ -118,6 +137,18 @@ public class ReservedRealmTests extends ESTestCase {
verifyNoMoreInteractions(usersStore);
}
public void testLookupDisabled() throws Exception {
Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build();
final ReservedRealm reservedRealm =
new ReservedRealm(mock(Environment.class), settings, usersStore, new AnonymousUser(settings));
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
final String principal = expectedUser.principal();
final User user = reservedRealm.doLookupUser(principal);
assertNull(user);
verifyZeroInteractions(usersStore);
}
public void testLookupThrows() throws Exception {
final ReservedRealm reservedRealm =
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
@ -146,12 +177,37 @@ public class ReservedRealmTests extends ESTestCase {
assertThat(ReservedRealm.isReserved(notExpected, Settings.EMPTY), is(false));
}
public void testIsReservedDisabled() {
Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build();
final User expectedUser = randomFrom(new ElasticUser(true), new KibanaUser(true));
final String principal = expectedUser.principal();
assertThat(ReservedRealm.isReserved(principal, settings), is(false));
final String notExpected = randomFrom("foobar", "", randomAsciiOfLengthBetween(1, 30));
assertThat(ReservedRealm.isReserved(notExpected, settings), is(false));
}
public void testGetUsers() {
final ReservedRealm reservedRealm =
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));
assertThat(reservedRealm.users(), containsInAnyOrder(new ElasticUser(true), new KibanaUser(true)));
}
public void testGetUsersDisabled() {
final boolean anonymousEnabled = randomBoolean();
Settings settings = Settings.builder()
.put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false)
.put(AnonymousUser.ROLES_SETTING.getKey(), anonymousEnabled ? "user" : "")
.build();
final AnonymousUser anonymousUser = new AnonymousUser(settings);
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser);
if (anonymousEnabled) {
assertThat(reservedRealm.users(), contains(anonymousUser));
} else {
assertThat(reservedRealm.users(), empty());
}
}
public void testFailedAuthentication() {
final ReservedRealm reservedRealm =
new ReservedRealm(mock(Environment.class), Settings.EMPTY, usersStore, new AnonymousUser(Settings.EMPTY));

View File

@ -16,11 +16,14 @@ import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtilsForTesting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.support.SecuredString;
import org.elasticsearch.xpack.security.authc.support.SecuredStringTests;
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.security.user.ElasticUser;
import org.elasticsearch.xpack.security.user.KibanaUser;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@ -181,6 +184,18 @@ public class UsersToolTests extends CommandTestCase {
assertTrue(e.getMessage(), e.getMessage().contains("Invalid username"));
}
public void testParseReservedUsername() throws Exception {
final String name = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
UserException e = expectThrows(UserException.class, () -> {
UsersTool.parseUsername(Collections.singletonList(name), Settings.EMPTY);
});
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
assertTrue(e.getMessage(), e.getMessage().contains("Invalid username"));
Settings settings = Settings.builder().put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false).build();
UsersTool.parseUsername(Collections.singletonList(name), settings);
}
public void testParseUsernameMissing() throws Exception {
UserException e = expectThrows(UserException.class, () -> {
UsersTool.parseUsername(Collections.emptyList(), Settings.EMPTY);
@ -273,6 +288,15 @@ public class UsersToolTests extends CommandTestCase {
assertEquals("User [existing_user] already exists", e.getMessage());
}
public void testUseraddReservedUser() throws Exception {
final String name = randomFrom(ElasticUser.NAME, KibanaUser.NAME);
UserException e = expectThrows(UserException.class, () -> {
execute("useradd", pathHomeParameter, fileTypeParameter, name, "-p", "changeme");
});
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
assertEquals("Invalid username [" + name + "]... Username [" + name + "] is reserved and may not be used.", e.getMessage());
}
public void testUseraddNoRoles() throws Exception {
Files.delete(confDir.resolve("users_roles"));
Files.createFile(confDir.resolve("users_roles"));

View File

@ -56,6 +56,10 @@ public class XPackSettings {
/** Setting for enabling or disabling http ssl. Defaults to false. */
public static final Setting<Boolean> HTTP_SSL_ENABLED = enabledSetting(XPackPlugin.SECURITY + ".http.ssl", false);
/** Setting for enabling or disabling the reserved realm. Defaults to true */
public static final Setting<Boolean> RESERVED_REALM_ENABLED_SETTING =
enabledSetting(XPackPlugin.SECURITY + ".authc.reserved_realm", true);
/*
* SSL settings. These are the settings that are specifically registered for SSL. Many are private as we do not explicitly use them
* but instead parse based on a prefix (eg *.ssl.*)