diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java index e0f8d4adb076f..8310d04fa28ec 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/DefaultAuthenticationFailureHandler.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.security.authc; import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestStatus; @@ -20,7 +21,9 @@ import java.util.Map; import java.util.stream.Collectors; +import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR; import static org.elasticsearch.xpack.core.security.support.Exceptions.authenticationError; +import static org.elasticsearch.xpack.core.security.support.Exceptions.internalServerError; /** * The default implementation of a {@link AuthenticationFailureHandler}. This @@ -160,6 +163,9 @@ private ElasticsearchSecurityException createAuthenticationError(final String me } else { containsNegotiateWithToken = false; } + } else if (t instanceof ElasticsearchStatusException && ((ElasticsearchStatusException) t).status() == INTERNAL_SERVER_ERROR) { + ese = internalServerError(message, t, args); + containsNegotiateWithToken = false; } else { ese = authenticationError(message, t, args); containsNegotiateWithToken = false; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/Exceptions.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/Exceptions.java index 9b2652883455f..a23c9a1bf3d2f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/Exceptions.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/support/Exceptions.java @@ -34,4 +34,8 @@ public static ElasticsearchSecurityException authorizationError(String msg, Obje public static ElasticsearchSecurityException authorizationError(String msg, Exception cause, Object... args) { return new ElasticsearchSecurityException(msg, RestStatus.FORBIDDEN, cause, args); } + + public static ElasticsearchSecurityException internalServerError(String msg, Throwable cause, Object... args) { + return new ElasticsearchSecurityException(msg, RestStatus.INTERNAL_SERVER_ERROR, cause, args); + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 2e0f5b192dd04..da9a9eb5fbbbc 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -10,8 +10,10 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; @@ -60,6 +62,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR; import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; @@ -283,6 +286,25 @@ private void createReservedUser(String username, char[] passwordHash, RefreshPol }); } + /** + * Asynchronous method to create a security index if necessary and a reserved user if one doesn't exist + */ + public void createReservedUserAndGetUserInfo(String username, char[] passwordHash, RefreshPolicy refresh, + ActionListener listener) { + securityIndex.prepareIndexIfNeededThenExecute((e) -> { listener.onFailure(new ElasticsearchStatusException(e.getMessage(), + INTERNAL_SERVER_ERROR, e.getCause())); }, + () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareIndex(SECURITY_MAIN_ALIAS).setOpType(DocWriteRequest.OpType.CREATE) + .setId(getIdForUser(RESERVED_USER_TYPE, username)) + .setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash), + Fields.ENABLED.getPreferredName(), + true, Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) + .setRefreshPolicy(refresh).request(), + listener.delegateFailure((l, indexResponse) -> getReservedUserInfo(username, l)), + client::index); + }); + } + /** * Asynchronous method to put a user. A put user request without a password hash is treated as an update and will fail with a * {@link ValidationException} if the user does not exist. If a password hash is provided, then we issue a update request with an diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java index 2dea4b8b1f2c1..6551165801003 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/ReservedRealm.java +++ b/x-pack/plugin/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.action.ActionListener; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.logging.DeprecationCategory; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.settings.KeyStoreWrapper; @@ -16,6 +17,7 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Nullable; import org.elasticsearch.env.Environment; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.XPackSettings; @@ -59,14 +61,16 @@ public class ReservedRealm extends CachingUsernamePasswordRealm { private final ReservedUserInfo bootstrapUserInfo; public static final Setting BOOTSTRAP_ELASTIC_PASSWORD = SecureSetting.secureString("bootstrap.password", KeyStoreWrapper.SEED_SETTING); - public static final Setting AUTOCONFIG_BOOOTSTRAP_ELASTIC_PASSWORD_HASH = - SecureSetting.secureString("autoconfig.password_hash", null); + public static final Setting AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH = + SecureSetting.secureString("autoconfiguration.password_hash", null); private final NativeUsersStore nativeUsersStore; private final AnonymousUser anonymousUser; private final boolean realmEnabled; private final boolean anonymousEnabled; private final SecurityIndexManager securityIndex; + private final boolean bootstrapPasswordExists; + private final boolean autoconfigHashExists; private final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName()); @@ -83,8 +87,12 @@ public ReservedRealm(Environment env, Settings settings, NativeUsersStore native this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings); this.securityIndex = securityIndex; final Hasher reservedRealmHasher = Hasher.resolve(XPackSettings.PASSWORD_HASHING_ALGORITHM.get(settings)); - final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? new char[0] : - reservedRealmHasher.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings)); + autoconfigHashExists = AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH.exists(settings); + bootstrapPasswordExists = BOOTSTRAP_ELASTIC_PASSWORD.exists(settings); + final char[] hash = (bootstrapPasswordExists || autoconfigHashExists == false) ? + (BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? + new char[0] : reservedRealmHasher.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings))) : + AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH.get(settings).getChars(); bootstrapUserInfo = new ReservedUserInfo(hash, true); } @@ -95,7 +103,7 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener { + getUserInfo(token.principal(), token.credentials(), ActionListener.wrap((userInfo) -> { AuthenticationResult result; if (userInfo != null) { try { @@ -113,7 +121,7 @@ protected void doAuthenticate(UsernamePasswordToken token, ActionListener listener) { } else if (AnonymousUser.isAnonymousUsername(username, config.settings())) { listener.onResponse(anonymousEnabled ? anonymousUser : null); } else { - getUserInfo(username, ActionListener.wrap((userInfo) -> { + getUserInfo(username, null, ActionListener.wrap((userInfo) -> { if (userInfo != null) { listener.onResponse(getUser(username, userInfo)); } else { @@ -212,13 +220,25 @@ public void users(ActionListener> listener) { } - private void getUserInfo(final String username, ActionListener listener) { + private void getUserInfo(final String username, @Nullable SecureString credentials, ActionListener listener) { if (securityIndex.indexExists() == false) { - listener.onResponse(getDefaultUserInfo(username)); + if ((bootstrapPasswordExists == false && autoconfigHashExists) && username.equals(ElasticUser.NAME) + && bootstrapUserInfo.verifyPassword(credentials)) { + nativeUsersStore.createReservedUserAndGetUserInfo(username, bootstrapUserInfo.passwordHash, + WriteRequest.RefreshPolicy.IMMEDIATE, listener); + } else { + listener.onResponse(getDefaultUserInfo(username)); + } } else { nativeUsersStore.getReservedUserInfo(username, ActionListener.wrap((userInfo) -> { if (userInfo == null) { - listener.onResponse(getDefaultUserInfo(username)); + if ((bootstrapPasswordExists == false && autoconfigHashExists) && username.equals(ElasticUser.NAME) + && bootstrapUserInfo.verifyPassword(credentials)) { + nativeUsersStore.createReservedUserAndGetUserInfo(username, bootstrapUserInfo.passwordHash, + WriteRequest.RefreshPolicy.IMMEDIATE, listener); + } else { + listener.onResponse(getDefaultUserInfo(username)); + } } else { listener.onResponse(userInfo); } @@ -250,5 +270,6 @@ private ReservedUserInfo getDefaultUserInfo(String username) { public static void addSettings(List> settingsList) { settingsList.add(BOOTSTRAP_ELASTIC_PASSWORD); + settingsList.add(AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHash.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHash.java index c96aeead444a0..60e77fbdb8db5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHash.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHash.java @@ -19,7 +19,7 @@ import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; -import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.AUTOCONFIG_BOOOTSTRAP_ELASTIC_PASSWORD_HASH; +import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH; import static org.elasticsearch.xpack.security.tool.CommandUtils.generatePassword; /** @@ -53,7 +53,7 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th SecureString elasticPassword = new SecureString(generatePassword(20)); KeyStoreWrapper nodeKeystore = KeyStoreWrapper.bootstrap(env.configFile(), () -> new SecureString(new char[0])) ) { - nodeKeystore.setString(AUTOCONFIG_BOOOTSTRAP_ELASTIC_PASSWORD_HASH.getKey(), hasher.hash(elasticPassword)); + nodeKeystore.setString(AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH.getKey(), hasher.hash(elasticPassword)); nodeKeystore.save(env.configFile(), new char[0]); terminal.print(Terminal.Verbosity.NORMAL, elasticPassword.toString()); } catch (Exception e) { diff --git a/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHashTests.java b/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHashTests.java index 2b279eb9b3c11..a8c765a446ca9 100644 --- a/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHashTests.java +++ b/x-pack/qa/security-tools-tests/src/test/java/org/elasticsearch/xpack/security/enrollment/tool/AutoConfigGenerateElasticPasswordHashTests.java @@ -31,7 +31,7 @@ import java.util.Map; import static org.elasticsearch.test.SecurityIntegTestCase.getFastStoredHashAlgoForTests; -import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.AUTOCONFIG_BOOOTSTRAP_ELASTIC_PASSWORD_HASH; +import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.equalTo; @@ -98,7 +98,7 @@ public void testSuccessfullyGenerateAndStoreHash() throws Exception { assertNotNull(keyStoreWrapper); keyStoreWrapper.decrypt(new char[0]); assertThat(keyStoreWrapper.getSettingNames(), - containsInAnyOrder(AUTOCONFIG_BOOOTSTRAP_ELASTIC_PASSWORD_HASH.getKey(), "keystore.seed")); + containsInAnyOrder(AUTOCONFIG_BOOTSTRAP_ELASTIC_PASSWORD_HASH.getKey(), "keystore.seed")); } public void testExistingKeystoreWithWrongPassword() throws Exception {