From 7a321534ead16e075c89b6a89315ef0c1549f0d1 Mon Sep 17 00:00:00 2001 From: jaymode Date: Fri, 9 Sep 2016 09:09:53 -0400 Subject: [PATCH] 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@3c6c93d7ebe42944319cf5951052982bf720c58f --- .../authc/esnative/ReservedRealm.java | 19 ++++++- .../authc/file/FileUserPasswdStore.java | 4 +- .../security/authc/file/tool/UsersTool.java | 8 ++- .../authc/esnative/ReservedRealmTests.java | 56 +++++++++++++++++++ .../authc/file/tool/UsersToolTests.java | 24 ++++++++ .../elasticsearch/xpack/XPackSettings.java | 4 ++ 6 files changed, 109 insertions(+), 6 deletions(-) diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java index acb93901917..23d7eb7a697 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java @@ -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 users() { - if (nativeUsersStore.started() == false) { + if (nativeUsersStore.started() == false || enabled == false) { return anonymousEnabled ? Collections.singletonList(anonymousUser) : Collections.emptyList(); } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java index 9cff9389a84..ebcda00c753 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStore.java @@ -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 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); diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java index 496a2767149..99c2a8fc38e 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/authc/file/tool/UsersTool.java @@ -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 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); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java index 780aa8742af..42c11c4f7d8 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealmTests.java @@ -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)); diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java index 7859030831d..d0659a63852 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/authc/file/tool/UsersToolTests.java @@ -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")); diff --git a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackSettings.java b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackSettings.java index 6727916ba5e..382720e8255 100644 --- a/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackSettings.java +++ b/elasticsearch/x-pack/src/main/java/org/elasticsearch/xpack/XPackSettings.java @@ -56,6 +56,10 @@ public class XPackSettings { /** Setting for enabling or disabling http ssl. Defaults to false. */ public static final Setting HTTP_SSL_ENABLED = enabledSetting(XPackPlugin.SECURITY + ".http.ssl", false); + /** Setting for enabling or disabling the reserved realm. Defaults to true */ + public static final Setting 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.*)