From cec90f452a6d9763ae7d9f233747d86df722f192 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Fri, 12 Jan 2018 12:45:52 -0700 Subject: [PATCH] IndexLifecycleManager checks for index existense and up to date mappings (elastic/x-pack-elasticsearch#3515) This change makes the IndexLifecycleManager check for both index existence and up to date mappings on the index prior to executing the provided runnable. Doing this provides a mechanism to make non-breaking mapping updates to the security index in minor versions. relates elastic/x-pack-elasticsearch#3462 Original commit: elastic/x-pack-elasticsearch@80f05d83b481eece316ff5c2a6a0c527c9a79cf9 --- .../xpack/security/Security.java | 89 ++-- .../security/SecurityLifecycleService.java | 54 +- .../xpack/security/authc/TokenService.java | 80 ++- .../authc/esnative/NativeUsersStore.java | 486 +++++++----------- .../mapper/NativeRoleMappingStore.java | 8 +- .../authz/store/NativeRolesStore.java | 303 +++++------ .../support/IndexLifecycleManager.java | 78 ++- .../elasticsearch/license/LicensingTests.java | 6 + .../test/SecurityIntegTestCase.java | 31 +- .../SecurityClusterClientYamlTestCase.java | 84 --- .../SecurityLifecycleServiceTests.java | 4 +- .../xpack/security/SecuritySettingsTests.java | 28 +- .../authc/AuthenticationServiceTests.java | 8 +- .../security/authc/TokenAuthIntegTests.java | 2 +- .../security/authc/TokenServiceTests.java | 23 +- .../authc/esnative/NativeUsersStoreTests.java | 8 +- .../security/authz/SecurityScrollTests.java | 2 +- .../IndexLifecycleManagerIntegTests.java | 2 +- .../support/IndexLifecycleManagerTests.java | 8 +- .../xpack/test/rest/XPackRestIT.java | 2 - .../xpack/restart/FullClusterRestartIT.java | 6 - ...sterSearchWithSecurityYamlTestSuiteIT.java | 2 +- .../UpgradeClusterClientYamlTestSuiteIT.java | 4 +- .../esnative/tool/SetupPasswordToolIT.java | 4 - ...rityWithMustacheClientYamlTestSuiteIT.java | 3 +- .../test/TribeWithSecurityIT.java | 8 +- 26 files changed, 532 insertions(+), 801 deletions(-) delete mode 100644 plugin/src/test/java/org/elasticsearch/xpack/security/SecurityClusterClientYamlTestCase.java diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java index 61d9a7fec71..e24b0c7955a 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -207,7 +207,6 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; @@ -755,29 +754,22 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus } final boolean indexAuditingEnabled = Security.indexAuditLoggingEnabled(settings); - final String auditIndex; if (indexAuditingEnabled) { - auditIndex = "," + IndexAuditTrail.INDEX_NAME_PREFIX + "*"; - } else { - auditIndex = ""; - } - String securityIndices = SecurityLifecycleService.indexNames().stream() - .collect(Collectors.joining(",")); - String errorMessage = LoggerMessageFormat.format( - "the [action.auto_create_index] setting value [{}] is too" + - " restrictive. disable [action.auto_create_index] or set it to " + - "[{}{}]", (Object) value, securityIndices, auditIndex); - if (Booleans.isFalse(value)) { - throw new IllegalArgumentException(errorMessage); - } + String auditIndex = IndexAuditTrail.INDEX_NAME_PREFIX + "*"; + String errorMessage = LoggerMessageFormat.format( + "the [action.auto_create_index] setting value [{}] is too" + + " restrictive. disable [action.auto_create_index] or set it to include " + + "[{}]", (Object) value, auditIndex); + if (Booleans.isFalse(value)) { + throw new IllegalArgumentException(errorMessage); + } - if (Booleans.isTrue(value)) { - return; - } + if (Booleans.isTrue(value)) { + return; + } - String[] matches = Strings.commaDelimitedListToStringArray(value); - List indices = new ArrayList<>(SecurityLifecycleService.indexNames()); - if (indexAuditingEnabled) { + String[] matches = Strings.commaDelimitedListToStringArray(value); + List indices = new ArrayList<>(); DateTime now = new DateTime(DateTimeZone.UTC); // just use daily rollover indices.add(IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, now, IndexNameResolver.Rollover.DAILY)); @@ -788,34 +780,32 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus indices.add(IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, now.plusMonths(4), IndexNameResolver.Rollover.DAILY)); indices.add(IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, now.plusMonths(5), IndexNameResolver.Rollover.DAILY)); indices.add(IndexNameResolver.resolve(IndexAuditTrail.INDEX_NAME_PREFIX, now.plusMonths(6), IndexNameResolver.Rollover.DAILY)); - } - for (String index : indices) { - boolean matched = false; - for (String match : matches) { - char c = match.charAt(0); - if (c == '-') { - if (Regex.simpleMatch(match.substring(1), index)) { - throw new IllegalArgumentException(errorMessage); - } - } else if (c == '+') { - if (Regex.simpleMatch(match.substring(1), index)) { - matched = true; - break; - } - } else { - if (Regex.simpleMatch(match, index)) { - matched = true; - break; + for (String index : indices) { + boolean matched = false; + for (String match : matches) { + char c = match.charAt(0); + if (c == '-') { + if (Regex.simpleMatch(match.substring(1), index)) { + throw new IllegalArgumentException(errorMessage); + } + } else if (c == '+') { + if (Regex.simpleMatch(match.substring(1), index)) { + matched = true; + break; + } + } else { + if (Regex.simpleMatch(match, index)) { + matched = true; + break; + } } } + if (!matched) { + throw new IllegalArgumentException(errorMessage); + } } - if (!matched) { - throw new IllegalArgumentException(errorMessage); - } - } - if (indexAuditingEnabled) { logger.warn("the [action.auto_create_index] setting is configured to be restrictive [{}]. " + " for the next 6 months audit indices are allowed to be created, but please make sure" + " that any future history indices after 6 months with the pattern " + @@ -904,17 +894,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus public UnaryOperator> getIndexTemplateMetaDataUpgrader() { return templates -> { - final byte[] securityTemplate = TemplateUtils.loadTemplate("/" + SECURITY_TEMPLATE_NAME + ".json", - Version.CURRENT.toString(), IndexLifecycleManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8); + templates.remove(SECURITY_TEMPLATE_NAME); final XContent xContent = XContentFactory.xContent(XContentType.JSON); - - try (XContentParser parser = xContent.createParser(NamedXContentRegistry.EMPTY, securityTemplate)) { - templates.put(SECURITY_TEMPLATE_NAME, IndexTemplateMetaData.Builder.fromXContent(parser, SECURITY_TEMPLATE_NAME)); - } catch (IOException e) { - // TODO: should we handle this with a thrown exception? - logger.error("Error loading template [{}] as part of metadata upgrading", SECURITY_TEMPLATE_NAME); - } - final byte[] auditTemplate = TemplateUtils.loadTemplate("/" + IndexAuditTrail.INDEX_TEMPLATE_NAME + ".json", Version.CURRENT.toString(), IndexLifecycleManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java index 6ba1a6a68d8..8dd7a3b0c9c 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/SecurityLifecycleService.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security; import org.apache.logging.log4j.Logger; import org.elasticsearch.Version; -import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; @@ -28,6 +27,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -64,7 +64,7 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust this.settings = settings; this.threadPool = threadPool; this.indexAuditTrail = indexAuditTrail; - this.securityIndex = new IndexLifecycleManager(settings, client, SECURITY_INDEX_NAME, SECURITY_TEMPLATE_NAME); + this.securityIndex = new IndexLifecycleManager(settings, client, SECURITY_INDEX_NAME); clusterService.addListener(this); clusterService.addLifecycleListener(new LifecycleListener() { @Override @@ -114,20 +114,34 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust return securityIndex; } + /** + * Returns {@code true} if the security index exists + */ public boolean isSecurityIndexExisting() { return securityIndex.indexExists(); } + /** + * Returns true if the security index does not exist or it exists and has the current + * value for the index.format index setting + */ public boolean isSecurityIndexUpToDate() { return securityIndex.isIndexUpToDate(); } + /** + * Returns true if the security index exists and all primary shards are active + */ public boolean isSecurityIndexAvailable() { return securityIndex.isAvailable(); } - public boolean isSecurityIndexWriteable() { - return securityIndex.isWritable(); + /** + * Returns true if the security index does not exist or the mappings are up to date + * based on the version in the _meta field + */ + public boolean isSecurityIndexMappingUpToDate() { + return securityIndex().isMappingUpToDate(); } /** @@ -170,22 +184,16 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust } } - public static boolean securityIndexMappingAndTemplateSufficientToRead(ClusterState clusterState, - Logger logger) { - return checkTemplateAndMappingVersions(clusterState, logger, MIN_READ_VERSION::onOrBefore); + public static boolean securityIndexMappingSufficientToRead(ClusterState clusterState, Logger logger) { + return checkMappingVersions(clusterState, logger, MIN_READ_VERSION::onOrBefore); } - public static boolean securityIndexMappingAndTemplateUpToDate(ClusterState clusterState, - Logger logger) { - return checkTemplateAndMappingVersions(clusterState, logger, Version.CURRENT::equals); + static boolean securityIndexMappingUpToDate(ClusterState clusterState, Logger logger) { + return checkMappingVersions(clusterState, logger, Version.CURRENT::equals); } - private static boolean checkTemplateAndMappingVersions(ClusterState clusterState, Logger logger, - Predicate versionPredicate) { - return IndexLifecycleManager.checkTemplateExistsAndVersionMatches(SECURITY_TEMPLATE_NAME, - clusterState, logger, versionPredicate) && - IndexLifecycleManager.checkIndexMappingVersionMatches(SECURITY_INDEX_NAME, - clusterState, logger, versionPredicate); + private static boolean checkMappingVersions(ClusterState clusterState, Logger logger, Predicate versionPredicate) { + return IndexLifecycleManager.checkIndexMappingVersionMatches(SECURITY_INDEX_NAME, clusterState, logger, versionPredicate); } public static List indexNames() { @@ -193,17 +201,11 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust } /** - * Creates the security index, if it does not already exist, then runs the given - * action on the security index. + * Prepares the security index by creating it if it doesn't exist or updating the mappings if the mappings are + * out of date. After any tasks have been executed, the runnable is then executed. */ - public void createIndexIfNeededThenExecute(final ActionListener listener, final Runnable andThen) { - if (!isSecurityIndexExisting() || isSecurityIndexUpToDate()) { - securityIndex.createIndexIfNeededThenExecute(listener, andThen); - } else { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - } + public void prepareIndexIfNeededThenExecute(final Consumer consumer, final Runnable andThen) { + securityIndex.prepareIndexIfNeededThenExecute(consumer, andThen); } /** diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index 83f87aff1b0..ee64493eb53 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -271,14 +271,7 @@ public final class TokenService extends AbstractComponent { */ public void invalidateToken(String tokenString, ActionListener listener) { ensureEnabled(); - if (lifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (lifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("cannot write to the tokens index")); - } else if (Strings.isNullOrEmpty(tokenString)) { + if (Strings.isNullOrEmpty(tokenString)) { listener.onFailure(new IllegalArgumentException("token must be provided")); } else { maybeStartTokenRemover(); @@ -291,7 +284,7 @@ public final class TokenService extends AbstractComponent { listener.onResponse(false); } else { final String id = getDocumentId(userToken); - lifecycleService.createIndexIfNeededThenExecute(listener, () -> { + lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE, id) .setOpType(OpType.CREATE) @@ -338,47 +331,38 @@ public final class TokenService extends AbstractComponent { * have been explicitly cleared. */ private void checkIfTokenIsRevoked(UserToken userToken, ActionListener listener) { - if (lifecycleService.isSecurityIndexAvailable()) { - if (lifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE, getDocumentId(userToken)).request(), - new ActionListener() { - - @Override - public void onResponse(GetResponse response) { - if (response.isExists()) { - // this token is explicitly expired! - listener.onFailure(expiredTokenException()); - } else { - listener.onResponse(userToken); - } - } - - @Override - public void onFailure(Exception e) { - // if the index or the shard is not there / available we assume that - // the token is not valid - if (TransportActions.isShardNotAvailableException(e)) { - logger.warn("failed to get token [{}] since index is not available", userToken.getId()); - listener.onResponse(null); - } else { - logger.error(new ParameterizedMessage("failed to get token [{}]", userToken.getId()), e); - listener.onFailure(e); - } - } - }, client::get); - } else if (lifecycleService.isSecurityIndexExisting()) { - // index exists but the index isn't available, do not trust the token - logger.warn("could not validate token as the security index is not available"); - listener.onResponse(null); - } else { + if (lifecycleService.isSecurityIndexExisting() == false) { // index doesn't exist so the token is considered valid. listener.onResponse(userToken); + } else { + lifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, TYPE, getDocumentId(userToken)).request(), + new ActionListener() { + + @Override + public void onResponse(GetResponse response) { + if (response.isExists()) { + // this token is explicitly expired! + listener.onFailure(expiredTokenException()); + } else { + listener.onResponse(userToken); + } + } + + @Override + public void onFailure(Exception e) { + // if the index or the shard is not there / available we assume that + // the token is not valid + if (TransportActions.isShardNotAvailableException(e)) { + logger.warn("failed to get token [{}] since index is not available", userToken.getId()); + listener.onResponse(null); + } else { + logger.error(new ParameterizedMessage("failed to get token [{}]", userToken.getId()), e); + listener.onFailure(e); + } + } + }, client::get)); } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 98b7b2964fa..f3c957f82b4 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -17,6 +17,7 @@ import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.ContextPreservingActionListener; +import org.elasticsearch.action.support.TransportActions; import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.Client; @@ -113,25 +114,23 @@ public class NativeUsersStore extends AbstractComponent { listener.onFailure(t); } }; - if (userNames.length == 1) { // optimization for single user lookup + + if (securityLifecycleService.isSecurityIndexExisting() == false) { + // TODO remove this short circuiting and fix tests that fail without this! + listener.onResponse(Collections.emptyList()); + } else if (userNames.length == 1) { // optimization for single user lookup final String username = userNames[0]; getUserAndPassword(username, ActionListener.wrap( (uap) -> listener.onResponse(uap == null ? Collections.emptyList() : Collections.singletonList(uap.user())), - handleException::accept)); + handleException)); } else { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational " + - "until the upgrade API is run on the security index")); - return; - } - try { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { final QueryBuilder query; if (userNames == null || userNames.length == 0) { query = QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), USER_DOC_TYPE); } else { final String[] users = Arrays.asList(userNames).stream() - .map(s -> getIdForUser(USER_DOC_TYPE, s)).toArray(String[]::new); + .map(s -> getIdForUser(USER_DOC_TYPE, s)).toArray(String[]::new); query = QueryBuilders.boolQuery().filter(QueryBuilders.idsQuery(INDEX_TYPE).addIds(users)); } final Supplier supplier = client.threadPool().getThreadContext().newRestorableContext(false); @@ -148,10 +147,7 @@ public class NativeUsersStore extends AbstractComponent { return u != null ? u.user() : null; }); } - } catch (Exception e) { - logger.error(new ParameterizedMessage("unable to retrieve users {}", Arrays.toString(userNames)), e); - listener.onFailure(e); - } + }); } } @@ -159,43 +155,34 @@ public class NativeUsersStore extends AbstractComponent { * Async method to retrieve a user and their password */ private void getUserAndPassword(final String user, final ActionListener listener) { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - try { - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, - INDEX_TYPE, getIdForUser(USER_DOC_TYPE, user)).request(), - new ActionListener() { - @Override - public void onResponse(GetResponse response) { - listener.onResponse(transformUser(response.getId(), response.getSource())); - } - - @Override - public void onFailure(Exception t) { - if (t instanceof IndexNotFoundException) { - logger.trace( - (org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage( - "could not retrieve user [{}] because security index does not exist", user), t); - } else { - logger.error(new ParameterizedMessage("failed to retrieve user [{}]", user), t); - } - // We don't invoke the onFailure listener here, instead - // we call the response with a null user - listener.onResponse(null); - } - }, client::get); - } catch (IndexNotFoundException infe) { - logger.trace((org.apache.logging.log4j.util.Supplier) - () -> new ParameterizedMessage("could not retrieve user [{}] because security index does not exist", user)); + if (securityLifecycleService.isSecurityIndexExisting() == false) { + // TODO remove this short circuiting and fix tests that fail without this! listener.onResponse(null); - } catch (Exception e) { - logger.error(new ParameterizedMessage("unable to retrieve user [{}]", user), e); - listener.onFailure(e); + } else { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, + INDEX_TYPE, getIdForUser(USER_DOC_TYPE, user)).request(), + new ActionListener() { + @Override + public void onResponse(GetResponse response) { + listener.onResponse(transformUser(response.getId(), response.getSource())); + } + + @Override + public void onFailure(Exception t) { + if (t instanceof IndexNotFoundException) { + logger.trace( + (org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage( + "could not retrieve user [{}] because security index does not exist", user), t); + } else { + logger.error(new ParameterizedMessage("failed to retrieve user [{}]", user), t); + } + // We don't invoke the onFailure listener here, instead + // we call the response with a null user + listener.onResponse(null); + } + }, client::get)); } } @@ -208,55 +195,46 @@ public class NativeUsersStore extends AbstractComponent { assert SystemUser.NAME.equals(username) == false && XPackUser.NAME.equals(username) == false : username + "is internal!"; if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("password cannot be changed as user service cannot write until template and " + - "mappings are up to date")); - return; - } - - final String docType; - if (ClientReservedRealm.isReserved(username, settings)) { - docType = RESERVED_USER_TYPE; } else { - docType = USER_DOC_TYPE; - } + final String docType; + if (ClientReservedRealm.isReserved(username, settings)) { + docType = RESERVED_USER_TYPE; + } else { + docType = USER_DOC_TYPE; + } - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(docType, username)) - .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.PASSWORD.getPreferredName(), String.valueOf(request.passwordHash())) - .setRefreshPolicy(request.getRefreshPolicy()).request(), - new ActionListener() { - @Override - public void onResponse(UpdateResponse updateResponse) { - assert updateResponse.getResult() == DocWriteResponse.Result.UPDATED; - clearRealmCache(request.username(), listener, null); - } - - @Override - public void onFailure(Exception e) { - if (isIndexNotFoundOrDocumentMissing(e)) { - if (docType.equals(RESERVED_USER_TYPE)) { - createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener); - } else { - logger.debug((org.apache.logging.log4j.util.Supplier) () -> - new ParameterizedMessage("failed to change password for user [{}]", request.username()), e); - ValidationException validationException = new ValidationException(); - validationException.addValidationError("user must exist in order to change password"); - listener.onFailure(validationException); - } - } else { - listener.onFailure(e); + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(docType, username)) + .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.PASSWORD.getPreferredName(), + String.valueOf(request.passwordHash())) + .setRefreshPolicy(request.getRefreshPolicy()).request(), + new ActionListener() { + @Override + public void onResponse(UpdateResponse updateResponse) { + assert updateResponse.getResult() == DocWriteResponse.Result.UPDATED; + clearRealmCache(request.username(), listener, null); } - } - }, client::update); - }); + + @Override + public void onFailure(Exception e) { + if (isIndexNotFoundOrDocumentMissing(e)) { + if (docType.equals(RESERVED_USER_TYPE)) { + createReservedUser(username, request.passwordHash(), request.getRefreshPolicy(), listener); + } else { + logger.debug((org.apache.logging.log4j.util.Supplier) () -> + new ParameterizedMessage("failed to change password for user [{}]", request.username()), e); + ValidationException validationException = new ValidationException(); + validationException.addValidationError("user must exist in order to change password"); + listener.onFailure(validationException); + } + } else { + listener.onFailure(e); + } + } + }, client::update); + }); + } } /** @@ -264,13 +242,7 @@ public class NativeUsersStore extends AbstractComponent { * has been indexed */ private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener listener) { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(RESERVED_USER_TYPE, username)) @@ -301,27 +273,10 @@ public class NativeUsersStore extends AbstractComponent { public void putUser(final PutUserRequest request, final ActionListener listener) { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("user cannot be created or changed as the user service cannot write until " + - "template and mappings are up to date")); - return; - } - - try { - if (request.passwordHash() == null) { - updateUserWithoutPassword(request, listener); - } else { - indexUser(request, listener); - } - } catch (Exception e) { - logger.error(new ParameterizedMessage("unable to put user [{}]", request.username()), e); - listener.onFailure(e); + } else if (request.passwordHash() == null) { + updateUserWithoutPassword(request, listener); + } else { + indexUser(request, listener); } } @@ -330,9 +285,8 @@ public class NativeUsersStore extends AbstractComponent { */ private void updateUserWithoutPassword(final PutUserRequest putUserRequest, final ActionListener listener) { assert putUserRequest.passwordHash() == null; - assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; // We must have an existing document - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) @@ -375,8 +329,7 @@ public class NativeUsersStore extends AbstractComponent { private void indexUser(final PutUserRequest putUserRequest, final ActionListener listener) { assert putUserRequest.passwordHash() != null; - assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) @@ -413,19 +366,7 @@ public class NativeUsersStore extends AbstractComponent { final ActionListener listener) { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("users may not be created or modified using a tribe node")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("enabled status cannot be changed as user service cannot write until template " + - "and mappings are up to date")); - return; - } - - if (ClientReservedRealm.isReserved(username, settings)) { + } else if (ClientReservedRealm.isReserved(username, settings)) { setReservedUserEnabled(username, enabled, refreshPolicy, true, listener); } else { setRegularUserEnabled(username, enabled, refreshPolicy, listener); @@ -434,115 +375,92 @@ public class NativeUsersStore extends AbstractComponent { private void setRegularUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy, final ActionListener listener) { - assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; - try { - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, - getIdForUser(USER_DOC_TYPE, username)) - .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) - .setRefreshPolicy(refreshPolicy) - .request(), - new ActionListener() { - @Override - public void onResponse(UpdateResponse updateResponse) { - clearRealmCache(username, listener, null); - } + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, + getIdForUser(USER_DOC_TYPE, username)) + .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) + .setRefreshPolicy(refreshPolicy) + .request(), + new ActionListener() { + @Override + public void onResponse(UpdateResponse updateResponse) { + clearRealmCache(username, listener, null); + } - @Override - public void onFailure(Exception e) { - Exception failure = e; - if (isIndexNotFoundOrDocumentMissing(e)) { - // if the index doesn't exist we can never update a user - // if the document doesn't exist, then this update is not valid - logger.debug((org.apache.logging.log4j.util.Supplier) - () -> new ParameterizedMessage("failed to {} user [{}]", - enabled ? "enable" : "disable", username), e); - ValidationException validationException = new ValidationException(); - validationException.addValidationError("only existing users can be " + - (enabled ? "enabled" : "disabled")); - failure = validationException; - } - listener.onFailure(failure); + @Override + public void onFailure(Exception e) { + Exception failure = e; + if (isIndexNotFoundOrDocumentMissing(e)) { + // if the index doesn't exist we can never update a user + // if the document doesn't exist, then this update is not valid + logger.debug((org.apache.logging.log4j.util.Supplier) + () -> new ParameterizedMessage("failed to {} user [{}]", + enabled ? "enable" : "disable", username), e); + ValidationException validationException = new ValidationException(); + validationException.addValidationError("only existing users can be " + + (enabled ? "enabled" : "disabled")); + failure = validationException; } - }, client::update); - }); - } catch (Exception e) { - listener.onFailure(e); - } + listener.onFailure(failure); + } + }, client::update); + }); } private void setReservedUserEnabled(final String username, final boolean enabled, final RefreshPolicy refreshPolicy, boolean clearCache, final ActionListener listener) { - assert !securityLifecycleService.isSecurityIndexOutOfDate() : "security index should be up to date"; - try { - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, - getIdForUser(RESERVED_USER_TYPE, username)) - .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) - .setUpsert(XContentType.JSON, - Fields.PASSWORD.getPreferredName(), "", - Fields.ENABLED.getPreferredName(), enabled, - Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) - .setRefreshPolicy(refreshPolicy) - .request(), - new ActionListener() { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareUpdate(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, + getIdForUser(RESERVED_USER_TYPE, username)) + .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) + .setUpsert(XContentType.JSON, + Fields.PASSWORD.getPreferredName(), "", + Fields.ENABLED.getPreferredName(), enabled, + Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) + .setRefreshPolicy(refreshPolicy) + .request(), + new ActionListener() { + @Override + public void onResponse(UpdateResponse updateResponse) { + if (clearCache) { + clearRealmCache(username, listener, null); + } else { + listener.onResponse(null); + } + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }, client::update); + }); + } + + public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener listener) { + if (isTribeNode) { + listener.onFailure(new UnsupportedOperationException("users may not be deleted using a tribe node")); + } else { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, + INDEX_TYPE, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())).request(); + request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy()); + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, + new ActionListener() { @Override - public void onResponse(UpdateResponse updateResponse) { - if (clearCache) { - clearRealmCache(username, listener, null); - } else { - listener.onResponse(null); - } + public void onResponse(DeleteResponse deleteResponse) { + clearRealmCache(deleteUserRequest.username(), listener, + deleteResponse.getResult() == DocWriteResponse.Result.DELETED); } @Override public void onFailure(Exception e) { listener.onFailure(e); } - }, client::update); + }, client::delete); }); - } catch (Exception e) { - listener.onFailure(e); - } - } - - public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener listener) { - if (isTribeNode) { - listener.onFailure(new UnsupportedOperationException("users may not be deleted using a tribe node")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("user cannot be deleted as user service cannot write until template and " + - "mappings are up to date")); - return; - } - - try { - DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, - INDEX_TYPE, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())).request(); - request.indicesOptions().ignoreUnavailable(); - request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy()); - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, new ActionListener() { - @Override - public void onResponse(DeleteResponse deleteResponse) { - clearRealmCache(deleteUserRequest.username(), listener, - deleteResponse.getResult() == DocWriteResponse.Result.DELETED); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }, client::delete); - } catch (Exception e) { - logger.error("unable to remove user", e); - listener.onFailure(e); } } @@ -565,62 +483,52 @@ public class NativeUsersStore extends AbstractComponent { } void getReservedUserInfo(String username, ActionListener listener) { - if (!securityLifecycleService.isSecurityIndexExisting()) { - listener.onFailure(new IllegalStateException("Attempt to get reserved user info but the security index does not exist")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm not be operational until " + - "the upgrade API is run on the security index")); - return; - } - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, getIdForUser(RESERVED_USER_TYPE, username)) - .request(), - new ActionListener() { - @Override - public void onResponse(GetResponse getResponse) { - if (getResponse.isExists()) { - Map sourceMap = getResponse.getSourceAsMap(); - String password = (String) sourceMap.get(Fields.PASSWORD.getPreferredName()); - Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName()); - if (password == null) { - listener.onFailure(new IllegalStateException("password hash must not be null!")); - } else if (enabled == null) { - listener.onFailure(new IllegalStateException("enabled must not be null!")); - } else if (password.isEmpty()) { - listener.onResponse((enabled ? ReservedRealm.ENABLED_DEFAULT_USER_INFO : ReservedRealm - .DISABLED_DEFAULT_USER_INFO).deepClone()); - } else { - listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled, false)); - } - } else { - listener.onResponse(null); - } - } + if (securityLifecycleService.isSecurityIndexExisting() == false) { + // TODO remove this short circuiting and fix tests that fail without this! + listener.onResponse(null); + } else { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, INDEX_TYPE, + getIdForUser(RESERVED_USER_TYPE, username)).request(), + new ActionListener() { + @Override + public void onResponse(GetResponse getResponse) { + if (getResponse.isExists()) { + Map sourceMap = getResponse.getSourceAsMap(); + String password = (String) sourceMap.get(Fields.PASSWORD.getPreferredName()); + Boolean enabled = (Boolean) sourceMap.get(Fields.ENABLED.getPreferredName()); + if (password == null) { + listener.onFailure(new IllegalStateException("password hash must not be null!")); + } else if (enabled == null) { + listener.onFailure(new IllegalStateException("enabled must not be null!")); + } else if (password.isEmpty()) { + listener.onResponse((enabled ? ReservedRealm.ENABLED_DEFAULT_USER_INFO : ReservedRealm + .DISABLED_DEFAULT_USER_INFO).deepClone()); + } else { + listener.onResponse(new ReservedUserInfo(password.toCharArray(), enabled, false)); + } + } else { + listener.onResponse(null); + } + } - @Override - public void onFailure(Exception e) { - if (e instanceof IndexNotFoundException) { - logger.trace((org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage( - "could not retrieve built in user [{}] info since security index does not exist", username), e); - listener.onResponse(null); - } else { - logger.error(new ParameterizedMessage("failed to retrieve built in user [{}] info", username), e); - listener.onFailure(null); - } - } - }, client::get); + @Override + public void onFailure(Exception e) { + if (TransportActions.isShardNotAvailableException(e)) { + logger.trace((org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage( + "could not retrieve built in user [{}] info since security index unavailable", username), + e); + } + listener.onFailure(e); + } + }, client::get)); + } } void getAllReservedUserInfo(ActionListener> listener) { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) .setQuery(QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)) .setFetchSource(true).request(), @@ -661,7 +569,7 @@ public class NativeUsersStore extends AbstractComponent { listener.onFailure(e); } } - }, client::search); + }, client::search)); } private void clearRealmCache(String username, ActionListener listener, Response response) { diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index f2375a7710e..0491f69ded2 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -167,8 +167,6 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol listener.onFailure(new IllegalStateException( "Security index is not on the current version - the native realm will not be operational until " + "the upgrade API is run on the security index")); - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("role-mappings cannot be modified until template and mappings are up to date")); } else { try { inner.accept(request, ActionListener.wrap(r -> refreshRealms(listener, r), listener::onFailure)); @@ -181,7 +179,7 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol private void innerPutMapping(PutRoleMappingRequest request, ActionListener listener) { final ExpressionRoleMapping mapping = request.getMapping(); - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { final XContentBuilder xContentBuilder; try { xContentBuilder = mapping.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true); @@ -270,11 +268,11 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol } else { logger.info("The security index is not yet available - no role mappings can be loaded"); if (logger.isDebugEnabled()) { - logger.debug("Security Index [{}] [exists: {}] [available: {}] [writable: {}]", + logger.debug("Security Index [{}] [exists: {}] [available: {}] [mapping up to date: {}]", SECURITY_INDEX_NAME, securityLifecycleService.isSecurityIndexExisting(), securityLifecycleService.isSecurityIndexAvailable(), - securityLifecycleService.isSecurityIndexWriteable() + securityLifecycleService.isSecurityIndexMappingUpToDate() ); } listener.onResponse(Collections.emptyList()); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 4b48f071cea..4af94758abc 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -31,8 +31,6 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentType; -import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.license.LicenseUtils; @@ -105,16 +103,15 @@ public class NativeRolesStore extends AbstractComponent { * Retrieve a list of roles, if rolesToGet is null or empty, fetch all roles */ public void getRoleDescriptors(String[] names, final ActionListener> listener) { - if (names != null && names.length == 1) { + if (securityLifecycleService.isSecurityIndexExisting() == false) { + // TODO remove this short circuiting and fix tests that fail without this! + listener.onResponse(Collections.emptyList()); + } else if (names != null && names.length == 1) { getRoleDescriptor(Objects.requireNonNull(names[0]), ActionListener.wrap(roleDescriptor -> listener.onResponse(roleDescriptor == null ? Collections.emptyList() : Collections.singletonList(roleDescriptor)), listener::onFailure)); - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); } else { - try { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { QueryBuilder query; if (names == null || names.length == 0) { query = QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE); @@ -134,61 +131,39 @@ public class NativeRolesStore extends AbstractComponent { ScrollHelper.fetchAllByEntity(client, request, new ContextPreservingActionListener<>(supplier, listener), (hit) -> transformRole(hit.getId(), hit.getSourceRef(), logger, licenseState)); } - } catch (Exception e) { - logger.error(new ParameterizedMessage("unable to retrieve roles {}", Arrays.toString(names)), e); - listener.onFailure(e); - } + }); } } public void deleteRole(final DeleteRoleRequest deleteRoleRequest, final ActionListener listener) { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("roles may not be deleted using a tribe node")); - return; - } else if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("role cannot be deleted as service cannot write until template and " + - "mappings are up to date")); - return; - } + } else { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, + ROLE_DOC_TYPE, getIdForUser(deleteRoleRequest.name())).request(); + request.setRefreshPolicy(deleteRoleRequest.getRefreshPolicy()); + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, + new ActionListener() { + @Override + public void onResponse(DeleteResponse deleteResponse) { + clearRoleCache(deleteRoleRequest.name(), listener, + deleteResponse.getResult() == DocWriteResponse.Result.DELETED); + } - try { - DeleteRequest request = client.prepareDelete(SecurityLifecycleService.SECURITY_INDEX_NAME, - ROLE_DOC_TYPE, getIdForUser(deleteRoleRequest.name())).request(); - request.setRefreshPolicy(deleteRoleRequest.getRefreshPolicy()); - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, - new ActionListener() { - @Override - public void onResponse(DeleteResponse deleteResponse) { - clearRoleCache(deleteRoleRequest.name(), listener, - deleteResponse.getResult() == DocWriteResponse.Result.DELETED); - } - - @Override - public void onFailure(Exception e) { - logger.error("failed to delete role from the index", e); - listener.onFailure(e); - } - }, client::delete); - } catch (IndexNotFoundException e) { - logger.trace("security index does not exist", e); - listener.onResponse(false); - } catch (Exception e) { - logger.error("unable to remove role", e); - listener.onFailure(e); + @Override + public void onFailure(Exception e) { + logger.error("failed to delete role from the index", e); + listener.onFailure(e); + } + }, client::delete); + }); } } public void putRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener listener) { if (isTribeNode) { listener.onFailure(new UnsupportedOperationException("roles may not be created or modified using a tribe node")); - } else if (securityLifecycleService.isSecurityIndexWriteable() == false) { - listener.onFailure(new IllegalStateException("role cannot be created or modified as service cannot write until template and " + - "mappings are up to date")); } else if (licenseState.isDocumentAndFieldLevelSecurityAllowed()) { innerPutRole(request, role, listener); } else if (role.isUsingDocumentOrFieldLevelSecurity()) { @@ -200,44 +175,33 @@ public class NativeRolesStore extends AbstractComponent { // pkg-private for testing void innerPutRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener listener) { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - try { - securityLifecycleService.createIndexIfNeededThenExecute(listener, () -> { - final XContentBuilder xContentBuilder; - try { - xContentBuilder = role.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true); - } catch (IOException e) { - listener.onFailure(e); - return; - } - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role.getName())) - .setSource(xContentBuilder) - .setRefreshPolicy(request.getRefreshPolicy()) - .request(), - new ActionListener() { - @Override - public void onResponse(IndexResponse indexResponse) { - final boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED; - clearRoleCache(role.getName(), listener, created); - } + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { + final XContentBuilder xContentBuilder; + try { + xContentBuilder = role.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS, true); + } catch (IOException e) { + listener.onFailure(e); + return; + } + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareIndex(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role.getName())) + .setSource(xContentBuilder) + .setRefreshPolicy(request.getRefreshPolicy()) + .request(), + new ActionListener() { + @Override + public void onResponse(IndexResponse indexResponse) { + final boolean created = indexResponse.getResult() == DocWriteResponse.Result.CREATED; + clearRoleCache(role.getName(), listener, created); + } - @Override - public void onFailure(Exception e) { - logger.error(new ParameterizedMessage("failed to put role [{}]", request.name()), e); - listener.onFailure(e); - } - }, client::index); - }); - } catch (Exception e) { - logger.error(new ParameterizedMessage("unable to put role [{}]", request.name()), e); - listener.onFailure(e); - } + @Override + public void onFailure(Exception e) { + logger.error(new ParameterizedMessage("failed to put role [{}]", request.name()), e); + listener.onFailure(e); + } + }, client::index); + }); } public void usageStats(ActionListener> listener) { @@ -248,118 +212,97 @@ public class NativeRolesStore extends AbstractComponent { usageStats.put("dls", false); listener.onResponse(usageStats); } else { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareMultiSearch() - .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .setSize(0)) - .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .must(QueryBuilders.boolQuery() - .should(existsQuery("indices.field_security.grant")) - .should(existsQuery("indices.field_security.except")) - // for backwardscompat with 2.x - .should(existsQuery("indices.fields")))) - .setSize(0) - .setTerminateAfter(1)) - .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) - .setQuery(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) - .filter(existsQuery("indices.query"))) - .setSize(0) - .setTerminateAfter(1)) - .request(), - new ActionListener() { - @Override - public void onResponse(MultiSearchResponse items) { - Item[] responses = items.getResponses(); - if (responses[0].isFailure()) { - usageStats.put("size", 0); - } else { - usageStats.put("size", responses[0].getResponse().getHits().getTotalHits()); + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, + client.prepareMultiSearch() + .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .setSize(0)) + .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .must(QueryBuilders.boolQuery() + .should(existsQuery("indices.field_security.grant")) + .should(existsQuery("indices.field_security.except")) + // for backwardscompat with 2.x + .should(existsQuery("indices.fields")))) + .setSize(0) + .setTerminateAfter(1)) + .add(client.prepareSearch(SecurityLifecycleService.SECURITY_INDEX_NAME) + .setQuery(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) + .filter(existsQuery("indices.query"))) + .setSize(0) + .setTerminateAfter(1)) + .request(), + new ActionListener() { + @Override + public void onResponse(MultiSearchResponse items) { + Item[] responses = items.getResponses(); + if (responses[0].isFailure()) { + usageStats.put("size", 0); + } else { + usageStats.put("size", responses[0].getResponse().getHits().getTotalHits()); + } + + if (responses[1].isFailure()) { + usageStats.put("fls", false); + } else { + usageStats.put("fls", responses[1].getResponse().getHits().getTotalHits() > 0L); + } + + if (responses[2].isFailure()) { + usageStats.put("dls", false); + } else { + usageStats.put("dls", responses[2].getResponse().getHits().getTotalHits() > 0L); + } + listener.onResponse(usageStats); } - if (responses[1].isFailure()) { - usageStats.put("fls", false); - } else { - usageStats.put("fls", responses[1].getResponse().getHits().getTotalHits() > 0L); + @Override + public void onFailure(Exception e) { + listener.onFailure(e); } - - if (responses[2].isFailure()) { - usageStats.put("dls", false); - } else { - usageStats.put("dls", responses[2].getResponse().getHits().getTotalHits() > 0L); - } - listener.onResponse(usageStats); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } - }, client::multiSearch); + }, client::multiSearch)); } } private void getRoleDescriptor(final String roleId, ActionListener roleActionListener) { if (securityLifecycleService.isSecurityIndexExisting() == false) { + // TODO remove this short circuiting and fix tests that fail without this! roleActionListener.onResponse(null); } else { - executeGetRoleRequest(roleId, new ActionListener() { - @Override - public void onResponse(GetResponse response) { - final RoleDescriptor descriptor = transformRole(response); - roleActionListener.onResponse(descriptor); - } + securityLifecycleService.prepareIndexIfNeededThenExecute(roleActionListener::onFailure, () -> + executeGetRoleRequest(roleId, new ActionListener() { + @Override + public void onResponse(GetResponse response) { + final RoleDescriptor descriptor = transformRole(response); + roleActionListener.onResponse(descriptor); + } - @Override - public void onFailure(Exception e) { - // if the index or the shard is not there / available we just claim the role is not there - if (TransportActions.isShardNotAvailableException(e)) { - logger.warn((org.apache.logging.log4j.util.Supplier) () -> - new ParameterizedMessage("failed to load role [{}] index not available", roleId), e); - roleActionListener.onResponse(null); - } else { - logger.error(new ParameterizedMessage("failed to load role [{}]", roleId), e); - roleActionListener.onFailure(e); - } - } - }); + @Override + public void onFailure(Exception e) { + // if the index or the shard is not there / available we just claim the role is not there + if (TransportActions.isShardNotAvailableException(e)) { + logger.warn((org.apache.logging.log4j.util.Supplier) () -> + new ParameterizedMessage("failed to load role [{}] index not available", roleId), e); + roleActionListener.onResponse(null); + } else { + logger.error(new ParameterizedMessage("failed to load role [{}]", roleId), e); + roleActionListener.onFailure(e); + } + } + })); } } private void executeGetRoleRequest(String role, ActionListener listener) { - if (securityLifecycleService.isSecurityIndexOutOfDate()) { - listener.onFailure(new IllegalStateException( - "Security index is not on the current version - the native realm will not be operational until " + - "the upgrade API is run on the security index")); - return; - } - - try { + securityLifecycleService.prepareIndexIfNeededThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareGet(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, getIdForUser(role)).request(), listener, - client::get); - } catch (IndexNotFoundException e) { - logger.trace( - (org.apache.logging.log4j.util.Supplier) () -> new ParameterizedMessage( - "unable to retrieve role [{}] since security index does not exist", role), e); - listener.onResponse(new GetResponse( - new GetResult(SecurityLifecycleService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, - getIdForUser(role), -1, false, null, null))); - } catch (Exception e) { - logger.error("unable to retrieve role", e); - listener.onFailure(e); - } + client::get)); } private void clearRoleCache(final String role, ActionListener listener, Response response) { diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java b/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java index 1e49038839f..881edb267ed 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/support/IndexLifecycleManager.java @@ -16,6 +16,10 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest; +import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest; +import org.elasticsearch.action.support.ActiveShardCount; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; @@ -25,18 +29,22 @@ import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.routing.IndexRoutingTable; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.xpack.template.TemplateUtils; import org.elasticsearch.xpack.upgrade.IndexUpgradeCheck; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -45,6 +53,7 @@ import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETT import static org.elasticsearch.xpack.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME; /** * Manages the lifecycle of a single index, its template, mapping and and data upgrades/migrations. @@ -58,7 +67,6 @@ public class IndexLifecycleManager extends AbstractComponent { Pattern.quote("${security.template.version}"); private final String indexName; - private final String templateName; private final Client client; private final List> indexHealthChangeListeners = new CopyOnWriteArrayList<>(); @@ -66,11 +74,10 @@ public class IndexLifecycleManager extends AbstractComponent { private volatile State indexState = new State(false, false, false, false, null); - public IndexLifecycleManager(Settings settings, Client client, String indexName, String templateName) { + public IndexLifecycleManager(Settings settings, Client client, String indexName) { super(settings); this.client = client; this.indexName = indexName; - this.templateName = templateName; } public boolean checkMappingVersion(Predicate requiredVersion) { @@ -95,8 +102,8 @@ public class IndexLifecycleManager extends AbstractComponent { return this.indexState.indexAvailable; } - public boolean isWritable() { - return this.indexState.canWriteToIndex; + public boolean isMappingUpToDate() { + return this.indexState.mappingUpToDate; } /** @@ -133,12 +140,9 @@ public class IndexLifecycleManager extends AbstractComponent { final boolean isIndexUpToDate = indexExists == false || INDEX_FORMAT_SETTING.get(securityIndex.getSettings()).intValue() == INTERNAL_INDEX_FORMAT; final boolean indexAvailable = checkIndexAvailable(clusterState); - final boolean templateIsUpToDate = TemplateUtils.checkTemplateExistsAndIsUpToDate(templateName, - SECURITY_VERSION_STRING, clusterState, logger); - final boolean mappingIsUpToDate = checkIndexMappingUpToDate(clusterState); - final boolean canWriteToIndex = templateIsUpToDate && (mappingIsUpToDate || isIndexUpToDate); + final boolean mappingIsUpToDate = indexExists == false || checkIndexMappingUpToDate(clusterState); final Version mappingVersion = oldestIndexMappingVersion(clusterState); - this.indexState = new State(indexExists, isIndexUpToDate, indexAvailable, canWriteToIndex, mappingVersion); + this.indexState = new State(indexExists, isIndexUpToDate, indexAvailable, mappingIsUpToDate, mappingVersion); } private void checkIndexHealthChange(ClusterChangedEvent event) { @@ -284,15 +288,23 @@ public class IndexLifecycleManager extends AbstractComponent { } /** - * Creates the security index, if it does not already exist, then runs the given - * action on the security index. + * Prepares the index by creating it if it doesn't exist or updating the mappings if the mappings are + * out of date. After any tasks have been executed, the runnable is then executed. */ - public void createIndexIfNeededThenExecute(final ActionListener listener, final Runnable andThen) { - if (this.indexState.indexExists) { - andThen.run(); - } else { - CreateIndexRequest request = new CreateIndexRequest(INTERNAL_SECURITY_INDEX); - request.alias(new Alias(SECURITY_INDEX_NAME)); + public void prepareIndexIfNeededThenExecute(final Consumer consumer, final Runnable andThen) { + final State indexState = this.indexState; // use a local copy so all checks execute against the same state! + // TODO we should improve this so we don't fire off a bunch of requests to do the same thing (create or update mappings) + if (indexState.indexExists && indexState.isIndexUpToDate == false) { + consumer.accept(new IllegalStateException( + "Security index is not on the current version. Security features relying on the index will not be available until " + + "the upgrade API is run on the security index")); + } else if (indexState.indexExists == false) { + Tuple mappingAndSettings = loadMappingAndSettingsSourceFromTemplate(); + CreateIndexRequest request = new CreateIndexRequest(INTERNAL_SECURITY_INDEX) + .alias(new Alias(SECURITY_INDEX_NAME)) + .mapping("doc", mappingAndSettings.v1(), XContentType.JSON) + .waitForActiveShards(ActiveShardCount.ALL) + .settings(mappingAndSettings.v2()); executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, new ActionListener() { @Override @@ -300,7 +312,7 @@ public class IndexLifecycleManager extends AbstractComponent { if (createIndexResponse.isAcknowledged()) { andThen.run(); } else { - listener.onFailure(new ElasticsearchException("Failed to create security index")); + consumer.accept(new ElasticsearchException("Failed to create security index")); } } @@ -312,13 +324,33 @@ public class IndexLifecycleManager extends AbstractComponent { // node hasn't yet received the cluster state update with the index andThen.run(); } else { - listener.onFailure(e); + consumer.accept(e); } } }, client.admin().indices()::create); + } else if (indexState.mappingUpToDate == false) { + PutMappingRequest request = new PutMappingRequest(INTERNAL_SECURITY_INDEX) + .source(loadMappingAndSettingsSourceFromTemplate().v1(), XContentType.JSON) + .type("doc"); + executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, + ActionListener.wrap(putMappingResponse -> { + if (putMappingResponse.isAcknowledged()) { + andThen.run(); + } else { + consumer.accept(new IllegalStateException("put mapping request was not acknowledged")); + } + }, consumer), client.admin().indices()::putMapping); + } else { + andThen.run(); } } + private Tuple loadMappingAndSettingsSourceFromTemplate() { + final byte[] template = TemplateUtils.loadTemplate("/" + SECURITY_TEMPLATE_NAME + ".json", + Version.CURRENT.toString(), IndexLifecycleManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8); + PutIndexTemplateRequest request = new PutIndexTemplateRequest(SECURITY_TEMPLATE_NAME).source(template, XContentType.JSON); + return new Tuple<>(request.mappings().get("doc"), request.settings()); + } /** * Holder class so we can update all values at once */ @@ -326,15 +358,15 @@ public class IndexLifecycleManager extends AbstractComponent { private final boolean indexExists; private final boolean isIndexUpToDate; private final boolean indexAvailable; - private final boolean canWriteToIndex; + private final boolean mappingUpToDate; private final Version mappingVersion; private State(boolean indexExists, boolean isIndexUpToDate, boolean indexAvailable, - boolean canWriteToIndex, Version mappingVersion) { + boolean mappingUpToDate, Version mappingVersion) { this.indexExists = indexExists; this.isIndexUpToDate = isIndexUpToDate; this.indexAvailable = indexAvailable; - this.canWriteToIndex = canWriteToIndex; + this.mappingUpToDate = mappingUpToDate; this.mappingVersion = mappingVersion; } } diff --git a/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java b/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java index 0e89ed7a575..d4987db28f7 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicensingTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.security.client.SecurityClient; +import org.junit.After; import org.junit.Before; import java.util.ArrayList; @@ -119,6 +120,11 @@ public class LicensingTests extends SecurityIntegTestCase { enableLicensing(); } + @After + public void cleanupSecurityIndex() { + deleteSecurityIndex(); + } + public void testEnableDisableBehaviour() throws Exception { IndexResponse indexResponse = index("test", "type", jsonBuilder() .startObject() diff --git a/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index 678d7b9a1d6..c6432fe6932 100644 --- a/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/plugin/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -58,8 +58,7 @@ import static org.elasticsearch.test.SecuritySettingsSource.TEST_PASSWORD_SECURE import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; -import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateSufficientToRead; -import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateUpToDate; +import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingSufficientToRead; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsCollectionContaining.hasItem; @@ -450,33 +449,9 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { ClusterState clusterState = client.admin().cluster().prepareState().setLocal(true).get().getState(); assertFalse(clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)); XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().startObject(); - assertTrue("security index mapping and template not sufficient to read:\n" + + assertTrue("security index mapping not sufficient to read:\n" + clusterState.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject().string(), - securityIndexMappingAndTemplateSufficientToRead(clusterState, logger)); - Index securityIndex = resolveSecurityIndex(clusterState.metaData()); - if (securityIndex != null) { - IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(securityIndex); - if (indexRoutingTable != null) { - assertTrue(indexRoutingTable.allPrimaryShardsActive()); - } - } - }, 30L, TimeUnit.SECONDS); - } - } - - public void assertSecurityIndexWriteable() throws Exception { - assertSecurityIndexWriteable(cluster()); - } - - public void assertSecurityIndexWriteable(TestCluster testCluster) throws Exception { - for (Client client : testCluster.getClients()) { - assertBusy(() -> { - ClusterState clusterState = client.admin().cluster().prepareState().setLocal(true).get().getState(); - assertFalse(clusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)); - XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().startObject(); - assertTrue("security index mapping and template not up to date:\n" + - clusterState.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject().string(), - securityIndexMappingAndTemplateUpToDate(clusterState, logger)); + securityIndexMappingSufficientToRead(clusterState, logger)); Index securityIndex = resolveSecurityIndex(clusterState.metaData()); if (securityIndex != null) { IndexRoutingTable indexRoutingTable = clusterState.routingTable().index(securityIndex); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityClusterClientYamlTestCase.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityClusterClientYamlTestCase.java deleted file mode 100644 index fb6b6b90e1f..00000000000 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityClusterClientYamlTestCase.java +++ /dev/null @@ -1,84 +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; - -import org.apache.http.HttpEntity; -import org.apache.http.util.EntityUtils; -import org.elasticsearch.Version; -import org.elasticsearch.client.Response; -import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; -import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; -import org.elasticsearch.test.rest.yaml.ObjectPath; -import org.junit.Before; - -import java.nio.charset.StandardCharsets; -import java.util.Map; - -import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; - -/** - * A base {@link ESClientYamlSuiteTestCase} test class for the security module, - * which depends on security template and mappings being up to date before any writes - * to the {@code .security} index can take place. - */ -public abstract class SecurityClusterClientYamlTestCase extends ESClientYamlSuiteTestCase { - - public SecurityClusterClientYamlTestCase(ClientYamlTestCandidate testCandidate) { - super(testCandidate); - } - - @Before - public void waitForSecuritySetup() throws Exception { - waitForSecurity(); - } - - public static void waitForSecurity() throws Exception { - String masterNode = null; - HttpEntity entity = client().performRequest("GET", "/_cat/nodes?h=id,master").getEntity(); - String catNodesResponse = EntityUtils.toString(entity, StandardCharsets.UTF_8); - for (String line : catNodesResponse.split("\n")) { - int indexOfStar = line.indexOf('*'); // * in the node's output denotes it is master - if (indexOfStar != -1) { - masterNode = line.substring(0, indexOfStar).trim(); - break; - } - } - assertNotNull(masterNode); - final String masterNodeId = masterNode; - - assertBusy(() -> { - try { - Response nodesResponse = client().performRequest("GET", "/_nodes"); - ObjectPath nodesPath = ObjectPath.createFromResponse(nodesResponse); - Map nodes = nodesPath.evaluate("nodes"); - Version masterVersion = null; - for (String nodeId : nodes.keySet()) { - // get the ES version number master is on - if (nodeId.startsWith(masterNodeId)) { - masterVersion = Version.fromString(nodesPath.evaluate("nodes." + nodeId + ".version")); - break; - } - } - assertNotNull(masterVersion); - - Response response = client().performRequest("GET", "/_cluster/state/metadata"); - ObjectPath objectPath = ObjectPath.createFromResponse(response); - String mappingsPath = "metadata.templates." + SECURITY_TEMPLATE_NAME + ".mappings"; - Map mappings = objectPath.evaluate(mappingsPath); - assertNotNull(mappings); - assertThat(mappings.size(), greaterThanOrEqualTo(1)); - for (String key : mappings.keySet()) { - String templatePath = mappingsPath + "." + key + "._meta.security-version"; - Version templateVersion = Version.fromString(objectPath.evaluate(templatePath)); - assertEquals(masterVersion, templateVersion); - } - } catch (Exception e) { - throw new AssertionError("failed to get cluster state", e); - } - }); - } -} diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java index b9e92457163..b64f40f673d 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityLifecycleServiceTests.java @@ -44,7 +44,7 @@ import org.junit.Before; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME; import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME; -import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingAndTemplateUpToDate; +import static org.elasticsearch.xpack.security.SecurityLifecycleService.securityIndexMappingUpToDate; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -135,7 +135,7 @@ public class SecurityLifecycleServiceTests extends ESTestCase { ClusterState.Builder clusterStateBuilder = createClusterStateWithMappingAndTemplate(templateString); final ClusterState clusterState = clusterStateBuilder.build(); IllegalStateException exception = expectThrows(IllegalStateException.class, - () -> securityIndexMappingAndTemplateUpToDate(clusterState, logger)); + () -> securityIndexMappingUpToDate(clusterState, logger)); assertEquals("Cannot read security-version string in index " + SECURITY_INDEX_NAME, exception.getMessage()); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java index 923b4954100..2c82dfe1d97 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/SecuritySettingsTests.java @@ -141,34 +141,13 @@ public class SecuritySettingsTests extends ESTestCase { public void testValidAutoCreateIndex() { Security.validateAutoCreateIndex(Settings.EMPTY); Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", true).build()); - - try { - Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", false).build()); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString(SecurityLifecycleService.SECURITY_INDEX_NAME)); - assertThat(e.getMessage(), not(containsString(IndexAuditTrail.INDEX_NAME_PREFIX))); - } - + Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", false).build()); Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", ".security,.security-6").build()); Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", ".security*").build()); Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", "*s*").build()); Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", ".s*").build()); - - try { - Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", "foo").build()); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString(SecurityLifecycleService.SECURITY_INDEX_NAME)); - assertThat(e.getMessage(), not(containsString(IndexAuditTrail.INDEX_NAME_PREFIX))); - } - - try { - Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", ".security_audit_log*").build()); - fail("IllegalArgumentException expected"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString(SecurityLifecycleService.SECURITY_INDEX_NAME)); - } + Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", "foo").build()); + Security.validateAutoCreateIndex(Settings.builder().put("action.auto_create_index", ".security_audit_log*").build()); Security.validateAutoCreateIndex(Settings.builder() .put("action.auto_create_index", ".security,.security-6") @@ -183,7 +162,6 @@ public class SecuritySettingsTests extends ESTestCase { .build()); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString(SecurityLifecycleService.SECURITY_INDEX_NAME)); assertThat(e.getMessage(), containsString(IndexAuditTrail.INDEX_NAME_PREFIX)); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 0e3494206a9..d82e74b47ae 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -62,6 +62,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.elasticsearch.xpack.security.support.Exceptions.authenticationError; @@ -865,7 +866,7 @@ public class AuthenticationServiceTests extends ESTestCase { User user = new User("_username", "r1"); final Authentication expected = new Authentication(user, new RealmRef("realm", "custom", "node"), null); String token = tokenService.getUserTokenString(tokenService.createUserToken(expected)); - when(lifecycleService.isSecurityIndexAvailable()).thenReturn(true); + when(lifecycleService.isSecurityIndexExisting()).thenReturn(true); GetRequestBuilder getRequestBuilder = mock(GetRequestBuilder.class); when(client.prepareGet(eq(SecurityLifecycleService.SECURITY_INDEX_NAME), eq("doc"), any(String.class))) .thenReturn(getRequestBuilder); @@ -877,6 +878,11 @@ public class AuthenticationServiceTests extends ESTestCase { return Void.TYPE; }).when(client).get(any(GetRequest.class), any(ActionListener.class)); + doAnswer(invocationOnMock -> { + ((Runnable) invocationOnMock.getArguments()[1]).run(); + return null; + }).when(lifecycleService).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); + try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { threadContext.putHeader("Authorization", "Bearer " + token); ElasticsearchSecurityException e = diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java index fb2963b7a7b..8d4b1647ed2 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java @@ -204,7 +204,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { @Before public void waitForSecurityIndexWritable() throws Exception { - assertSecurityIndexWriteable(); + assertSecurityIndexActive(); } @After diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java index 6ab6f22136c..39711c7d956 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authc; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.NoShardAvailableActionException; import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetRequestBuilder; @@ -18,6 +19,8 @@ import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.node.Node; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.EqualsHashCodeTestUtils; @@ -38,10 +41,12 @@ import java.security.GeneralSecurityException; import java.time.Clock; import java.util.Base64; import java.util.Collections; +import java.util.function.Consumer; import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes; import static org.hamcrest.Matchers.containsString; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -60,19 +65,22 @@ public class TokenServiceTests extends ESTestCase { .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build(); @Before - public void setupClient() throws GeneralSecurityException { + public void setupClient() { client = mock(Client.class); when(client.threadPool()).thenReturn(threadPool); when(client.settings()).thenReturn(settings); lifecycleService = mock(SecurityLifecycleService.class); - when(lifecycleService.isSecurityIndexWriteable()).thenReturn(true); doAnswer(invocationOnMock -> { ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2]; GetResponse response = mock(GetResponse.class); when(response.isExists()).thenReturn(false); listener.onResponse(response); return Void.TYPE; - }).when(client).execute(eq(GetAction.INSTANCE), any(GetRequest.class), any(ActionListener.class)); + }).when(client).get(any(GetRequest.class), any(ActionListener.class)); + doAnswer(invocationOnMock -> { + ((Runnable) invocationOnMock.getArguments()[1]).run(); + return null; + }).when(lifecycleService).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); when(client.threadPool()).thenReturn(threadPool); this.clusterService = new ClusterService(settings, new ClusterSettings(settings, ClusterSettings .BUILT_IN_CLUSTER_SETTINGS), threadPool, Collections.emptyMap()); @@ -286,7 +294,7 @@ public class TokenServiceTests extends ESTestCase { } public void testInvalidatedToken() throws Exception { - when(lifecycleService.isSecurityIndexAvailable()).thenReturn(true); + when(lifecycleService.isSecurityIndexExisting()).thenReturn(true); TokenService tokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, lifecycleService, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); @@ -438,6 +446,13 @@ public class TokenServiceTests extends ESTestCase { ThreadContext requestContext = new ThreadContext(Settings.EMPTY); requestContext.putHeader("Authorization", "Bearer " + tokenService.getUserTokenString(token)); + doAnswer(invocationOnMock -> { + ActionListener listener = (ActionListener) invocationOnMock.getArguments()[1]; + listener.onFailure(new NoShardAvailableActionException(new ShardId(new Index("foo", "uuid"), 0), "shard oh shard")); + return Void.TYPE; + }).when(client).get(any(GetRequest.class), any(ActionListener.class)); + when(client.prepareGet(anyString(), anyString(), anyString())).thenReturn(new GetRequestBuilder(client, GetAction.INSTANCE)); + try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); tokenService.getAndValidateToken(requestContext, future); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java index 9dda5c0a1fa..77cc1c483e8 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import org.elasticsearch.action.Action; import org.elasticsearch.action.ActionListener; @@ -231,20 +232,19 @@ public class NativeUsersStoreTests extends ESTestCase { actionRespond(GetRequest.class, new GetResponse(getResult)); } + private NativeUsersStore startNativeUsersStore() { SecurityLifecycleService securityLifecycleService = mock(SecurityLifecycleService.class); when(securityLifecycleService.isSecurityIndexAvailable()).thenReturn(true); when(securityLifecycleService.isSecurityIndexExisting()).thenReturn(true); - when(securityLifecycleService.isSecurityIndexWriteable()).thenReturn(true); + when(securityLifecycleService.isSecurityIndexMappingUpToDate()).thenReturn(true); when(securityLifecycleService.isSecurityIndexOutOfDate()).thenReturn(false); when(securityLifecycleService.isSecurityIndexUpToDate()).thenReturn(true); doAnswer((i) -> { Runnable action = (Runnable) i.getArguments()[1]; action.run(); - ActionListener listener = (ActionListener) i.getArguments()[0]; - listener.onResponse(null); return null; - }).when(securityLifecycleService).createIndexIfNeededThenExecute(any(ActionListener.class), any(Runnable.class)); + }).when(securityLifecycleService).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); return new NativeUsersStore(Settings.EMPTY, client, securityLifecycleService); } diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java index 0d09d2a71e2..8f875405e11 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/SecurityScrollTests.java @@ -28,7 +28,7 @@ import static org.hamcrest.Matchers.instanceOf; public class SecurityScrollTests extends SecurityIntegTestCase { public void testScrollIsPerUser() throws Exception { - assertSecurityIndexWriteable(); + assertSecurityIndexActive(); securityClient().preparePutRole("scrollable") .addIndices(new String[] { randomAlphaOfLengthBetween(4, 12) }, new String[] { "read" }, null, null, null) .get(); diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerIntegTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerIntegTests.java index fe8d45a8b63..c117a1f91c5 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerIntegTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerIntegTests.java @@ -24,7 +24,7 @@ import java.util.concurrent.atomic.AtomicInteger; public class IndexLifecycleManagerIntegTests extends SecurityIntegTestCase { public void testConcurrentOperationsTryingToCreateSecurityIndexAndAlias() throws Exception { - assertSecurityIndexWriteable(); + assertSecurityIndexActive(); final int processors = Runtime.getRuntime().availableProcessors(); final int numThreads = scaledRandomIntBetween((processors + 1) / 2, 4 * processors); final int maxNumRequests = 100 / numThreads; // bound to a maximum of 100 requests diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java index d75d1a06393..03207d2fded 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/support/IndexLifecycleManagerTests.java @@ -86,7 +86,7 @@ public class IndexLifecycleManagerTests extends ESTestCase { actions.put(action, map); } }; - manager = new IndexLifecycleManager(Settings.EMPTY, client, INDEX_NAME, TEMPLATE_NAME); + manager = new IndexLifecycleManager(Settings.EMPTY, client, INDEX_NAME); } public void testIndexWithUpToDateMappingAndTemplate() throws IOException { @@ -98,7 +98,7 @@ public class IndexLifecycleManagerTests extends ESTestCase { assertThat(manager.indexExists(), Matchers.equalTo(true)); assertThat(manager.isAvailable(), Matchers.equalTo(true)); - assertThat(manager.isWritable(), Matchers.equalTo(true)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); } public void testIndexWithoutPrimaryShards() throws IOException { @@ -245,13 +245,13 @@ public class IndexLifecycleManagerTests extends ESTestCase { private void assertInitialState() { assertThat(manager.indexExists(), Matchers.equalTo(false)); assertThat(manager.isAvailable(), Matchers.equalTo(false)); - assertThat(manager.isWritable(), Matchers.equalTo(false)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(false)); } private void assertIndexUpToDateButNotAvailable() { assertThat(manager.indexExists(), Matchers.equalTo(true)); assertThat(manager.isAvailable(), Matchers.equalTo(false)); - assertThat(manager.isWritable(), Matchers.equalTo(true)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); } public static ClusterState.Builder createClusterState(String indexName, String templateName) throws IOException { diff --git a/plugin/src/test/java/org/elasticsearch/xpack/test/rest/XPackRestIT.java b/plugin/src/test/java/org/elasticsearch/xpack/test/rest/XPackRestIT.java index e1ffd9074ef..b30307bed0e 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/test/rest/XPackRestIT.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/test/rest/XPackRestIT.java @@ -21,7 +21,6 @@ import org.elasticsearch.xpack.ml.MlMetaIndex; import org.elasticsearch.xpack.ml.integration.MlRestTestStateCleaner; import org.elasticsearch.xpack.ml.job.persistence.AnomalyDetectorsIndex; import org.elasticsearch.xpack.ml.notifications.Auditor; -import org.elasticsearch.xpack.security.SecurityLifecycleService; import org.elasticsearch.xpack.watcher.support.WatcherIndexTemplateRegistry; import org.junit.After; import org.junit.Before; @@ -81,7 +80,6 @@ public class XPackRestIT extends ESClientYamlSuiteTestCase { private void waitForTemplates() throws Exception { if (installTemplates()) { List templates = new ArrayList<>(); - templates.add(SecurityLifecycleService.SECURITY_TEMPLATE_NAME); templates.addAll(Arrays.asList(Auditor.NOTIFICATIONS_INDEX, MlMetaIndex.INDEX_NAME, AnomalyDetectorsIndex.jobStateIndexName(), AnomalyDetectorsIndex.jobResultsIndexPrefix())); templates.addAll(Arrays.asList(WatcherIndexTemplateRegistry.TEMPLATE_NAMES)); diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java index 8161ee1326d..ca2383a8008 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/xpack/restart/FullClusterRestartIT.java @@ -21,7 +21,6 @@ import org.elasticsearch.test.StreamsUtils; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.xpack.watcher.common.text.TextTemplate; import org.elasticsearch.xpack.monitoring.exporter.MonitoringTemplateUtils; -import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase; import org.elasticsearch.xpack.security.support.IndexLifecycleManager; import org.elasticsearch.xpack.test.rest.XPackRestTestHelper; import org.elasticsearch.xpack.watcher.actions.logging.LoggingAction; @@ -61,11 +60,6 @@ public class FullClusterRestartIT extends ESRestTestCase { private final boolean runningAgainstOldCluster = Booleans.parseBoolean(System.getProperty("tests.is_old_cluster")); private final Version oldClusterVersion = Version.fromString(System.getProperty("tests.old_cluster_version")); - @Before - public void waitForSecuritySetup() throws Exception { - SecurityClusterClientYamlTestCase.waitForSecurity(); - } - @Before public void waitForMlTemplates() throws Exception { XPackRestTestHelper.waitForMlTemplates(client()); diff --git a/qa/multi-cluster-search-security/src/test/java/org/elasticsearch/xpack/security/MultiClusterSearchWithSecurityYamlTestSuiteIT.java b/qa/multi-cluster-search-security/src/test/java/org/elasticsearch/xpack/security/MultiClusterSearchWithSecurityYamlTestSuiteIT.java index 9dd86392089..78e7b26f5ba 100644 --- a/qa/multi-cluster-search-security/src/test/java/org/elasticsearch/xpack/security/MultiClusterSearchWithSecurityYamlTestSuiteIT.java +++ b/qa/multi-cluster-search-security/src/test/java/org/elasticsearch/xpack/security/MultiClusterSearchWithSecurityYamlTestSuiteIT.java @@ -16,7 +16,7 @@ import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class MultiClusterSearchWithSecurityYamlTestSuiteIT extends SecurityClusterClientYamlTestCase { +public class MultiClusterSearchWithSecurityYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String USER = "test_user"; private static final String PASS = "x-pack-test-password"; diff --git a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java index 8ff97d04daa..168cda6eeda 100644 --- a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java +++ b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/UpgradeClusterClientYamlTestSuiteIT.java @@ -14,7 +14,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.test.rest.ESRestTestCase; import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import org.elasticsearch.test.rest.yaml.ClientYamlTestResponse; -import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; import org.elasticsearch.xpack.test.rest.XPackRestTestHelper; import org.junit.Before; @@ -29,7 +29,7 @@ import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.is; @TimeoutSuite(millis = 5 * TimeUnits.MINUTE) // to account for slow as hell VMs -public class UpgradeClusterClientYamlTestSuiteIT extends SecurityClusterClientYamlTestCase { +public class UpgradeClusterClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { /** * Waits for the Machine Learning templates to be created by {@link org.elasticsearch.plugins.MetaDataUpgrader} diff --git a/qa/security-setup-password-tests/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolIT.java b/qa/security-setup-password-tests/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolIT.java index 713cae6da78..e7592fb90fc 100644 --- a/qa/security-setup-password-tests/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolIT.java +++ b/qa/security-setup-password-tests/src/test/java/org/elasticsearch/xpack/security/authc/esnative/tool/SetupPasswordToolIT.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.authc.esnative.tool; -import org.apache.http.HttpHost; import org.apache.http.message.BasicHeader; import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.client.Response; @@ -18,7 +17,6 @@ import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.test.rest.ESRestTestCase; -import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase; import java.io.IOException; import java.io.UncheckedIOException; @@ -49,8 +47,6 @@ public class SetupPasswordToolIT extends ESRestTestCase { @SuppressWarnings("unchecked") public void testSetupPasswordToolAutoSetup() throws Exception { - SecurityClusterClientYamlTestCase.waitForSecurity(); - final String testConfigDir = System.getProperty("tests.config.dir"); logger.info("--> CONF: {}", testConfigDir); final Path configPath = PathUtils.get(testConfigDir); diff --git a/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java b/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java index a2e4ba6ec89..12287b739d2 100644 --- a/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java +++ b/qa/smoke-test-security-with-mustache/src/test/java/org/elasticsearch/smoketest/SmokeTestSecurityWithMustacheClientYamlTestSuiteIT.java @@ -13,11 +13,10 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; -import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase; import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -public class SmokeTestSecurityWithMustacheClientYamlTestSuiteIT extends SecurityClusterClientYamlTestCase { +public class SmokeTestSecurityWithMustacheClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { private static final String BASIC_AUTH_VALUE = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray())); diff --git a/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java b/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java index 2a0ce60d95b..a76d2683dc8 100644 --- a/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java +++ b/qa/tribe-tests-with-security/src/test/java/org/elasticsearch/test/TribeWithSecurityIT.java @@ -113,7 +113,7 @@ public class TribeWithSecurityIT extends SecurityIntegTestCase { } public void testThatTribeCanAuthenticateElasticUserWithChangedPassword() throws Exception { - assertSecurityIndexWriteable(); + assertSecurityIndexActive(); securityClient(client()).prepareChangePassword("elastic", "password".toCharArray()).get(); assertTribeNodeHasAllIndices(); @@ -124,8 +124,8 @@ public class TribeWithSecurityIT extends SecurityIntegTestCase { } public void testThatTribeClustersHaveDifferentPasswords() throws Exception { - assertSecurityIndexWriteable(); - assertSecurityIndexWriteable(cluster2); + assertSecurityIndexActive(); + assertSecurityIndexActive(cluster2); securityClient().prepareChangePassword("elastic", "password".toCharArray()).get(); securityClient(cluster2.client()).prepareChangePassword("elastic", "password2".toCharArray()).get(); @@ -155,7 +155,7 @@ public class TribeWithSecurityIT extends SecurityIntegTestCase { final int randomRoles = scaledRandomIntBetween(3, 8); List shouldBeSuccessfulRoles = new ArrayList<>(); - assertSecurityIndexWriteable(); + assertSecurityIndexActive(); for (int i = 0; i < randomRoles; i++) { final String rolename = "preferredClusterRole" + i; PutRoleResponse response = securityClient(client()).preparePutRole(rolename).cluster("monitor").get();