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:
parent
2c3a63816c
commit
7a321534ea
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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.*)
|
||||
|
|
Loading…
Reference in New Issue