Don't bootstrap security index on start-up but authenticate bootstrap password locally (elastic/x-pack-elasticsearch#2272)

Today we try to bootstrap the security index with the bootstrap password and recommend the user to change the password with the user tool. This is trappy for instance if you happen to configure multiple nodes with a different bootstrap passwords (which is possible) it's unclear which password made it too bootstrap. Yet, we tell in the logs but it can still be very confusing. In general it should be possible to bootstrap with the user tool from any node unless the user is already created in the native user store. This change uses the bootstrap.password from the local node and always authenticate against it until the user is bootstrapped even if the passwords are different on different nodes. This will also work for authenticating against the cluster for instance if a user deletes the .security index or if that index has not been upgraded.

Original commit: elastic/x-pack-elasticsearch@8cebecb287
This commit is contained in:
Simon Willnauer 2017-08-17 08:36:26 +02:00 committed by GitHub
parent 4ed7b5473c
commit 8f15324a08
10 changed files with 147 additions and 600 deletions

View File

@ -133,7 +133,6 @@ import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.security.bootstrap.BootstrapElasticPassword;
import org.elasticsearch.xpack.security.rest.SecurityRestFilter;
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction;
@ -408,10 +407,6 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin {
securityInterceptor.set(new SecurityServerTransportInterceptor(settings, threadPool, authcService.get(), authzService, licenseState,
sslService, securityContext.get(), destructiveOperations));
BootstrapElasticPassword bootstrapElasticPassword = new BootstrapElasticPassword(settings, clusterService, reservedRealm,
securityLifecycleService);
bootstrapElasticPassword.initiatePasswordBootstrap();
return components;
}

View File

@ -71,6 +71,7 @@ public class NativeUsersStore extends AbstractComponent {
static final String USER_DOC_TYPE = "user";
public static final String RESERVED_USER_TYPE = "reserved-user";
private final Hasher hasher = Hasher.BCRYPT;
private final InternalClient client;
private final boolean isTribeNode;
@ -550,7 +551,8 @@ public class NativeUsersStore extends AbstractComponent {
} else if (enabled == null) {
listener.onFailure(new IllegalStateException("enabled must not be null!"));
} else if (password.isEmpty()) {
listener.onResponse(new ReservedUserInfo(ReservedRealm.EMPTY_PASSWORD_HASH, enabled, true));
listener.onResponse((enabled ? ReservedRealm.ENABLED_DEFAULT_USER_INFO : ReservedRealm
.DISABLED_DEFAULT_USER_INFO).deepClone());
} else {
listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled, false));
}
@ -687,7 +689,7 @@ public class NativeUsersStore extends AbstractComponent {
return docType + "-" + userName;
}
static class ReservedUserInfo {
static final class ReservedUserInfo {
public final char[] passwordHash;
public final boolean enabled;
@ -698,5 +700,10 @@ public class NativeUsersStore extends AbstractComponent {
this.enabled = enabled;
this.hasEmptyPassword = hasEmptyPassword;
}
ReservedUserInfo deepClone() {
return new ReservedUserInfo(Arrays.copyOf(passwordHash, passwordHash.length), enabled, hasEmptyPassword);
}
}
}

View File

