diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveGenerateInitialPasswordTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveGenerateInitialPasswordTests.java new file mode 100644 index 0000000000000..2e0b6f0df8d77 --- /dev/null +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveGenerateInitialPasswordTests.java @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.packaging.test; + +import org.apache.http.client.fluent.Request; +import org.elasticsearch.packaging.util.Distribution; +import org.elasticsearch.packaging.util.ServerUtils; +import org.elasticsearch.packaging.util.Shell; +import org.junit.BeforeClass; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.elasticsearch.packaging.util.Archives.installArchive; +import static org.elasticsearch.packaging.util.Archives.verifyArchiveInstallation; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.junit.Assume.assumeTrue; + +public class ArchiveGenerateInitialPasswordTests extends PackagingTestCase { + + private static final Pattern PASSWORD_REGEX = Pattern.compile("Password for the (\\w+) user is: (.+)$", Pattern.MULTILINE); + + @BeforeClass + public static void filterDistros() { + assumeTrue("archives only", distribution.isArchive()); + } + + public void test10Install() throws Exception { + installation = installArchive(sh, distribution()); + // Enable security for these tests only where it is necessary, until we can enable it for all + ServerUtils.enableSecurityFeatures(installation); + verifyArchiveInstallation(installation, distribution()); + } + + public void test20NoAutoGenerationWhenBootstrapPassword() throws Exception { + /* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */ + assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS); + installation.executables().keystoreTool.run("create"); + installation.executables().keystoreTool.run("add --stdin bootstrap.password", "some-password-here"); + Shell.Result result = runElasticsearchStartCommand(null, false, true); + Map usersAndPasswords = parseUsersAndPasswords(result.stdout); + assertThat(usersAndPasswords.isEmpty(), is(true)); + String response = ServerUtils.makeRequest(Request.Get("http://localhost:9200"), "elastic", "some-password-here", null); + assertThat(response, containsString("You Know, for Search")); + } + + public void test30NoAutoGenerationWhenAutoConfigurationDisabled() throws Exception { + /* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */ + assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS); + stopElasticsearch(); + installation.executables().keystoreTool.run("remove bootstrap.password"); + ServerUtils.disableSecurityAutoConfiguration(installation); + Shell.Result result = runElasticsearchStartCommand(null, false, true); + Map usersAndPasswords = parseUsersAndPasswords(result.stdout); + assertThat(usersAndPasswords.isEmpty(), is(true)); + } + + public void test40VerifyAutogeneratedCredentials() throws Exception { + /* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */ + assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS); + stopElasticsearch(); + ServerUtils.enableSecurityAutoConfiguration(installation); + Shell.Result result = runElasticsearchStartCommand(null, false, true); + Map usersAndPasswords = parseUsersAndPasswords(result.stdout); + assertThat(usersAndPasswords.size(), equalTo(2)); + assertThat(usersAndPasswords.containsKey("elastic"), is(true)); + assertThat(usersAndPasswords.containsKey("kibana_system"), is(true)); + for (Map.Entry userpass : usersAndPasswords.entrySet()) { + String response = ServerUtils.makeRequest(Request.Get("http://localhost:9200"), userpass.getKey(), userpass.getValue(), null); + assertThat(response, containsString("You Know, for Search")); + } + } + + public void test50PasswordAutogenerationOnlyOnce() throws Exception { + /* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */ + assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS); + stopElasticsearch(); + Shell.Result result = runElasticsearchStartCommand(null, false, true); + Map usersAndPasswords = parseUsersAndPasswords(result.stdout); + assertThat(usersAndPasswords.isEmpty(), is(true)); + } + + private Map parseUsersAndPasswords(String output) { + Matcher matcher = PASSWORD_REGEX.matcher(output); + assertNotNull(matcher); + Map usersAndPasswords = new HashMap<>(); + while (matcher.find()) { + usersAndPasswords.put(matcher.group(1), matcher.group(2)); + } + return usersAndPasswords; + } +} diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/CertGenCliTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/CertGenCliTests.java index b8d4f19ba3ca5..57a5ed11fcee7 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/CertGenCliTests.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/CertGenCliTests.java @@ -20,8 +20,6 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static com.carrotsearch.randomizedtesting.RandomizedTest.assumeFalse; import static java.nio.file.StandardOpenOption.APPEND; @@ -119,16 +117,8 @@ public void test40RunWithCert() throws Exception { } private String setElasticPassword() { - final Pattern userpassRegex = Pattern.compile("PASSWORD (\\w+) = ([^\\s]+)"); - Shell.Result result = installation.executables().setupPasswordsTool.run("auto --batch", null); - Matcher matcher = userpassRegex.matcher(result.stdout); - assertNotNull(matcher); - while (matcher.find()) { - if (matcher.group(1).equals("elastic")) { - return matcher.group(2); - } - } - return null; + Shell.Result result = installation.executables().resetElasticPasswordTool.run("--auto --batch --silent", null); + return result.stdout; } } diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java index e5cb19ddb6196..0dc1dc3eba454 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java @@ -41,6 +41,8 @@ public void test010Install() throws Exception { install(); // Enable security for this test only where it is necessary, until we can enable it for all ServerUtils.enableSecurityFeatures(installation); + // Disable auto-configuration so that we can run setup-passwords + ServerUtils.disableSecurityAutoConfiguration(installation); } public void test20GeneratePasswords() throws Exception { diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java index 06c95c3aab887..6a94be1a55426 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java @@ -187,6 +187,7 @@ public class Executables { public final Executable nodeTool = new Executable("elasticsearch-node"); public final Executable securityConfigTool = new Executable("elasticsearch-security-config"); public final Executable setupPasswordsTool = new Executable("elasticsearch-setup-passwords"); + public final Executable resetElasticPasswordTool = new Executable("elasticsearch-reset-elastic-password"); public final Executable sqlCli = new Executable("elasticsearch-sql-cli"); public final Executable syskeygenTool = new Executable("elasticsearch-syskeygen"); public final Executable usersTool = new Executable("elasticsearch-users"); diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java index f5383139b3a88..d1b4cc92c44ee 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java @@ -333,4 +333,26 @@ public static void enableSecurityFeatures(Installation installation) throws IOEx } Files.write(yml, lines, TRUNCATE_EXISTING); } + + public static void disableSecurityAutoConfiguration(Installation installation) throws IOException { + Path yml = installation.config("elasticsearch.yml"); + List addedLines = List.of("xpack.security.autoconfiguration.enabled: false"); + List lines; + try (Stream allLines = Files.readAllLines(yml).stream()) { + lines = allLines.filter(s -> s.startsWith("xpack.security.autoconfiguration.enabled") == false).collect(Collectors.toList()); + } + lines.addAll(addedLines); + Files.write(yml, lines, TRUNCATE_EXISTING); + } + + public static void enableSecurityAutoConfiguration(Installation installation) throws IOException { + Path yml = installation.config("elasticsearch.yml"); + List addedLines = List.of("xpack.security.autoconfiguration.enabled: true"); + List lines; + try (Stream allLines = Files.readAllLines(yml).stream()) { + lines = allLines.filter(s -> s.startsWith("xpack.security.autoconfiguration.enabled") == false).collect(Collectors.toList()); + } + lines.addAll(addedLines); + Files.write(yml, lines, TRUNCATE_EXISTING); + } } diff --git a/qa/remote-clusters/docker-compose.yml b/qa/remote-clusters/docker-compose.yml index d2ca0688217f7..2847b05fdb280 100644 --- a/qa/remote-clusters/docker-compose.yml +++ b/qa/remote-clusters/docker-compose.yml @@ -30,6 +30,7 @@ services: - xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt - xpack.http.ssl.verification_mode=certificate - xpack.security.transport.ssl.verification_mode=certificate + - xpack.security.autoconfiguration.enabled=false - xpack.license.self_generated.type=trial - action.destructive_requires_name=false volumes: @@ -84,6 +85,7 @@ services: - xpack.security.http.ssl.certificate=/usr/share/elasticsearch/config/testnode.crt - xpack.http.ssl.verification_mode=certificate - xpack.security.transport.ssl.verification_mode=certificate + - xpack.security.autoconfiguration.enabled=false - xpack.license.self_generated.type=trial - action.destructive_requires_name=false volumes: diff --git a/qa/system-indices/build.gradle b/qa/system-indices/build.gradle index 6762b90588c17..a0652341db338 100644 --- a/qa/system-indices/build.gradle +++ b/qa/system-indices/build.gradle @@ -25,5 +25,6 @@ tasks.named("javaRestTest").configure { testClusters.all { testDistribution = 'DEFAULT' setting 'xpack.security.enabled', 'true' + setting 'xpack.security.autoconfiguration.enabled', 'false' user username: 'rest_user', password: 'rest-user-password', role: 'superuser' } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index ed73c0cd26e97..1ff32e27eb95b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -105,6 +105,9 @@ private XPackSettings() { public static final Setting ENROLLMENT_ENABLED = Setting.boolSetting("xpack.security.enrollment.enabled", false, Property.NodeScope); + public static final Setting SECURITY_AUTOCONFIGURATION_ENABLED = + Setting.boolSetting("xpack.security.autoconfiguration.enabled", true, Property.NodeScope); + /* * 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.*) @@ -232,6 +235,7 @@ public static List> getAllSettings() { settings.add(USER_SETTING); settings.add(PASSWORD_HASHING_ALGORITHM); settings.add(ENROLLMENT_ENABLED); + settings.add(SECURITY_AUTOCONFIGURATION_ENABLED); return Collections.unmodifiableList(settings); } diff --git a/x-pack/plugin/eql/qa/multi-cluster-with-security/build.gradle b/x-pack/plugin/eql/qa/multi-cluster-with-security/build.gradle index 6671fedb57d22..10fabdd9f49ca 100644 --- a/x-pack/plugin/eql/qa/multi-cluster-with-security/build.gradle +++ b/x-pack/plugin/eql/qa/multi-cluster-with-security/build.gradle @@ -16,6 +16,7 @@ testClusters { setting 'xpack.ml.enabled', 'false' setting 'xpack.watcher.enabled', 'false' setting 'xpack.security.enabled', 'true' + setting 'xpack.security.autoconfiguration.enabled', 'false' user username: "test_user", password: "x-pack-test-password" } @@ -29,6 +30,7 @@ testClusters { } setting 'cluster.remote.connections_per_cluster', "1" setting 'xpack.security.enabled', 'true' + setting 'xpack.security.autoconfiguration.enabled', 'false' user username: "test_user", password: "x-pack-test-password" } diff --git a/x-pack/plugin/fleet/build.gradle b/x-pack/plugin/fleet/build.gradle index fecc258a16e80..c64d5d90c9935 100644 --- a/x-pack/plugin/fleet/build.gradle +++ b/x-pack/plugin/fleet/build.gradle @@ -26,5 +26,6 @@ dependencies { testClusters.all { testDistribution = 'DEFAULT' setting 'xpack.security.enabled', 'true' + setting 'xpack.security.autoconfiguration.enabled', 'false' user username: 'x_pack_rest_user', password: 'x-pack-test-password', role: 'superuser' } diff --git a/x-pack/plugin/ilm/qa/rest/build.gradle b/x-pack/plugin/ilm/qa/rest/build.gradle index 6964fd717e848..70c05bedaf27b 100644 --- a/x-pack/plugin/ilm/qa/rest/build.gradle +++ b/x-pack/plugin/ilm/qa/rest/build.gradle @@ -32,5 +32,6 @@ testClusters.all { setting 'xpack.ml.enabled', 'false' setting 'xpack.security.enabled', 'true' setting 'xpack.license.self_generated.type', 'trial' + setting 'xpack.security.autoconfiguration.enabled', 'false' user clusterCredentials } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/GenerateInitialBuiltinUsersPasswordListener.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/GenerateInitialBuiltinUsersPasswordListener.java new file mode 100644 index 0000000000000..586a7f084c978 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/GenerateInitialBuiltinUsersPasswordListener.java @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.security; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.DocWriteRequest; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.index.engine.VersionConflictEngineException; +import org.elasticsearch.xpack.core.security.user.ElasticUser; +import org.elasticsearch.xpack.core.security.user.KibanaSystemUser; +import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; +import org.elasticsearch.xpack.security.support.SecurityIndexManager; + +import java.util.function.BiConsumer; + +import static org.elasticsearch.xpack.security.tool.CommandUtils.generatePassword; + +public class GenerateInitialBuiltinUsersPasswordListener implements BiConsumer { + + private static final Logger LOGGER = LogManager.getLogger(GenerateInitialBuiltinUsersPasswordListener.class); + private NativeUsersStore nativeUsersStore; + private SecurityIndexManager securityIndexManager; + + public GenerateInitialBuiltinUsersPasswordListener(NativeUsersStore nativeUsersStore, SecurityIndexManager securityIndexManager) { + this.nativeUsersStore = nativeUsersStore; + this.securityIndexManager = securityIndexManager; + } + + @Override + public void accept(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) { + if (previousState.equals(SecurityIndexManager.State.UNRECOVERED_STATE) + && currentState.equals(SecurityIndexManager.State.UNRECOVERED_STATE) == false + && securityIndexManager.indexExists() == false) { + + final SecureString elasticPassword = new SecureString(generatePassword(20)); + final SecureString kibanaSystemPassword = new SecureString(generatePassword(20)); + nativeUsersStore + .updateReservedUser( + ElasticUser.NAME, + elasticPassword.getChars(), + DocWriteRequest.OpType.CREATE, + WriteRequest.RefreshPolicy.IMMEDIATE, + ActionListener.wrap(result -> { + nativeUsersStore + .updateReservedUser( + KibanaSystemUser.NAME, + kibanaSystemPassword.getChars(), + DocWriteRequest.OpType.CREATE, + WriteRequest.RefreshPolicy.IMMEDIATE, + ActionListener.wrap( + r -> { + outputOnSuccess(elasticPassword, kibanaSystemPassword); + }, this::outputOnError + ) + ); + }, this::outputOnError)); + securityIndexManager.removeStateListener(this); + } + } + + private void outputOnSuccess(SecureString elasticPassword, SecureString kibanaSystemPassword) { + LOGGER.info(""); + LOGGER.info("-----------------------------------------------------------------"); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("Password for the elastic user is: " + elasticPassword); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("Password for the kibana_system user is: " + kibanaSystemPassword); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("Please note these down as they will not be shown again."); + LOGGER.info(""); + LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password' at any time"); + LOGGER.info("in order to reset the password for the elastic user."); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password' at any time"); + LOGGER.info("in order to reset the password for the kibana_system user."); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("-----------------------------------------------------------------"); + LOGGER.info(""); + } + + private void outputOnError(Exception e) { + if (e instanceof VersionConflictEngineException == false) { + LOGGER.info(""); + LOGGER.info("-----------------------------------------------------------------"); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("Failed to set the password for the elastic and kibana-system users "); + LOGGER.info("automatically"); + LOGGER.info(""); + LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password'"); + LOGGER.info("in order to set the password for the elastic user."); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password'"); + LOGGER.info("in order to set the password for the kibana_system user."); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info(""); + LOGGER.info("-----------------------------------------------------------------"); + LOGGER.info(""); + } + } +} diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 408feb4711a5f..36fb1cfcf2d4e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -337,8 +337,10 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.XPackSettings.API_KEY_SERVICE_ENABLED_SETTING; import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED; +import static org.elasticsearch.xpack.core.XPackSettings.SECURITY_AUTOCONFIGURATION_ENABLED; import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_TOKENS_ALIAS; +import static org.elasticsearch.xpack.security.authc.esnative.ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD; import static org.elasticsearch.xpack.security.operator.OperatorPrivileges.OPERATOR_PRIVILEGES_ENABLED; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_TOKENS_INDEX_FORMAT; @@ -453,7 +455,6 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste } scriptServiceReference.set(scriptService); - // We need to construct the checks here while the secure settings are still available. // If we wait until #getBoostrapChecks the secure settings will have been cleared/closed. final List checks = new ArrayList<>(); @@ -495,6 +496,11 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste // realms construction final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, securityIndex.get()); + GenerateInitialBuiltinUsersPasswordListener generateInitialBuiltinUsersPasswordListener = + new GenerateInitialBuiltinUsersPasswordListener(nativeUsersStore, securityIndex.get()); + if (BOOTSTRAP_ELASTIC_PASSWORD.exists(settings) == false && SECURITY_AUTOCONFIGURATION_ENABLED.get(settings)) { + securityIndex.get().addStateListener(generateInitialBuiltinUsersPasswordListener); + } final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(), scriptService); final AnonymousUser anonymousUser = new AnonymousUser(settings); 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..47bdae1d846d2 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 @@ -12,6 +12,7 @@ import org.elasticsearch.ElasticsearchException; 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; @@ -252,7 +253,13 @@ public void onResponse(UpdateResponse updateResponse) { public void onFailure(Exception e) { if (isIndexNotFoundOrDocumentMissing(e)) { if (docType.equals(RESERVED_USER_TYPE)) { - createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener); + updateReservedUser( + username, + request.passwordHash(), + DocWriteRequest.OpType.INDEX, + request.getRefreshPolicy(), + listener + ); } else { logger.debug((org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage("failed to change password for user [{}]", request.username()), e); @@ -269,17 +276,36 @@ public void onFailure(Exception e) { } /** - * Asynchronous method to create a reserved user with the given password hash. The cache for the user will be cleared after the document - * has been indexed + * Asynchronous method to create or update a reserved user with the given password hash. The cache for the user will be + * cleared after the document has been indexed */ - private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener listener) { + public void updateReservedUser( + String username, + char[] passwordHash, + DocWriteRequest.OpType opType, + RefreshPolicy refresh, + ActionListener listener + ) { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SECURITY_MAIN_ALIAS).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) -> clearRealmCache(username, l, null)), client::index); + executeAsyncWithOrigin( + client.threadPool().getThreadContext(), + SECURITY_ORIGIN, + client.prepareIndex(SECURITY_MAIN_ALIAS) + .setOpType(opType) + .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) -> clearRealmCache(username, l, null)), + client::index + ); }); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java index 1b5ecdba005f3..66055f831bee0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java @@ -155,6 +155,14 @@ public void addStateListener(BiConsumer listener) { stateChangeListeners.add(listener); } + /** + * Remove a listener from notifications on state changes to the configured index. + * + */ + public void removeStateListener(BiConsumer listener) { + stateChangeListeners.remove(listener); + } + @Override public void clusterChanged(ClusterChangedEvent event) { if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/tool/CommandUtils.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/tool/CommandUtils.java index 9b9f48e3b4585..6f424fa07f820 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/tool/CommandUtils.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/tool/CommandUtils.java @@ -20,7 +20,7 @@ public class CommandUtils { * @return the char array with the password */ public static char[] generatePassword(int passwordLength) { - final char[] passwordChars = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*-_=+?").toCharArray(); + final char[] passwordChars = ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-_=+").toCharArray(); char[] characters = new char[passwordLength]; for (int i = 0; i < passwordLength; ++i) { characters[i] = passwordChars[SECURE_RANDOM.nextInt(passwordChars.length)]; diff --git a/x-pack/qa/core-rest-tests-with-security/build.gradle b/x-pack/qa/core-rest-tests-with-security/build.gradle index 82c1f6a730dba..d5cd4fd817967 100644 --- a/x-pack/qa/core-rest-tests-with-security/build.gradle +++ b/x-pack/qa/core-rest-tests-with-security/build.gradle @@ -42,6 +42,7 @@ testClusters.matching { it.name == "integTest" }.configureEach { setting 'xpack.ml.enabled', 'false' setting 'xpack.license.self_generated.type', 'trial' setting 'indices.lifecycle.history_index_enabled', 'false' + setting 'xpack.security.autoconfiguration.enabled', 'false' user username: clusterCredentials.username, password: clusterCredentials.password diff --git a/x-pack/qa/reindex-tests-with-security/build.gradle b/x-pack/qa/reindex-tests-with-security/build.gradle index e30368f4e0090..2d002666b4377 100644 --- a/x-pack/qa/reindex-tests-with-security/build.gradle +++ b/x-pack/qa/reindex-tests-with-security/build.gradle @@ -35,6 +35,7 @@ testClusters.matching { it.name == "integTest" }.configureEach { setting 'xpack.security.http.ssl.key', 'http.key' setting 'xpack.security.http.ssl.key_passphrase', 'http-password' setting 'reindex.ssl.certificate_authorities', 'ca.crt' + setting 'xpack.security.autoconfiguration.enabled', 'false' // Workaround for JDK-8212885 if (BuildParams.runtimeJavaVersion.isJava12Compatible() == false) {