From 4df081532e423952ef7e1efd99a065c58e7717f9 Mon Sep 17 00:00:00 2001 From: Jake Landis Date: Fri, 29 Sep 2023 13:50:21 -0500 Subject: [PATCH] split isAvailable check --- .../xpack/security/authc/ApiKeyService.java | 22 +-- .../security/authc/ExpiredTokenRemover.java | 5 +- .../xpack/security/authc/TokenService.java | 28 ++-- .../authc/esnative/NativeUsersStore.java | 36 ++--- .../IndexServiceAccountTokenStore.java | 14 +- .../mapper/NativeRoleMappingStore.java | 30 ++-- .../authz/store/NativePrivilegeStore.java | 14 +- .../authz/store/NativeRolesStore.java | 22 +-- .../security/profile/ProfileService.java | 23 ++-- .../support/SecurityIndexManager.java | 130 +++++++++++++----- ...ansportOpenIdConnectLogoutActionTests.java | 5 +- ...sportSamlInvalidateSessionActionTests.java | 5 +- .../saml/TransportSamlLogoutActionTests.java | 5 +- .../TransportInvalidateTokenActionTests.java | 14 +- .../user/TransportGetUsersActionTests.java | 12 +- .../security/authc/ApiKeyServiceTests.java | 2 +- .../authc/AuthenticationServiceTests.java | 11 +- .../security/authc/TokenServiceTests.java | 16 ++- .../authc/esnative/NativeRealmTests.java | 1 + .../authc/esnative/NativeUsersStoreTests.java | 5 +- .../authc/ldap/ActiveDirectoryRealmTests.java | 5 +- .../security/authc/ldap/LdapRealmTests.java | 5 +- .../IndexServiceAccountTokenStoreTests.java | 15 +- .../mapper/NativeRoleMappingStoreTests.java | 6 +- .../authz/store/CompositeRolesStoreTests.java | 1 + .../store/NativePrivilegeStoreTests.java | 6 +- .../security/profile/ProfileServiceTests.java | 4 +- .../CacheInvalidatorRegistryTests.java | 1 + .../support/SecurityIndexManagerTests.java | 105 +++++++++++++- .../xpack/security/test/SecurityMocks.java | 5 +- 30 files changed, 383 insertions(+), 170 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 3f23e62bad729..ce622ddf6fa69 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -149,6 +149,8 @@ import static org.elasticsearch.xpack.core.security.action.apikey.CrossClusterApiKeyRoleDescriptorBuilder.CCS_CLUSTER_PRIVILEGE_NAMES; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.WORKFLOWS_RESTRICTION_VERSION; import static org.elasticsearch.xpack.security.Security.SECURITY_CRYPTO_THREAD_POOL_NAME; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; public class ApiKeyService { @@ -1309,12 +1311,12 @@ public void crossClusterApiKeyUsageStats(ActionListener> lis listener.onResponse(Map.of()); return; } - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { logger.debug("security index does not exist"); listener.onResponse(Map.of("total", 0, "ccs", 0, "ccr", 0, "ccs_ccr", 0)); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .filter(QueryBuilders.termQuery("doc_type", "api_key")) @@ -1638,11 +1640,11 @@ private void findApiKeysForUserRealmApiKeyIdAndNameCombination( Function hitParser, ActionListener> listener ) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("doc_type", "api_key")); QueryBuilder realmsQuery = filterForRealmNames(realmNames); @@ -1879,7 +1881,7 @@ long lastTimeWhenApiKeysRemoverWasTriggered() { } private void maybeStartApiKeyRemover() { - if (securityIndex.isAvailable()) { + if (securityIndex.isAvailable(PRIMARY_SHARDS)) { if (client.threadPool().relativeTimeInMillis() - lastExpirationRunMs > deleteInterval.getMillis()) { inactiveApiKeysRemover.submit(client.threadPool()); lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); @@ -1935,12 +1937,12 @@ public void getApiKeys( public void queryApiKeys(SearchRequest searchRequest, boolean withLimitedBy, ActionListener listener) { ensureEnabled(); - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { logger.debug("security index does not exist"); listener.onResponse(QueryApiKeyResponse.emptyResponse()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute( listener::onFailure, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java index 060f53ee4d8ad..bec9e90c9f190 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredTokenRemover.java @@ -34,6 +34,7 @@ import static org.elasticsearch.core.Strings.format; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; /** * Responsible for cleaning the invalidated and expired tokens from the security indices (`main` and `tokens`). @@ -68,10 +69,10 @@ final class ExpiredTokenRemover extends AbstractRunnable { @Override public void doRun() { final List indicesWithTokens = new ArrayList<>(); - if (securityTokensIndex.isAvailable()) { + if (securityTokensIndex.isAvailable(PRIMARY_SHARDS)) { indicesWithTokens.add(securityTokensIndex.aliasName()); } - if (securityMainIndex.isAvailable() && checkMainIndexForExpiredTokens) { + if (securityMainIndex.isAvailable(PRIMARY_SHARDS) && checkMainIndexForExpiredTokens) { indicesWithTokens.add(securityMainIndex.aliasName()); } if (indicesWithTokens.isEmpty()) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java index e543248b8ad1d..0b05715940e6c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/TokenService.java @@ -147,6 +147,8 @@ import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; /** * Service responsible for the creation, validation, and other management of {@link UserToken} @@ -552,10 +554,10 @@ private void getTokenDocById( ActionListener listener ) { final SecurityIndexManager tokensIndex = getTokensIndexForVersion(tokenVersion); - final SecurityIndexManager frozenTokensIndex = tokensIndex.freeze(); - if (frozenTokensIndex.isAvailable() == false) { + final SecurityIndexManager frozenTokensIndex = tokensIndex.defensiveCopy(); + if (frozenTokensIndex.isAvailable(PRIMARY_SHARDS) == false) { logger.warn("failed to get access token [{}] because index [{}] is not available", tokenId, tokensIndex.aliasName()); - listener.onFailure(frozenTokensIndex.getUnavailableReason()); + listener.onFailure(frozenTokensIndex.getUnavailableReason(PRIMARY_SHARDS)); return; } final GetRequest getRequest = client.prepareGet(tokensIndex.aliasName(), getTokenDocumentId(tokenId)).request(); @@ -1168,13 +1170,13 @@ private void findTokenFromRefreshToken( onFailure.accept(ex); } }; - final SecurityIndexManager frozenTokensIndex = tokensIndexManager.freeze(); + final SecurityIndexManager frozenTokensIndex = tokensIndexManager.defensiveCopy(); if (frozenTokensIndex.indexExists() == false) { logger.warn("index [{}] does not exist so we can't find token from refresh token", frozenTokensIndex.aliasName()); - listener.onFailure(frozenTokensIndex.getUnavailableReason()); - } else if (frozenTokensIndex.isAvailable() == false) { + listener.onFailure(new IndexNotFoundException(frozenTokensIndex.aliasName())); + } else if (frozenTokensIndex.isAvailable(SEARCH_SHARDS) == false) { logger.debug("index [{}] is not available to find token from refresh token, retrying", frozenTokensIndex.aliasName()); - maybeRetryOnFailure.accept(frozenTokensIndex.getUnavailableReason()); + maybeRetryOnFailure.accept(frozenTokensIndex.getUnavailableReason(SEARCH_SHARDS)); } else { final SearchRequest request = client.prepareSearch(tokensIndexManager.aliasName()) .setQuery( @@ -1786,11 +1788,11 @@ private void searchActiveTokens( */ private void sourceIndicesWithTokensAndRun(ActionListener> listener) { final List indicesWithTokens = new ArrayList<>(2); - final SecurityIndexManager frozenTokensIndex = securityTokensIndex.freeze(); + final SecurityIndexManager frozenTokensIndex = securityTokensIndex.defensiveCopy(); if (frozenTokensIndex.indexExists()) { // an existing tokens index always contains tokens (if available and version allows) - if (false == frozenTokensIndex.isAvailable()) { - listener.onFailure(frozenTokensIndex.getUnavailableReason()); + if (false == frozenTokensIndex.isAvailable(PRIMARY_SHARDS)) { + listener.onFailure(frozenTokensIndex.getUnavailableReason(PRIMARY_SHARDS)); return; } if (false == frozenTokensIndex.isIndexUpToDate()) { @@ -1806,14 +1808,14 @@ private void sourceIndicesWithTokensAndRun(ActionListener> listener } indicesWithTokens.add(frozenTokensIndex.aliasName()); } - final SecurityIndexManager frozenMainIndex = securityMainIndex.freeze(); + final SecurityIndexManager frozenMainIndex = securityMainIndex.defensiveCopy(); if (frozenMainIndex.indexExists()) { // main security index _might_ contain tokens if the tokens index has been created recently if (false == frozenTokensIndex.indexExists() || frozenTokensIndex.getCreationTime() .isAfter(clock.instant().minus(ExpiredTokenRemover.MAXIMUM_TOKEN_LIFETIME_HOURS, ChronoUnit.HOURS))) { - if (false == frozenMainIndex.isAvailable()) { - listener.onFailure(frozenMainIndex.getUnavailableReason()); + if (false == frozenMainIndex.isAvailable(PRIMARY_SHARDS)) { + listener.onFailure(frozenMainIndex.getUnavailableReason(PRIMARY_SHARDS)); return; } if (false == frozenMainIndex.isIndexUpToDate()) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java index 76029b779d8d9..d202fbfb3400d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStore.java @@ -64,6 +64,8 @@ import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; /** @@ -118,11 +120,11 @@ public void getUsers(String[] userNames, final ActionListener> listener.onFailure(t); }; - final SecurityIndexManager frozenSecurityIndex = this.securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = this.securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else if (userNames.length == 1) { // optimization for single user lookup final String username = userNames[0]; getUserAndPassword( @@ -160,11 +162,11 @@ public void getUsers(String[] userNames, final ActionListener> } void getUserCount(final ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = this.securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = this.securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(0L); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute( listener::onFailure, @@ -187,8 +189,8 @@ void getUserCount(final ActionListener listener) { * Async method to retrieve a user and their password */ private void getUserAndPassword(final String user, final ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); - if (frozenSecurityIndex.isAvailable() == false) { + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); + if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { if (frozenSecurityIndex.indexExists() == false) { logger.trace("could not retrieve user [{}] because security index does not exist", user); } else { @@ -537,11 +539,11 @@ private void setReservedUserEnabled( } public void deleteUser(final DeleteUserRequest deleteUserRequest, final ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(false); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { DeleteRequest request = client.prepareDelete(SECURITY_MAIN_ALIAS, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())) @@ -595,11 +597,11 @@ void verifyPassword(String username, final SecureString password, ActionListener } void getReservedUserInfo(String username, ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(null); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute( listener::onFailure, @@ -648,11 +650,11 @@ public void onFailure(Exception e) { } void getAllReservedUserInfo(ActionListener> listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyMap()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute( listener::onFailure, diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStore.java index 07651e0272df4..a16eeb7cf4e0f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStore.java @@ -65,6 +65,8 @@ import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; public class IndexServiceAccountTokenStore extends CachingServiceAccountTokenStore { @@ -168,11 +170,11 @@ void createToken( } void findTokensFor(ServiceAccountId accountId, ActionListener> listener) { - final SecurityIndexManager frozenSecurityIndex = this.securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = this.securityIndex.defensiveCopy(); if (false == frozenSecurityIndex.indexExists()) { listener.onResponse(List.of()); - } else if (false == frozenSecurityIndex.isAvailable()) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (false == frozenSecurityIndex.isAvailable(SEARCH_SHARDS)) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { final Supplier contextSupplier = client.threadPool() @@ -204,11 +206,11 @@ void findTokensFor(ServiceAccountId accountId, ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = this.securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = this.securityIndex.defensiveCopy(); if (false == frozenSecurityIndex.indexExists()) { listener.onResponse(false); - } else if (false == frozenSecurityIndex.isAvailable()) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (false == frozenSecurityIndex.isAvailable(PRIMARY_SHARDS)) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { final ServiceAccountId accountId = new ServiceAccountId(request.getNamespace(), request.getServiceName()); if (false == ServiceAccountService.isServiceAccountPrincipal(accountId.asPrincipal())) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index f49558ad6875d..9f2afac9f45de 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -62,6 +62,8 @@ import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isIndexDeleted; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; @@ -245,11 +247,11 @@ public void onFailure(Exception e) { } private void innerDeleteMapping(DeleteRoleMappingRequest request, ActionListener listener) { - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(false); - } else if (securityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (securityIndex.isAvailable(PRIMARY_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin( @@ -293,19 +295,15 @@ public void getRoleMappings(Set names, ActionListener> listener) { - if (securityIndex.isAvailable()) { - loadMappings(listener); - } 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: {}]", - SECURITY_MAIN_ALIAS, - securityIndex.indexExists(), - securityIndex.isAvailable() - ); - } + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); + if (frozenSecurityIndex.indexExists() == false) { + logger.debug("The security index exists - no role mappings can be loaded"); listener.onResponse(Collections.emptyList()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + logger.debug("The security index exists but is not available - no role mappings can be loaded"); + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); + } else { + loadMappings(listener); } } @@ -319,7 +317,7 @@ private void getMappings(ActionListener> listener) { * */ public void usageStats(ActionListener> listener) { - if (securityIndex.isAvailable() == false) { + if (securityIndex.isAvailable(SEARCH_SHARDS) == false) { reportStats(listener, Collections.emptyList()); } else { getMappings(ActionListener.wrap(mappings -> reportStats(listener, mappings), listener::onFailure)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java index 10f5539b953b6..0e509c8af26b0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStore.java @@ -74,6 +74,8 @@ import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor.DOC_TYPE_VALUE; import static org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor.Fields.APPLICATION; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; /** @@ -189,11 +191,11 @@ public void getPrivileges( private void innerGetPrivileges(Collection applications, ActionListener> listener) { assert applications != null && applications.size() > 0 : "Application names are required (found " + applications + ")"; - final SecurityIndexManager frozenSecurityIndex = securityIndexManager.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndexManager.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(SEARCH_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(SEARCH_SHARDS)); } else { securityIndexManager.checkIndexVersionThenExecute(listener::onFailure, () -> { @@ -419,11 +421,11 @@ public void deletePrivileges( WriteRequest.RefreshPolicy refreshPolicy, ActionListener>> listener ) { - final SecurityIndexManager frozenSecurityIndex = securityIndexManager.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndexManager.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(Collections.emptyMap()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { securityIndexManager.checkIndexVersionThenExecute(listener::onFailure, () -> { ActionListener groupListener = new GroupedActionListener<>(names.size(), ActionListener.wrap(responses -> { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 085863fdb5e31..31875655a7ee9 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -69,6 +69,8 @@ import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.core.security.SecurityField.DOCUMENT_LEVEL_SECURITY_FEATURE; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ROLE_TYPE; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS; /** @@ -133,12 +135,12 @@ public void getRoleDescriptors(Set names, final ActionListener { QueryBuilder query = QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE); @@ -208,11 +210,11 @@ public void deleteRole(final DeleteRoleRequest deleteRoleRequest, final ActionLi return; } - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { listener.onResponse(false); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); + } else if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { + listener.onFailure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS)); } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { DeleteRequest request = client.prepareDelete(SECURITY_MAIN_ALIAS, getIdForRole(deleteRoleRequest.name())).request(); @@ -309,7 +311,7 @@ public void onFailure(Exception e) { public void usageStats(ActionListener> listener) { Map usageStats = Maps.newMapWithExpectedSize(3); - if (securityIndex.isAvailable() == false) { + if (securityIndex.isAvailable(SEARCH_SHARDS) == false) { usageStats.put("size", 0L); usageStats.put("fls", false); usageStats.put("dls", false); @@ -406,12 +408,12 @@ public String toString() { } private void getRoleDescriptor(final String roleId, ActionListener resultListener) { - final SecurityIndexManager frozenSecurityIndex = this.securityIndex.freeze(); + final SecurityIndexManager frozenSecurityIndex = this.securityIndex.defensiveCopy(); if (frozenSecurityIndex.indexExists() == false) { // TODO remove this short circuiting and fix tests that fail without this! resultListener.onResponse(RoleRetrievalResult.success(Collections.emptySet())); - } else if (frozenSecurityIndex.isAvailable() == false) { - resultListener.onResponse(RoleRetrievalResult.failure(frozenSecurityIndex.getUnavailableReason())); + } else if (frozenSecurityIndex.isAvailable(PRIMARY_SHARDS) == false) { + resultListener.onResponse(RoleRetrievalResult.failure(frozenSecurityIndex.getUnavailableReason(PRIMARY_SHARDS))); } else { securityIndex.checkIndexVersionThenExecute( e -> resultListener.onResponse(RoleRetrievalResult.failure(e)), diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java index 7be1d0f96c043..054583d94cbb1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/profile/ProfileService.java @@ -99,6 +99,8 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_PROFILE_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.core.security.authc.Authentication.isFileOrNativeRealm; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.PRIMARY_SHARDS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.Availability.SEARCH_SHARDS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_PROFILE_ALIAS; import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.VERSION_SECURITY_PROFILE_ORIGIN; @@ -261,7 +263,7 @@ public void suggestProfile(SuggestProfilesRequest request, TaskId parentTaskId, 0, new TotalHits(0, TotalHits.Relation.EQUAL_TO) ); - })).ifPresent(frozenProfileIndex -> { + }), SEARCH_SHARDS).ifPresent(frozenProfileIndex -> { final SearchRequest searchRequest = buildSearchRequestForSuggest(request, parentTaskId); frozenProfileIndex.checkIndexVersionThenExecute( @@ -334,7 +336,7 @@ public void usageStats(ActionListener> listener) { tryFreezeAndCheckIndex(listener.map(response -> { // index does not exist assert response == null : "only null response can reach here"; return Map.of("total", 0L, "enabled", 0L, "recent", 0L); - })).ifPresent(frozenProfileIndex -> { + }), SEARCH_SHARDS).ifPresent(frozenProfileIndex -> { final MultiSearchRequest multiSearchRequest = client.prepareMultiSearch() .add( client.prepareSearch(SECURITY_PROFILE_ALIAS) @@ -445,7 +447,7 @@ SearchRequest buildSearchRequestForSuggest(SuggestProfilesRequest request, TaskI } private void getVersionedDocument(String uid, ActionListener listener) { - tryFreezeAndCheckIndex(listener).ifPresent(frozenProfileIndex -> { + tryFreezeAndCheckIndex(listener, PRIMARY_SHARDS).ifPresent(frozenProfileIndex -> { final GetRequest getRequest = new GetRequest(SECURITY_PROFILE_ALIAS, uidToDocId(uid)); frozenProfileIndex.checkIndexVersionThenExecute( listener::onFailure, @@ -472,7 +474,7 @@ private void getVersionedDocuments(Collection uids, ActionListener { + tryFreezeAndCheckIndex(listener, PRIMARY_SHARDS).ifPresent(frozenProfileIndex -> { frozenProfileIndex.checkIndexVersionThenExecute( listener::onFailure, () -> new OriginSettingClient(client, getActionOrigin()).prepareMultiGet() @@ -544,7 +546,7 @@ private void searchVersionedDocumentsForSubjects( listener.onResponse(new SubjectSearchResultsAndErrors<>(List.of(), Map.of())); return; } - tryFreezeAndCheckIndex(listener).ifPresent(frozenProfileIndex -> { + tryFreezeAndCheckIndex(listener, SEARCH_SHARDS).ifPresent(frozenProfileIndex -> { frozenProfileIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { final MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); subjects.forEach(subject -> multiSearchRequest.add(buildSearchRequestForSubject(subject))); @@ -1006,14 +1008,17 @@ private static XContentBuilder wrapProfileDocumentWithoutApplicationData(Profile * Freeze the profile index check its availability and return it if everything is ok. * Otherwise it calls the listener with null and returns an empty Optional. */ - private Optional tryFreezeAndCheckIndex(ActionListener listener) { - final SecurityIndexManager frozenProfileIndex = profileIndex.freeze(); + private Optional tryFreezeAndCheckIndex( + ActionListener listener, + SecurityIndexManager.Availability availability + ) { + final SecurityIndexManager frozenProfileIndex = profileIndex.defensiveCopy(); if (false == frozenProfileIndex.indexExists()) { logger.debug("profile index does not exist"); listener.onResponse(null); return Optional.empty(); - } else if (false == frozenProfileIndex.isAvailable()) { - listener.onFailure(frozenProfileIndex.getUnavailableReason()); + } else if (false == frozenProfileIndex.isAvailable(availability)) { + listener.onFailure(frozenProfileIndex.getUnavailableReason(availability)); return Optional.empty(); } return Optional.of(frozenProfileIndex); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java index bdc25098f1760..5cce58c49f17a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/SecurityIndexManager.java @@ -34,6 +34,7 @@ import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.routing.IndexRoutingTable; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.core.Tuple; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; @@ -67,31 +68,45 @@ public class SecurityIndexManager implements ClusterStateListener { private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class); + /** + * When checking availability, check for availability of search or availability of all primaries + **/ + public enum Availability { + SEARCH_SHARDS, + PRIMARY_SHARDS + } + private final Client client; private final SystemIndexDescriptor systemIndexDescriptor; private final List> stateChangeListeners = new CopyOnWriteArrayList<>(); private volatile State state; + private final boolean defensiveCopy; public static SecurityIndexManager buildSecurityIndexManager( Client client, ClusterService clusterService, SystemIndexDescriptor descriptor ) { - final SecurityIndexManager securityIndexManager = new SecurityIndexManager(client, descriptor, State.UNRECOVERED_STATE); + final SecurityIndexManager securityIndexManager = new SecurityIndexManager(client, descriptor, State.UNRECOVERED_STATE, false); clusterService.addListener(securityIndexManager); return securityIndexManager; } - private SecurityIndexManager(Client client, SystemIndexDescriptor descriptor, State state) { + private SecurityIndexManager(Client client, SystemIndexDescriptor descriptor, State state, boolean defensiveCopy) { this.client = client; this.state = state; this.systemIndexDescriptor = descriptor; + this.defensiveCopy = defensiveCopy; } - public SecurityIndexManager freeze() { - return new SecurityIndexManager(null, systemIndexDescriptor, state); + /** + * Creates a defensive to protect against the underlying state changes. Should be called prior to making decisions and that same copy + * should be reused for multiple checks in the same workflow. + */ + public SecurityIndexManager defensiveCopy() { + return new SecurityIndexManager(null, systemIndexDescriptor, state, true); } public String aliasName() { @@ -114,8 +129,27 @@ public boolean isIndexUpToDate() { return this.state.isIndexUpToDate; } - public boolean isAvailable() { - return this.state.indexAvailable; + /** + * Optimization to avoid making unnecessary calls when we know the underlying shard state. This call will check that the index exists, + * is discoverable from the alias, is not closed, and will determine if available based on the {@link Availability} parameter. + * @param availability Check availability for search or write/update/real time get workflows. Write/update/realtime get workflows + * should check for availability of primary shards. Search workflows should check availability of search shards + * (which may or may not also be the primary shards). + * @return + * when checking for search: true if all searchable shards for the security index are available + * when checking for primary: true if all primary shards for the security index are available + */ + public boolean isAvailable(Availability availability) { + switch (availability) { + case SEARCH_SHARDS -> { + return this.state.indexAvailableForSearch; + } + case PRIMARY_SHARDS -> { + return this.state.indexAvailableForWrite; + } + } + // can never happen + throw new IllegalStateException("Unexpected availability enumeration. This is bug, please contact support."); } public boolean isMappingUpToDate() { @@ -126,19 +160,34 @@ public boolean isStateRecovered() { return this.state != State.UNRECOVERED_STATE; } - public ElasticsearchException getUnavailableReason() { - final State state = this.state; // use a local copy so all checks execute against the same state! - if (state.indexAvailable) { - throw new IllegalStateException("caller must make sure to use a frozen state and check indexAvailable"); + public ElasticsearchException getUnavailableReason(Availability availability) { + // ensure usage of a local copy so all checks execute against the same state! + if (defensiveCopy == false) { + throw new IllegalStateException("caller must make sure to use a defensive copy"); } - + final State state = this.state; if (state.indexState == IndexMetadata.State.CLOSE) { return new IndexClosedException(new Index(state.concreteIndexName, ClusterState.UNKNOWN_UUID)); } else if (state.indexExists()) { - return new UnavailableShardsException( - null, - "at least one primary shard for the index [" + state.concreteIndexName + "] is unavailable" - ); + assert state.indexAvailableForSearch == false || state.indexAvailableForWrite == false; + if (Availability.PRIMARY_SHARDS.equals(availability) && state.indexAvailableForWrite == false) { + return new UnavailableShardsException( + null, + "at least one primary shard for the index [" + state.concreteIndexName + "] is unavailable" + ); + } else if (Availability.SEARCH_SHARDS.equals(availability) && state.indexAvailableForSearch == false) { + // The current behavior is that when primaries are unavailable and replicas can not be promoted then + // any replicas will be marked as unavailable as well. This is applicable in stateless where there index only primaries + // with non-promotable replicas (i.e. search only shards). In the case "at least one search ... is unavailable" is + // a technically correct statement, but it may be unavailable because it is not promotable and the primary is unavailable + return new UnavailableShardsException( + null, + "at least one search shard for the index [" + state.concreteIndexName + "] is unavailable" + ); + } else { + // should never happen + throw new IllegalStateException("caller must ensure original availability matches the current availability"); + } } else { return new IndexNotFoundException(state.concreteIndexName); } @@ -174,7 +223,9 @@ public void clusterChanged(ClusterChangedEvent event) { final Instant creationTime = indexMetadata != null ? Instant.ofEpochMilli(indexMetadata.getCreationDate()) : null; final boolean isIndexUpToDate = indexMetadata == null || INDEX_FORMAT_SETTING.get(indexMetadata.getSettings()) == systemIndexDescriptor.getIndexFormat(); - final boolean indexAvailable = checkIndexAvailable(event.state()); + Tuple available = checkIndexAvailable(event.state()); + final boolean indexAvailableForWrite = available.v1(); + final boolean indexAvailableForSearch = available.v2(); final boolean mappingIsUpToDate = indexMetadata == null || checkIndexMappingUpToDate(event.state()); final Version mappingVersion = oldestIndexMappingVersion(event.state()); final String concreteIndexName = indexMetadata == null @@ -199,7 +250,8 @@ public void clusterChanged(ClusterChangedEvent event) { final State newState = new State( creationTime, isIndexUpToDate, - indexAvailable, + indexAvailableForSearch, + indexAvailableForWrite, mappingIsUpToDate, mappingVersion, concreteIndexName, @@ -230,24 +282,35 @@ public void onStateRecovered(Consumer recoveredStateConsumer) { stateChangeListeners.add(stateChangeListener); } - private boolean checkIndexAvailable(ClusterState state) { + private Tuple checkIndexAvailable(ClusterState state) { final String aliasName = systemIndexDescriptor.getAliasName(); IndexMetadata metadata = resolveConcreteIndex(aliasName, state.metadata()); if (metadata == null) { logger.debug("Index [{}] is not available - no metadata", aliasName); - return false; + return new Tuple<>(false, false); } if (metadata.getState() == IndexMetadata.State.CLOSE) { logger.warn("Index [{}] is closed", aliasName); - return false; + return new Tuple<>(false, false); } + boolean allPrimaryShards = false; + boolean searchShards = false; final IndexRoutingTable routingTable = state.routingTable().index(metadata.getIndex()); - if (routingTable == null || routingTable.allPrimaryShardsActive() == false) { - logger.debug("Index [{}] is not yet active", aliasName); - return false; - } else { - return true; + if (routingTable != null && routingTable.allPrimaryShardsActive()) { + allPrimaryShards = true; + } + if (routingTable != null && routingTable.readyForSearch(state)) { + searchShards = true; + } + if (allPrimaryShards == false || searchShards == false) { + logger.debug( + "Index [{}] is not fully available." + " all primary shards available [{}], search shards available, [{}]", + aliasName, + allPrimaryShards, + searchShards + ); } + return new Tuple<>(allPrimaryShards, searchShards); } private boolean checkIndexMappingUpToDate(ClusterState clusterState) { @@ -482,10 +545,11 @@ public static boolean isIndexDeleted(State previousState, State currentState) { * State of the security index. */ public static class State { - public static final State UNRECOVERED_STATE = new State(null, false, false, false, null, null, null, null, null, null); + public static final State UNRECOVERED_STATE = new State(null, false, false, false, false, null, null, null, null, null, null); public final Instant creationTime; public final boolean isIndexUpToDate; - public final boolean indexAvailable; + public final boolean indexAvailableForSearch; + public final boolean indexAvailableForWrite; public final boolean mappingUpToDate; public final Version mappingVersion; public final String concreteIndexName; @@ -497,7 +561,8 @@ public static class State { public State( Instant creationTime, boolean isIndexUpToDate, - boolean indexAvailable, + boolean indexAvailableForSearch, + boolean indexAvailableForWrite, boolean mappingUpToDate, Version mappingVersion, String concreteIndexName, @@ -508,7 +573,8 @@ public State( ) { this.creationTime = creationTime; this.isIndexUpToDate = isIndexUpToDate; - this.indexAvailable = indexAvailable; + this.indexAvailableForSearch = indexAvailableForSearch; + this.indexAvailableForWrite = indexAvailableForWrite; this.mappingUpToDate = mappingUpToDate; this.mappingVersion = mappingVersion; this.concreteIndexName = concreteIndexName; @@ -525,7 +591,8 @@ public boolean equals(Object o) { State state = (State) o; return Objects.equals(creationTime, state.creationTime) && isIndexUpToDate == state.isIndexUpToDate - && indexAvailable == state.indexAvailable + && indexAvailableForSearch == state.indexAvailableForSearch + && indexAvailableForWrite == state.indexAvailableForWrite && mappingUpToDate == state.mappingUpToDate && Objects.equals(mappingVersion, state.mappingVersion) && Objects.equals(concreteIndexName, state.concreteIndexName) @@ -543,7 +610,8 @@ public int hashCode() { return Objects.hash( creationTime, isIndexUpToDate, - indexAvailable, + indexAvailableForSearch, + indexAvailableForWrite, mappingUpToDate, mappingVersion, concreteIndexName, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java index 61aa0e22fd905..2a6fad9c81f53 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java @@ -173,8 +173,9 @@ public void setup() throws Exception { ((Runnable) inv.getArguments()[1]).run(); return null; }).when(securityIndex).checkIndexVersionThenExecute(anyConsumer(), any(Runnable.class)); - when(securityIndex.isAvailable()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java index f65dda28be125..a748de0c89413 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlInvalidateSessionActionTests.java @@ -263,12 +263,13 @@ protected void ((Runnable) inv.getArguments()[1]).run(); return null; }).when(securityIndex).checkIndexVersionThenExecute(anyConsumer(), any(Runnable.class)); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(securityIndex.indexExists()).thenReturn(true); when(securityIndex.isIndexUpToDate()).thenReturn(true); when(securityIndex.getCreationTime()).thenReturn(Clock.systemUTC().instant()); when(securityIndex.aliasName()).thenReturn(".security"); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); final MockLicenseState licenseState = mock(MockLicenseState.class); when(licenseState.isAllowed(Security.TOKEN_SERVICE_FEATURE)).thenReturn(true); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java index 9020d45041cea..e3631a785b9f3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java @@ -204,8 +204,9 @@ public void setup() throws Exception { ((Runnable) inv.getArguments()[1]).run(); return null; }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); - when(securityIndex.isAvailable()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); final MockLicenseState licenseState = mock(MockLicenseState.class); when(licenseState.isAllowed(Security.TOKEN_SERVICE_FEATURE)).thenReturn(true); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportInvalidateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportInvalidateTokenActionTests.java index a0f7892c3319d..6b9594c1c68ea 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportInvalidateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportInvalidateTokenActionTests.java @@ -78,10 +78,12 @@ public void setup() { } public void testInvalidateTokensWhenIndexUnavailable() throws Exception { - when(securityIndex.isAvailable()).thenReturn(false); + + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(false); when(securityIndex.indexExists()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); - when(securityIndex.getUnavailableReason()).thenReturn(new ElasticsearchException("simulated")); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); + when(securityIndex.getUnavailableReason(SecurityIndexManager.Availability.PRIMARY_SHARDS)) + .thenReturn(new ElasticsearchException("simulated")); final TokenService tokenService = new TokenService( SETTINGS, Clock.systemUTC(), @@ -122,10 +124,10 @@ public void testInvalidateTokensWhenIndexUnavailable() throws Exception { } public void testInvalidateTokensWhenIndexClosed() throws Exception { - when(securityIndex.isAvailable()).thenReturn(false); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); when(securityIndex.indexExists()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); - when(securityIndex.getUnavailableReason()).thenReturn( + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); + when(securityIndex.getUnavailableReason(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn( new IndexClosedException(new Index(INTERNAL_SECURITY_TOKENS_INDEX_7, ClusterState.UNKNOWN_UUID)) ); final TokenService tokenService = new TokenService( diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportGetUsersActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportGetUsersActionTests.java index 00f478f68b6ba..b6a1523b09784 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportGetUsersActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/user/TransportGetUsersActionTests.java @@ -110,7 +110,8 @@ public void terminateThreadPool() throws InterruptedException { public void testAnonymousUser() { NativeUsersStore usersStore = mock(NativeUsersStore.class); SecurityIndexManager securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); AnonymousUser anonymousUser = new AnonymousUser(settings); ReservedRealm reservedRealm = new ReservedRealm(mock(Environment.class), settings, usersStore, anonymousUser, threadPool); reservedRealm.initRealmRef( @@ -183,7 +184,8 @@ public void onFailure(Exception e) { public void testReservedUsersOnly() { NativeUsersStore usersStore = mock(NativeUsersStore.class); SecurityIndexManager securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); ReservedRealmTests.mockGetAllReservedUserInfo(usersStore, Collections.emptyMap()); ReservedRealm reservedRealm = new ReservedRealm( @@ -272,7 +274,8 @@ public void testGetAllUsers() { ); NativeUsersStore usersStore = mock(NativeUsersStore.class); SecurityIndexManager securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); ReservedRealmTests.mockGetAllReservedUserInfo(usersStore, Collections.emptyMap()); ReservedRealm reservedRealm = new ReservedRealm( mock(Environment.class), @@ -377,7 +380,8 @@ public void testGetUsersWithProfileUidException() { ); NativeUsersStore usersStore = mock(NativeUsersStore.class); SecurityIndexManager securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); ReservedRealmTests.mockGetAllReservedUserInfo(usersStore, Collections.emptyMap()); ReservedRealm reservedRealm = new ReservedRealm( mock(Environment.class), diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java index 117d1f1fe14bb..a0a1b622cf36e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java @@ -877,7 +877,7 @@ public void testCrossClusterApiKeyUsageStatsAreZerosWhenIndexDoesNotExist() { public void testCrossClusterApiKeyUsageFailsWhenIndexNotAvailable() { securityIndex = SecurityMocks.mockSecurityIndexManager(".security", true, false); final ElasticsearchException expectedException = new ElasticsearchException("not available"); - when(securityIndex.getUnavailableReason()).thenReturn(expectedException); + when(securityIndex.getUnavailableReason(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(expectedException); final ApiKeyService apiKeyService = createApiKeyService(); final PlainActionFuture> future = new PlainActionFuture<>(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index cb4bbc383764c..cf343f790d85c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -1909,8 +1909,9 @@ public void testAuthenticateWithToken() throws Exception { String token = tokenFuture.get().getAccessToken(); when(client.prepareMultiGet()).thenReturn(new MultiGetRequestBuilder(client, MultiGetAction.INSTANCE)); mockGetTokenFromAccessTokenBytes(tokenService, newTokenBytes.v1(), expected, Map.of(), false, null, client); - when(securityIndex.freeze()).thenReturn(securityIndex); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(securityIndex.indexExists()).thenReturn(true); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { threadContext.putHeader("Authorization", "Bearer " + token); @@ -2014,8 +2015,9 @@ public void testInvalidToken() throws Exception { } public void testExpiredToken() throws Exception { - when(securityIndex.freeze()).thenReturn(securityIndex); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(securityIndex.indexExists()).thenReturn(true); User user = new User("_username", "r1"); final Authentication expected = AuthenticationTestHelper.builder() @@ -2501,6 +2503,7 @@ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { true, true, true, + true, null, concreteSecurityIndexName, indexStatus, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java index 4c276993381b5..35335fd5e4a53 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenServiceTests.java @@ -922,14 +922,14 @@ public void testIndexNotAvailable() throws Exception { final SecurityIndexManager tokensIndex; if (pre72OldNode != null) { tokensIndex = securityMainIndex; - when(securityTokensIndex.isAvailable()).thenReturn(false); + when(securityTokensIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); when(securityTokensIndex.indexExists()).thenReturn(false); - when(securityTokensIndex.freeze()).thenReturn(securityTokensIndex); + when(securityTokensIndex.defensiveCopy()).thenReturn(securityTokensIndex); } else { tokensIndex = securityTokensIndex; - when(securityMainIndex.isAvailable()).thenReturn(false); + when(securityMainIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); when(securityMainIndex.indexExists()).thenReturn(false); - when(securityMainIndex.freeze()).thenReturn(securityMainIndex); + when(securityMainIndex.defensiveCopy()).thenReturn(securityMainIndex); } try (ThreadContext.StoredContext ignore = requestContext.newStoredContextPreservingResponseHeaders()) { PlainActionFuture future = new PlainActionFuture<>(); @@ -937,8 +937,10 @@ public void testIndexNotAvailable() throws Exception { tokenService.tryAuthenticateToken(bearerToken3, future); assertNull(future.get()); - when(tokensIndex.isAvailable()).thenReturn(false); - when(tokensIndex.getUnavailableReason()).thenReturn(new UnavailableShardsException(null, "unavailable")); + when(tokensIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); + when(tokensIndex.getUnavailableReason(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn( + new UnavailableShardsException(null, "unavailable") + ); when(tokensIndex.indexExists()).thenReturn(true); future = new PlainActionFuture<>(); final SecureString bearerToken2 = Authenticator.extractBearerTokenFromHeader(requestContext); @@ -951,7 +953,7 @@ public void testIndexNotAvailable() throws Exception { tokenService.tryAuthenticateToken(bearerToken1, future); assertNull(future.get()); - when(tokensIndex.isAvailable()).thenReturn(true); + when(tokensIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); when(tokensIndex.indexExists()).thenReturn(true); mockGetTokenFromAccessTokenBytes(tokenService, newTokenBytes.v1(), authentication, false, null); future = new PlainActionFuture<>(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java index 7dc5af1717fda..b9cc599609ea1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmTests.java @@ -37,6 +37,7 @@ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { true, true, true, + true, null, concreteSecurityIndexName, indexStatus, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java index 5f195477d57a0..4e364518bb7f3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeUsersStoreTests.java @@ -317,10 +317,11 @@ private void respondToGetUserRequest(String username, SecureString password, Str @SuppressWarnings("unchecked") private NativeUsersStore startNativeUsersStore() { SecurityIndexManager securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(securityIndex.indexExists()).thenReturn(true); when(securityIndex.isIndexUpToDate()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); doAnswer((i) -> { Runnable action = (Runnable) i.getArguments()[1]; action.run(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java index afc6d5c17e135..e9af65bd8fc4a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/ActiveDirectoryRealmTests.java @@ -423,8 +423,11 @@ public void testRealmWithTemplatedRoleMapping() throws Exception { ActiveDirectorySessionFactory sessionFactory = new ActiveDirectorySessionFactory(config, sslService, threadPool); SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class); - when(mockSecurityIndex.isAvailable()).thenReturn(true); + when(mockSecurityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(mockSecurityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(mockSecurityIndex.isIndexUpToDate()).thenReturn(true); + when(mockSecurityIndex.indexExists()).thenReturn(true); + when(mockSecurityIndex.defensiveCopy()).thenReturn(mockSecurityIndex); Client mockClient = mock(Client.class); when(mockClient.threadPool()).thenReturn(threadPool); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java index 34c9a7e3b0b0f..9bbf4dd312d27 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealmTests.java @@ -490,8 +490,11 @@ public void testLdapRealmWithTemplatedRoleMapping() throws Exception { RealmConfig config = getRealmConfig(REALM_IDENTIFIER, settings); SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class); - when(mockSecurityIndex.isAvailable()).thenReturn(true); + when(mockSecurityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(mockSecurityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(mockSecurityIndex.isIndexUpToDate()).thenReturn(true); + when(mockSecurityIndex.defensiveCopy()).thenReturn(mockSecurityIndex); + when(mockSecurityIndex.indexExists()).thenReturn(true); Client mockClient = mock(Client.class); when(mockClient.threadPool()).thenReturn(threadPool); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java index f536a696a8e23..2dec4eb8ea2b5 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/IndexServiceAccountTokenStoreTests.java @@ -132,10 +132,11 @@ protected void cacheInvalidatorRegistry = mock(CacheInvalidatorRegistry.class); securityIndex = mock(SecurityIndexManager.class); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); when(securityIndex.indexExists()).thenReturn(true); when(securityIndex.isIndexUpToDate()).thenReturn(true); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); doAnswer((i) -> { Runnable action = (Runnable) i.getArguments()[1]; action.run(); @@ -375,7 +376,7 @@ public void testDeleteToken() { public void testIndexStateIssues() { // Index not exists Mockito.reset(securityIndex); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); when(securityIndex.indexExists()).thenReturn(false); final ServiceAccountId accountId = new ServiceAccountId(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8)); @@ -394,11 +395,13 @@ public void testIndexStateIssues() { // Index exists but not available Mockito.reset(securityIndex); - when(securityIndex.freeze()).thenReturn(securityIndex); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); when(securityIndex.indexExists()).thenReturn(true); - when(securityIndex.isAvailable()).thenReturn(false); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(false); final ElasticsearchException e = new ElasticsearchException("fail"); - when(securityIndex.getUnavailableReason()).thenReturn(e); + when(securityIndex.getUnavailableReason(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(e); + when(securityIndex.getUnavailableReason(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(e); final PlainActionFuture> future3 = new PlainActionFuture<>(); store.findTokensFor(accountId, future3); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java index 1a0634f3234a6..16ef229ed5436 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java @@ -125,7 +125,10 @@ public void testResolveRoles() throws Exception { ScriptModule.CORE_CONTEXTS, () -> 1L ); - when(securityIndex.isAvailable()).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(true); + when(securityIndex.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(true); + when(securityIndex.indexExists()).thenReturn(true); + when(securityIndex.defensiveCopy()).thenReturn(securityIndex); final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, securityIndex, scriptService) { @Override @@ -190,6 +193,7 @@ private SecurityIndexManager.State indexState(boolean isUpToDate, ClusterHealthS isUpToDate, true, true, + true, null, concreteSecurityIndexName, healthStatus, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index f30fb242abc13..46a78f1055a6f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -1527,6 +1527,7 @@ public SecurityIndexManager.State dummyIndexState(boolean isIndexUpToDate, Clust isIndexUpToDate, true, true, + true, null, concreteSecurityIndexName, healthStatus, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java index 10ed5c66f3c15..01d3ca6db354e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativePrivilegeStoreTests.java @@ -126,9 +126,10 @@ public void searchScroll(SearchScrollRequest request, ActionListener { assertThat(invocationOnMock.getArguments().length, equalTo(2)); assertThat(invocationOnMock.getArguments()[1], instanceOf(Runnable.class)); @@ -976,6 +977,7 @@ private SecurityIndexManager.State dummyState( isIndexUpToDate, true, true, + true, null, concreteSecurityIndexName, healthStatus, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java index c9346ba488838..35efb12b278f2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/profile/ProfileServiceTests.java @@ -297,8 +297,8 @@ public void testGetProfileSubjectsNoIndex() throws Exception { assertThat(resultsAndErrors.errors().size(), is(0)); when(profileIndex.indexExists()).thenReturn(true); ElasticsearchException unavailableException = new ElasticsearchException("mock profile index unavailable"); - when(profileIndex.isAvailable()).thenReturn(false); - when(profileIndex.getUnavailableReason()).thenReturn(unavailableException); + when(profileIndex.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(false); + when(profileIndex.getUnavailableReason(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(unavailableException); PlainActionFuture>> future2 = new PlainActionFuture<>(); profileService.getProfileSubjects(randomList(1, 5, () -> randomAlphaOfLength(20)), future2); ExecutionException e = expectThrows(ExecutionException.class, () -> future2.get()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java index d8fc00a2f1560..89d667de56c37 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/CacheInvalidatorRegistryTests.java @@ -59,6 +59,7 @@ public void testSecurityIndexStateChangeWillInvalidateAllRegisteredInvalidators( true, true, true, + true, Version.CURRENT, ".security", ClusterHealthStatus.GREEN, diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerTests.java index 1e0969c96c0de..c8f86957f84a3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/support/SecurityIndexManagerTests.java @@ -123,7 +123,8 @@ public void testIndexWithUpToDateMappingAndTemplate() { manager.clusterChanged(event(markShardsAvailable(clusterStateBuilder))); assertThat(manager.indexExists(), Matchers.equalTo(true)); - assertThat(manager.isAvailable(), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(true)); assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); } @@ -164,6 +165,96 @@ public void testIndexWithoutPrimaryShards() { assertIndexUpToDateButNotAvailable(); } + public void testIndexAvailability() { + assertInitialState(); + final ClusterState cs = createClusterState( + TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7, + SecuritySystemIndices.SECURITY_MAIN_ALIAS + ).build(); + Index index = cs.metadata().index(TestRestrictedIndices.INTERNAL_SECURITY_MAIN_INDEX_7).getIndex(); + ShardId shardId = new ShardId(index, 0); + ShardRouting primary = ShardRouting.newUnassigned( + shardId, + true, + RecoverySource.ExistingStoreRecoverySource.INSTANCE, + new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, ""), + ShardRouting.Role.INDEX_ONLY + ); + ShardRouting replica = ShardRouting.newUnassigned( + shardId, + false, + RecoverySource.PeerRecoverySource.INSTANCE, + new UnassignedInfo(UnassignedInfo.Reason.REPLICA_ADDED, null), + ShardRouting.Role.SEARCH_ONLY + ); + String nodeId = ESTestCase.randomAlphaOfLength(8); + String nodeId2 = ESTestCase.randomAlphaOfLength(8); + + // primary/index unavailable, replica/search unavailable + IndexShardRoutingTable.Builder indxShardRoutingTableBuilder = IndexShardRoutingTable.builder(shardId) + .addShard( + primary.initialize(nodeId, null, primary.getExpectedShardSize()) + .moveToUnassigned(new UnassignedInfo(UnassignedInfo.Reason.ALLOCATION_FAILED, "")) + ) + .addShard( + replica.initialize(nodeId2, null, replica.getExpectedShardSize()) + .moveToUnassigned(new UnassignedInfo(UnassignedInfo.Reason.ALLOCATION_FAILED, "")) + ); + IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index).addIndexShard(indxShardRoutingTableBuilder); + RoutingTable routingTable = RoutingTable.builder().add(indexRoutingTableBuilder.build()).build(); + ClusterState.Builder clusterStateBuilder = ClusterState.builder(cs); + clusterStateBuilder.routingTable(routingTable); + ClusterState clusterState = clusterStateBuilder.build(); + manager.clusterChanged(event(clusterState)); + assertThat(manager.indexExists(), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(false)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); + assertThat(manager.isStateRecovered(), Matchers.equalTo(true)); + + // primary/index available, replica/search available + indxShardRoutingTableBuilder = IndexShardRoutingTable.builder(shardId) + .addShard( + primary.initialize(nodeId, null, primary.getExpectedShardSize()).moveToStarted(ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE) + ) + .addShard( + replica.initialize(nodeId2, null, replica.getExpectedShardSize()) + .moveToStarted(ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE) // start replica + ); + indexRoutingTableBuilder = IndexRoutingTable.builder(index).addIndexShard(indxShardRoutingTableBuilder); + routingTable = RoutingTable.builder().add(indexRoutingTableBuilder.build()).build(); + clusterStateBuilder = ClusterState.builder(cs); + clusterStateBuilder.routingTable(routingTable); + clusterState = clusterStateBuilder.build(); + manager.clusterChanged(event(clusterState)); + assertThat(manager.indexExists(), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(true)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); + assertThat(manager.isStateRecovered(), Matchers.equalTo(true)); + + // primary/index available, replica/search unavailable + indxShardRoutingTableBuilder = IndexShardRoutingTable.builder(shardId) + .addShard( + primary.initialize(nodeId, null, primary.getExpectedShardSize()).moveToStarted(ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE) + ) + .addShard(replica.initialize(nodeId2, null, replica.getExpectedShardSize())); // initialized, but not started + indexRoutingTableBuilder = IndexRoutingTable.builder(index).addIndexShard(indxShardRoutingTableBuilder); + routingTable = RoutingTable.builder().add(indexRoutingTableBuilder.build()).build(); + clusterStateBuilder = ClusterState.builder(cs); + clusterStateBuilder.routingTable(routingTable); + clusterState = clusterStateBuilder.build(); + manager.clusterChanged(event(clusterState)); + assertThat(manager.indexExists(), Matchers.equalTo(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(true)); + assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); + assertThat(manager.isStateRecovered(), Matchers.equalTo(true)); + + // primary/index unavailable, replica/search available + // it is not currently possibly to have unassigned primaries with assigned replicas + } + private ClusterChangedEvent event(ClusterState clusterState) { return new ClusterChangedEvent("test-event", clusterState, EMPTY_CLUSTER_STATE); } @@ -419,7 +510,8 @@ public void testProcessClosedIndexState() { ); manager.clusterChanged(event(markShardsAvailable(indexAvailable))); assertThat(manager.indexExists(), is(true)); - assertThat(manager.isAvailable(), is(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), is(true)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), is(true)); // Now close it ClusterState.Builder indexClosed = createClusterState( @@ -436,19 +528,22 @@ public void testProcessClosedIndexState() { manager.clusterChanged(event(indexClosed.build())); assertThat(manager.indexExists(), is(true)); - assertThat(manager.isAvailable(), is(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), is(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), is(false)); } private void assertInitialState() { assertThat(manager.indexExists(), Matchers.equalTo(false)); - assertThat(manager.isAvailable(), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(false)); assertThat(manager.isMappingUpToDate(), Matchers.equalTo(false)); assertThat(manager.isStateRecovered(), Matchers.equalTo(false)); } private void assertIndexUpToDateButNotAvailable() { assertThat(manager.indexExists(), Matchers.equalTo(true)); - assertThat(manager.isAvailable(), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS), Matchers.equalTo(false)); + assertThat(manager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS), Matchers.equalTo(false)); assertThat(manager.isMappingUpToDate(), Matchers.equalTo(true)); assertThat(manager.isStateRecovered(), Matchers.equalTo(true)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java index 82b7b312465d3..a15d8409fe2b4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityMocks.java @@ -86,9 +86,10 @@ public static SecurityIndexManager mockSecurityIndexManager(String alias, boolea return null; }).when(securityIndexManager).checkIndexVersionThenExecute(anyConsumer(), any(Runnable.class)); when(securityIndexManager.indexExists()).thenReturn(exists); - when(securityIndexManager.isAvailable()).thenReturn(available); + when(securityIndexManager.isAvailable(SecurityIndexManager.Availability.PRIMARY_SHARDS)).thenReturn(available); + when(securityIndexManager.isAvailable(SecurityIndexManager.Availability.SEARCH_SHARDS)).thenReturn(available); when(securityIndexManager.aliasName()).thenReturn(alias); - when(securityIndexManager.freeze()).thenReturn(securityIndexManager); + when(securityIndexManager.defensiveCopy()).thenReturn(securityIndexManager); return securityIndexManager; }