@ -18,7 +18,6 @@ import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest;
import org.elasticsearch.xpack.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.security.authc.RealmConfig;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore.ReservedUserInfo;
@ -46,11 +45,10 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
public static final String TYPE = "reserved";
public static final SecureString EMPTY_PASSWORD_TEXT = new SecureString("".toCharArray());
static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(EMPTY_PASSWORD_TEXT);
private static final ReservedUserInfo DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true);
private static final ReservedUserInfo DISABLED_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true);
private final ReservedUserInfo bootstrapUserInfo;
static final char[] EMPTY_PASSWORD_HASH = Hasher.BCRYPT.hash(new SecureString("".toCharArray()));
static final ReservedUserInfo DISABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, false, true);
static final ReservedUserInfo ENABLED_DEFAULT_USER_INFO = new ReservedUserInfo(EMPTY_PASSWORD_HASH, true, true);
public static final Setting<Boolean> ACCEPT_DEFAULT_PASSWORD_SETTING = Setting.boolSetting(
Security.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered,
@ -71,6 +69,9 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
this.anonymousUser = anonymousUser;
this.anonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
this.securityLifecycleService = securityLifecycleService;
final char[] hash = BOOTSTRAP_ELASTIC_PASSWORD.get(settings).length() == 0 ? EMPTY_PASSWORD_HASH :
Hasher.BCRYPT.hash(BOOTSTRAP_ELASTIC_PASSWORD.get(settings));
bootstrapUserInfo = new ReservedUserInfo(hash, true, hash == EMPTY_PASSWORD_HASH);
}
@Override
@ -86,16 +87,16 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
try {
if (userInfo.hasEmptyPassword) {
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
} else if (verifyPassword(userInfo, token)) {
} else if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
final User user = getUser(token.principal(), userInfo);
result = AuthenticationResult.success(user);
} else {
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
}
} finally {
if (userInfo.passwordHash != EMPTY_PASSWORD_HASH) {
Arrays.fill(userInfo.passwordHash, (char) 0);
}
assert userInfo.passwordHash != DISABLED_DEFAULT_USER_INFO.passwordHash : "default user info must be cloned";
assert userInfo.passwordHash != bootstrapUserInfo.passwordHash : "bootstrap user info must be cloned";
Arrays.fill(userInfo.passwordHash, (char) 0);
}
} else {
result = AuthenticationResult.terminate("failed to authenticate user [" + token.principal() + "]", null);
@ -106,13 +107,6 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
}
private boolean verifyPassword(ReservedUserInfo userInfo, UsernamePasswordToken token) {
if (Hasher.BCRYPT.verify(token.credentials(), userInfo.passwordHash)) {
return true;
}
return false;
}
@Override
protected void doLookupUser(String username, ActionListener<User> listener) {
if (realmEnabled == false) {
@ -148,31 +142,6 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
}
public synchronized void bootstrapElasticUserCredentials(SecureString passwordHash, ActionListener<Boolean> listener) {
getUserInfo(ElasticUser.NAME, new ActionListener<ReservedUserInfo>() {
@Override
public void onResponse(ReservedUserInfo reservedUserInfo) {
if (reservedUserInfo == null) {
listener.onFailure(new IllegalStateException("unexpected state: ReservedUserInfo was null"));
} else if (reservedUserInfo.hasEmptyPassword) {
ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest();
changePasswordRequest.username(ElasticUser.NAME);
changePasswordRequest.passwordHash(passwordHash.getChars());
nativeUsersStore.changePassword(changePasswordRequest,
ActionListener.wrap(v -> listener.onResponse(true), listener::onFailure));
} else {
listener.onResponse(false);
}
}
@Override
public void onFailure(Exception e) {
listener.onFailure(e);
}
});
}
private User getUser(String username, ReservedUserInfo userInfo) {
assert username != null;
switch (username) {
@ -219,16 +188,21 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
}
}
private void getUserInfo(final String username, ActionListener<ReservedUserInfo> listener) {
if (userIsDefinedForCurrentSecurityMapping(username) == false) {
logger.debug("Marking user [{}] as disabled because the security mapping is not at the required version", username);
listener.onResponse(DISABLED_USER_INFO);
listener.onResponse(DISABLED_DEFAULT_USER_INFO.deepClone());
} else if (securityLifecycleService.isSecurityIndexExisting() == false) {
listener.onResponse(DEFAULT_USER_INFO);
listener.onResponse(bootstrapUserInfo.deepClone());
} else {
nativeUsersStore.getReservedUserInfo(username, ActionListener.wrap((userInfo) -> {
if (userInfo == null) {
listener.onResponse(DEFAULT_USER_INFO);
if (ElasticUser.NAME.equals(username)) {
listener.onResponse(bootstrapUserInfo.deepClone());
} else {
listener.onResponse(ENABLED_DEFAULT_USER_INFO.deepClone());
}
} else {
listener.onResponse(userInfo);
}

View File

@ -1,125 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.bootstrap;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.SetOnce;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.action.user.ChangePasswordRequestBuilder;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import java.util.concurrent.Semaphore;
/**
* This process adds a ClusterStateListener to the ClusterService that will listen for cluster state updates.
* Once the cluster and the security index are ready, it will attempt to bootstrap the elastic user's
* password with a password from the keystore. If the password is not in the keystore or the elastic user
* already has a password, then the user's password will not be set. Once the process is complete, the
* listener will remove itself.
*/
public final class BootstrapElasticPassword {
private final Settings settings;
private final Logger logger;
private final ClusterService clusterService;
private final ReservedRealm reservedRealm;
private final SecurityLifecycleService lifecycleService;
private final boolean reservedRealmDisabled;
public BootstrapElasticPassword(Settings settings, ClusterService clusterService, ReservedRealm reservedRealm,
SecurityLifecycleService lifecycleService) {
this.reservedRealmDisabled = XPackSettings.RESERVED_REALM_ENABLED_SETTING.get(settings) == false;
this.settings = settings;
this.logger = Loggers.getLogger(BootstrapElasticPassword.class, settings);
this.clusterService = clusterService;
this.reservedRealm = reservedRealm;
this.lifecycleService = lifecycleService;
}
public void initiatePasswordBootstrap() {
SecureString bootstrapPassword = ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.get(settings);
if (bootstrapPassword.length() == 0) {
return;
} else if (reservedRealmDisabled) {
logger.warn("elastic password will not be bootstrapped because the reserved realm is disabled");
bootstrapPassword.close();
return;
}
SecureString passwordHash = new SecureString(ChangePasswordRequestBuilder.validateAndHashPassword(bootstrapPassword));
bootstrapPassword.close();
clusterService.addListener(new BootstrapPasswordClusterStateListener(passwordHash));
}
private class BootstrapPasswordClusterStateListener implements ClusterStateListener {
private final Semaphore semaphore = new Semaphore(1);
private final SecureString passwordHash;
private final SetOnce<Boolean> isDone = new SetOnce<>();
private BootstrapPasswordClusterStateListener(SecureString passwordHash) {
this.passwordHash = passwordHash;
}
@Override
public void clusterChanged(ClusterChangedEvent event) {
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)
|| lifecycleService.isSecurityIndexOutOfDate()
|| (lifecycleService.isSecurityIndexExisting() && lifecycleService.isSecurityIndexAvailable() == false)
|| lifecycleService.isSecurityIndexWriteable() == false) {
// We hold off bootstrapping until the node recovery is complete, the security index is up to date, and
// security index is writeable. If the security index currently exists, it must also be available.
return;
}
// Only allow one attempt to bootstrap the password at a time
if (semaphore.tryAcquire()) {
// Ensure that we do not attempt to bootstrap after the process is complete. This is important as we
// clear the password hash in the cleanup phase.
if (isDone.get() != null) {
semaphore.release();
return;
}
reservedRealm.bootstrapElasticUserCredentials(passwordHash, new ActionListener<Boolean>() {
@Override
public void onResponse(Boolean passwordSet) {
cleanup();
if (passwordSet) {
logger.info("elastic password was bootstrapped successfully");
} else {
logger.warn("elastic password was not bootstrapped because its password was already set");
}
semaphore.release();
}
@Override
public void onFailure(Exception e) {
cleanup();
logger.error("unexpected exception when attempting to bootstrap password", e);
semaphore.release();
}
});
}
}
private void cleanup() {
isDone.set(true);
IOUtils.closeWhileHandlingException(() -> clusterService.removeListener(this), passwordHash);
}
}
}

View File

@ -216,7 +216,7 @@ public class Upgrade implements ActionPlugin {
}
try (SecureString secureString = new SecureString(passwordHash.toCharArray())) {
return Hasher.BCRYPT.verify(ReservedRealm.EMPTY_PASSWORD_TEXT, secureString.getChars());
return Hasher.BCRYPT.verify(new SecureString("".toCharArray()), secureString.getChars());
}
}

View File

@ -6,6 +6,7 @@
package org.elasticsearch.test;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.http.HttpEntity;
import org.elasticsearch.client.http.entity.ContentType;
import org.elasticsearch.client.http.message.BasicHeader;
@ -35,7 +36,6 @@ public abstract class NativeRealmIntegTestCase extends SecurityIntegTestCase {
public void ensureNativeStoresStarted() throws Exception {
assertSecurityIndexActive();
if (shouldSetReservedUserPasswords()) {
ensureElasticPasswordBootstrapped();
setupReservedPasswords();
}
}
@ -70,16 +70,29 @@ public abstract class NativeRealmIntegTestCase extends SecurityIntegTestCase {
}
public void setupReservedPasswords() throws IOException {
setupReservedPasswords(getRestClient());
}
public void setupReservedPasswords(RestClient restClient) throws IOException {
logger.info("setting up reserved passwords for test");
SecureString defaultPassword = new SecureString("".toCharArray());
{
String payload = "{\"password\": \"" + new String(reservedPassword.getChars()) + "\"}";
HttpEntity entity = new NStringEntity(payload, ContentType.APPLICATION_JSON);
BasicHeader authHeader = new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
UsernamePasswordToken.basicAuthHeaderValue(ElasticUser.NAME, BOOTSTRAP_PASSWORD));
String route = "/_xpack/security/user/elastic/_password";
Response response = restClient.performRequest("PUT", route, Collections.emptyMap(), entity, authHeader);
assertEquals(response.getStatusLine().getReasonPhrase(), 200, response.getStatusLine().getStatusCode());
}
for (String username : Arrays.asList(KibanaUser.NAME, LogstashSystemUser.NAME)) {
String payload = "{\"password\": \"" + new String(reservedPassword.getChars()) + "\"}";
HttpEntity entity = new NStringEntity(payload, ContentType.APPLICATION_JSON);
BasicHeader authHeader = new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
UsernamePasswordToken.basicAuthHeaderValue(ElasticUser.NAME, SecuritySettingsSource.TEST_PASSWORD_SECURE_STRING));
UsernamePasswordToken.basicAuthHeaderValue(ElasticUser.NAME, reservedPassword));
String route = "/_xpack/security/user/" + username + "/_password";
Response response = getRestClient().performRequest("PUT", route, Collections.emptyMap(), entity, authHeader);
Response response = restClient.performRequest("PUT", route, Collections.emptyMap(), entity, authHeader);
assertEquals(response.getStatusLine().getReasonPhrase(), 200, response.getStatusLine().getStatusCode());
}
logger.info("setting up reserved passwords finished");
}

View File

@ -6,7 +6,6 @@
package org.elasticsearch.test;
import org.elasticsearch.AbstractOldXPackIndicesBackwardsCompatibilityTestCase;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
@ -37,7 +36,6 @@ import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.ml.MachineLearning;
import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.client.SecurityClient;
import org.junit.AfterClass;
import org.junit.Before;
@ -45,6 +43,7 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Collection;
@ -73,6 +72,7 @@ import static org.hamcrest.core.IsCollectionContaining.hasItem;
public abstract class SecurityIntegTestCase extends ESIntegTestCase {
private static SecuritySettingsSource SECURITY_DEFAULT_SETTINGS;
protected static SecureString BOOTSTRAP_PASSWORD = null;
/**
* Settings used when the {@link org.elasticsearch.test.ESIntegTestCase.ClusterScope} is set to
@ -82,6 +82,11 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
*/
private static CustomSecuritySettingsSource customSecuritySettingsSource = null;
@BeforeClass
public static void generateBootstrapPassword() {
BOOTSTRAP_PASSWORD = new SecureString("FOOBAR".toCharArray());
}
//UnicastZen requires the number of nodes in a cluster to generate the unicast configuration.
//The number of nodes is randomized though, but we can predict what the maximum number of nodes will be
//and configure them all in unicast.hosts
@ -215,6 +220,10 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
SecuritySettingsSource.addSecureSettings(builder, secureSettings ->
secureSettings.merge((MockSecureSettings) customBuilder.getSecureSettings()));
}
if (builder.getSecureSettings() == null) {
builder.setSecureSettings(new MockSecureSettings());
}
((MockSecureSettings) builder.getSecureSettings()).setString("bootstrap.password", BOOTSTRAP_PASSWORD.toString());
return builder.build();
}
@ -469,29 +478,6 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase {
}
}
public void ensureElasticPasswordBootstrapped() throws Exception {
ensureElasticPasswordBootstrapped(internalCluster());
}
public void ensureElasticPasswordBootstrapped(InternalTestCluster internalTestCluster) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
SecureString testPasswordHashed = new SecureString(SecuritySettingsSource.TEST_PASSWORD_HASHED.toCharArray());
ReservedRealm reservedRealm = internalTestCluster.getInstances(ReservedRealm.class).iterator().next();
reservedRealm.bootstrapElasticUserCredentials(testPasswordHashed, new ActionListener<Boolean>() {
@Override
public void onResponse(Boolean passwordSet) {
latch.countDown();
}
@Override
public void onFailure(Exception e) {
logger.error("Exception attempting to bootstrap password for test", e);
fail("Failed to bootstrap elastic password for test due to exception: " + e.getMessage());
}
});
latch.await();
}
public void assertSecurityIndexWriteable() throws Exception {
for (ClusterService clusterService : internalCluster().getInstances(ClusterService.class)) {
assertBusy(() -> {

View File

@ -7,7 +7,9 @@ package org.elasticsearch.xpack.security;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateObserver;
import org.elasticsearch.cluster.service.ClusterService;
@ -40,6 +42,8 @@ import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
@ -83,10 +87,16 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
new SecuritySettingsSource(defaultMaxNumberOfNodes(), useGeneratedSSL, createTempDir(), Scope.SUITE) {
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
Settings.Builder builder = Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(NetworkModule.HTTP_ENABLED.getKey(), true)
.build();
.put(NetworkModule.HTTP_ENABLED.getKey(), true);
if (builder.getSecureSettings() == null) {
builder.setSecureSettings(new MockSecureSettings());
}
((MockSecureSettings) builder.getSecureSettings()).setString("bootstrap.password",
BOOTSTRAP_PASSWORD.toString());
return builder.build();
}
@Override
@ -296,19 +306,19 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
}
public void testThatTribeCanAuthenticateElasticUser() throws Exception {
setupTribeNode(Settings.EMPTY);
ensureElasticPasswordBootstrapped(internalCluster());
setupTribeNode(Settings.EMPTY);
assertTribeNodeHasAllIndices();
ClusterHealthResponse response = tribeClient.filterWithHeader(Collections.singletonMap("Authorization",
UsernamePasswordToken.basicAuthHeaderValue("elastic", SecuritySettingsSource.TEST_PASSWORD_SECURE_STRING)))
UsernamePasswordToken.basicAuthHeaderValue("elastic", getReservedPassword())))
.admin().cluster().prepareHealth().get();
assertNoTimeout(response);
}
public void testThatTribeCanAuthenticateElasticUserWithChangedPassword() throws Exception {
setupTribeNode(Settings.EMPTY);
InternalTestCluster cluster = randomBoolean() ? internalCluster() : cluster2;
ensureElasticPasswordBootstrapped(cluster);
setupTribeNode(Settings.EMPTY);
securityClient(cluster.client()).prepareChangePassword("elastic", "password".toCharArray()).get();
assertTribeNodeHasAllIndices();
@ -319,9 +329,9 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
}
public void testThatTribeClustersHaveDifferentPasswords() throws Exception {
setupTribeNode(Settings.EMPTY);
ensureElasticPasswordBootstrapped(internalCluster());
ensureElasticPasswordBootstrapped(cluster2);
setupTribeNode(Settings.EMPTY);
securityClient().prepareChangePassword("elastic", "password".toCharArray()).get();
securityClient(cluster2.client()).prepareChangePassword("elastic", "password2".toCharArray()).get();
@ -380,14 +390,15 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
public void testUsersInNonPreferredClusterOnly() throws Exception {
final String preferredTribe = randomBoolean() ? "t1" : "t2";
// only create users in the non preferred client
final InternalTestCluster nonPreferredCluster = "t1".equals(preferredTribe) ? cluster2 : internalCluster();
ensureElasticPasswordBootstrapped(nonPreferredCluster);
setupTribeNode(Settings.builder().put("tribe.on_conflict", "prefer_" + preferredTribe).build());
final int randomUsers = scaledRandomIntBetween(3, 8);
List<String> shouldBeSuccessfulUsers = new ArrayList<>();
// only create users in the non preferred client
final InternalTestCluster nonPreferredCluster = "t1".equals(preferredTribe) ? cluster2 : internalCluster();
ensureElasticPasswordBootstrapped(nonPreferredCluster);
for (int i = 0; i < randomUsers; i++) {
final String username = "user" + i;
PutUserResponse response =
@ -405,6 +416,16 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
}
}
private void ensureElasticPasswordBootstrapped(InternalTestCluster cluster) {
NodesInfoResponse nodesInfoResponse = cluster.client().admin().cluster().prepareNodesInfo().get();
assertFalse(nodesInfoResponse.hasFailures());
try (RestClient restClient = createRestClient(nodesInfoResponse.getNodes(), null, "http")) {
setupReservedPasswords(restClient);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void testUserModificationUsingTribeNodeAreDisabled() throws Exception {
ensureElasticPasswordBootstrapped(internalCluster());
@ -467,11 +488,12 @@ public class SecurityTribeIT extends NativeRealmIntegTestCase {
public void testRetrieveRolesOnNonPreferredClusterOnly() throws Exception {
final String preferredTribe = randomBoolean() ? "t1" : "t2";
final InternalTestCluster nonPreferredCluster = "t1".equals(preferredTribe) ? cluster2 : internalCluster();
ensureElasticPasswordBootstrapped(nonPreferredCluster);
setupTribeNode(Settings.builder().put("tribe.on_conflict", "prefer_" + preferredTribe).build());
final int randomRoles = scaledRandomIntBetween(3, 8);
List<String> shouldBeSuccessfulRoles = new ArrayList<>();
final InternalTestCluster nonPreferredCluster = "t1".equals(preferredTribe) ? cluster2 : internalCluster();
ensureElasticPasswordBootstrapped(nonPreferredCluster);
Client nonPreferredClient = nonPreferredCluster.client();
for (int i = 0; i < randomRoles; i++) {

View File

@ -9,6 +9,7 @@ import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
@ -33,6 +34,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import static org.hamcrest.Matchers.contains;
@ -312,63 +314,73 @@ public class ReservedRealmTests extends ESTestCase {
}
@SuppressWarnings("unchecked")
public void testBootstrapElasticPassword() {
ReservedUserInfo user = new ReservedUserInfo(ReservedRealm.EMPTY_PASSWORD_HASH, true, true);
mockGetAllReservedUserInfo(usersStore, Collections.singletonMap(ElasticUser.NAME, user));
Settings settings = Settings.builder().build();
public void testBootstrapElasticPasswordWorksOnceSecurityIndexExists() throws Exception {
MockSecureSettings mockSecureSettings = new MockSecureSettings();
mockSecureSettings.setString("bootstrap.password", "foobar");
Settings settings = Settings.builder().setSecureSettings(mockSecureSettings).build();
when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(true);
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
PlainActionFuture<Boolean> listenerFuture = new PlainActionFuture<>();
SecureString passwordHash = new SecureString(randomAlphaOfLength(10).toCharArray());
reservedRealm.bootstrapElasticUserCredentials(passwordHash, listenerFuture);
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
ArgumentCaptor<ChangePasswordRequest> requestCaptor = ArgumentCaptor.forClass(ChangePasswordRequest.class);
ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
verify(usersStore).changePassword(requestCaptor.capture(), listenerCaptor.capture());
assertEquals(passwordHash.getChars(), requestCaptor.getValue().passwordHash());
listenerCaptor.getValue().onResponse(null);
assertTrue(listenerFuture.actionGet());
doAnswer((i) -> {
ActionListener callback = (ActionListener) i.getArguments()[1];
callback.onResponse(null);
return null;
}).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class));
reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
mockSecureSettings.getString("bootstrap.password")),
listener);
final AuthenticationResult result = listener.get();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
}
public void testBootstrapElasticPasswordNotSetIfPasswordExists() {
mockGetAllReservedUserInfo(usersStore, Collections.singletonMap(ElasticUser.NAME, new ReservedUserInfo(new char[7], true, false)));
when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(true);
Settings settings = Settings.builder().build();
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
SecureString passwordHash = new SecureString(randomAlphaOfLength(10).toCharArray());
reservedRealm.bootstrapElasticUserCredentials(passwordHash, new PlainActionFuture<>());
verify(usersStore, times(0)).changePassword(any(ChangePasswordRequest.class), any());
}
public void testBootstrapElasticPasswordSettingFails() {
ReservedUserInfo user = new ReservedUserInfo(ReservedRealm.EMPTY_PASSWORD_HASH, true, true);
mockGetAllReservedUserInfo(usersStore, Collections.singletonMap(ElasticUser.NAME, user));
Settings settings = Settings.builder().build();
public void testBootstrapElasticPasswordFailsOnceElasticUserExists() throws Exception {
MockSecureSettings mockSecureSettings = new MockSecureSettings();
mockSecureSettings.setString("bootstrap.password", "foobar");
Settings settings = Settings.builder().setSecureSettings(mockSecureSettings).build();
when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(true);
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
PlainActionFuture<Boolean> listenerFuture = new PlainActionFuture<>();
SecureString passwordHash = new SecureString(randomAlphaOfLength(10).toCharArray());
reservedRealm.bootstrapElasticUserCredentials(passwordHash, listenerFuture);
ArgumentCaptor<ChangePasswordRequest> requestCaptor = ArgumentCaptor.forClass(ChangePasswordRequest.class);
ArgumentCaptor<ActionListener> listenerCaptor = ArgumentCaptor.forClass(ActionListener.class);
verify(usersStore).changePassword(requestCaptor.capture(), listenerCaptor.capture());
assertEquals(passwordHash.getChars(), requestCaptor.getValue().passwordHash());
listenerCaptor.getValue().onFailure(new RuntimeException());
expectThrows(RuntimeException.class, listenerFuture::actionGet);
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
SecureString password = new SecureString("password".toCharArray());
doAnswer((i) -> {
ActionListener callback = (ActionListener) i.getArguments()[1];
char[] hash = Hasher.BCRYPT.hash(password);
ReservedUserInfo userInfo = new ReservedUserInfo(hash, true, false);
callback.onResponse(userInfo);
return null;
}).when(usersStore).getReservedUserInfo(eq("elastic"), any(ActionListener.class));
reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
mockSecureSettings.getString("bootstrap.password")), listener);
assertFailedAuthentication(listener, "elastic");
// now try with the real password
listener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(), password), listener);
final AuthenticationResult result = listener.get();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
}
public void testBootstrapElasticPasswordWorksBeforeSecurityIndexExists() throws ExecutionException, InterruptedException {
MockSecureSettings mockSecureSettings = new MockSecureSettings();
mockSecureSettings.setString("bootstrap.password", "foobar");
Settings settings = Settings.builder().setSecureSettings(mockSecureSettings).build();
when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(false);
final ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore,
new AnonymousUser(Settings.EMPTY), securityLifecycleService, new ThreadContext(Settings.EMPTY));
PlainActionFuture<AuthenticationResult> listener = new PlainActionFuture<>();
reservedRealm.doAuthenticate(new UsernamePasswordToken(new ElasticUser(true).principal(),
mockSecureSettings.getString("bootstrap.password")),
listener);
final AuthenticationResult result = listener.get();
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
}
/*
* NativeUserStore#getAllReservedUserInfo is pkg private we can't mock it otherwise
*/

View File

@ -1,337 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.bootstrap;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.settings.MockSecureSettings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.SecurityLifecycleService;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.junit.Before;
import org.mockito.ArgumentCaptor;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
public class BootstrapElasticPasswordTests extends ESTestCase {
private ClusterService clusterService;
private ReservedRealm realm;
private SecurityLifecycleService lifecycle;
private ArgumentCaptor<ClusterStateListener> listenerCaptor;
private ArgumentCaptor<ActionListener> actionLister;
@Before
public void setupBootstrap() {
clusterService = mock(ClusterService.class);
realm = mock(ReservedRealm.class);
lifecycle = mock(SecurityLifecycleService.class);
listenerCaptor = ArgumentCaptor.forClass(ClusterStateListener.class);
actionLister = ArgumentCaptor.forClass(ActionListener.class);
}
public void testNoListenerAttachedWhenNoBootstrapPassword() {
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(Settings.EMPTY, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verifyZeroInteractions(clusterService);
}
public void testNoListenerAttachedWhenReservedRealmDisabled() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.put(XPackSettings.RESERVED_REALM_ENABLED_SETTING.getKey(), false)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verifyZeroInteractions(clusterService);
}
public void testPasswordHasToBeValid() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(5));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
expectThrows(ValidationException.class, bootstrap::initiatePasswordBootstrap);
verifyZeroInteractions(clusterService);
}
public void testDoesNotBootstrapUntilStateRecovered() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = mock(ClusterChangedEvent.class);
ClusterState state = mock(ClusterState.class);
ClusterBlocks blocks = mock(ClusterBlocks.class);
when(event.state()).thenReturn(state);
when(state.blocks()).thenReturn(blocks);
when(blocks.hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)).thenReturn(true);
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
listener.clusterChanged(event);
verifyZeroInteractions(realm);
}
public void testDoesNotBootstrapUntilSecurityIndexUpdated() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(true);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(true);
listener.clusterChanged(event);
verifyZeroInteractions(realm);
}
public void testDoesNotBootstrapUntilSecurityIndexIfExistingIsAvailable() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(false);
listener.clusterChanged(event);
verifyZeroInteractions(realm);
}
public void testDoesNotBootstrapUntilSecurityIndexWriteable() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(false);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(true);
listener.clusterChanged(event);
verifyZeroInteractions(realm);
}
public void testDoesAllowBootstrapForUnavailableIndexIfNotExisting() {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), randomAlphaOfLength(10));
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(false);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(false);
listener.clusterChanged(event);
verify(realm).bootstrapElasticUserCredentials(any(SecureString.class), any(ActionListener.class));
}
public void testDoesNotBootstrapBeginsWhenRecoveryDoneAndIndexReady() {
String password = randomAlphaOfLength(10);
ensureBootstrapStarted(password);
ArgumentCaptor<SecureString> hashedPasswordCaptor = ArgumentCaptor.forClass(SecureString.class);
verify(realm).bootstrapElasticUserCredentials(hashedPasswordCaptor.capture(), any(ActionListener.class));
assertTrue(Hasher.BCRYPT.verify(new SecureString(password.toCharArray()), hashedPasswordCaptor.getValue().getChars()));
}
public void testWillNotAllowTwoConcurrentBootstrapAttempts() {
String password = randomAlphaOfLength(10);
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), password);
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(true);
listener.clusterChanged(event);
listener.clusterChanged(event);
verify(realm, times(1)).bootstrapElasticUserCredentials(any(SecureString.class), any(ActionListener.class));
}
public void testWillNotAllowSecondBootstrapAttempt() {
String password = randomAlphaOfLength(10);
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), password);
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(true);
listener.clusterChanged(event);
verify(realm, times(1)).bootstrapElasticUserCredentials(any(SecureString.class), actionLister.capture());
actionLister.getValue().onResponse(true);
listener.clusterChanged(event);
verify(realm, times(1)).bootstrapElasticUserCredentials(any(SecureString.class), any());
}
public void testBootstrapCompleteRemovesListener() {
String password = randomAlphaOfLength(10);
ensureBootstrapStarted(password);
verify(realm).bootstrapElasticUserCredentials(any(SecureString.class), actionLister.capture());
actionLister.getValue().onResponse(randomBoolean());
verify(clusterService).removeListener(listenerCaptor.getValue());
}
public void testBootstrapFailedRemovesListener() {
String password = randomAlphaOfLength(10);
ensureBootstrapStarted(password);
verify(realm).bootstrapElasticUserCredentials(any(SecureString.class), actionLister.capture());
actionLister.getValue().onFailure(new RuntimeException("failed"));
verify(clusterService).removeListener(listenerCaptor.getValue());
}
private void ensureBootstrapStarted(String password) {
MockSecureSettings secureSettings = new MockSecureSettings();
secureSettings.setString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(), password);
Settings settings = Settings.builder()
.setSecureSettings(secureSettings)
.build();
BootstrapElasticPassword bootstrap = new BootstrapElasticPassword(settings, clusterService, realm, lifecycle);
bootstrap.initiatePasswordBootstrap();
verify(clusterService).addListener(listenerCaptor.capture());
ClusterStateListener listener = listenerCaptor.getValue();
ClusterChangedEvent event = getStateRecoveredEvent();
when(lifecycle.isSecurityIndexOutOfDate()).thenReturn(false);
when(lifecycle.isSecurityIndexWriteable()).thenReturn(true);
when(lifecycle.isSecurityIndexExisting()).thenReturn(true);
when(lifecycle.isSecurityIndexAvailable()).thenReturn(true);
listener.clusterChanged(event);
}
private ClusterChangedEvent getStateRecoveredEvent() {
ClusterChangedEvent event = mock(ClusterChangedEvent.class);
ClusterState state = mock(ClusterState.class);
ClusterBlocks blocks = mock(ClusterBlocks.class);
when(event.state()).thenReturn(state);
when(state.blocks()).thenReturn(blocks);
when(blocks.hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)).thenReturn(false);
return event;
}
}