diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java index 439168350fc..80c17c48473 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java @@ -14,12 +14,15 @@ import java.util.Collections; import java.util.Set; public final class RestrictedIndicesNames { - public static final String INTERNAL_SECURITY_INDEX_6 = ".security-6"; - public static final String INTERNAL_SECURITY_INDEX_7 = ".security-7"; - public static final String SECURITY_INDEX_NAME = ".security"; + public static final String INTERNAL_SECURITY_MAIN_INDEX_6 = ".security-6"; + public static final String INTERNAL_SECURITY_MAIN_INDEX_7 = ".security-7"; + public static final String SECURITY_MAIN_ALIAS = ".security"; - public static final Set RESTRICTED_NAMES = Collections.unmodifiableSet( - Sets.newHashSet(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX_6, INTERNAL_SECURITY_INDEX_7)); + public static final String INTERNAL_SECURITY_TOKENS_INDEX_7 = ".security-tokens-7"; + public static final String SECURITY_TOKENS_ALIAS = ".security-tokens"; + + public static final Set RESTRICTED_NAMES = Collections.unmodifiableSet(Sets.newHashSet(SECURITY_MAIN_ALIAS, + INTERNAL_SECURITY_MAIN_INDEX_6, INTERNAL_SECURITY_MAIN_INDEX_7, INTERNAL_SECURITY_TOKENS_INDEX_7, SECURITY_TOKENS_ALIAS)); public static final Automaton NAMES_AUTOMATON = Automatons.patterns(RESTRICTED_NAMES); diff --git a/x-pack/plugin/core/src/main/resources/security-index-template.json b/x-pack/plugin/core/src/main/resources/security-index-template-7.json similarity index 99% rename from x-pack/plugin/core/src/main/resources/security-index-template.json rename to x-pack/plugin/core/src/main/resources/security-index-template-7.json index 8d567df5a51..ebf6d073cd8 100644 --- a/x-pack/plugin/core/src/main/resources/security-index-template.json +++ b/x-pack/plugin/core/src/main/resources/security-index-template-7.json @@ -1,5 +1,5 @@ { - "index_patterns" : [ ".security-*" ], + "index_patterns" : [ ".security-7" ], "order" : 1000, "settings" : { "number_of_shards" : 1, diff --git a/x-pack/plugin/core/src/main/resources/security-tokens-index-template-7.json b/x-pack/plugin/core/src/main/resources/security-tokens-index-template-7.json new file mode 100644 index 00000000000..e7450d0be9c --- /dev/null +++ b/x-pack/plugin/core/src/main/resources/security-tokens-index-template-7.json @@ -0,0 +1,96 @@ +{ + "index_patterns" : [ ".security-tokens-7" ], + "order" : 1000, + "settings" : { + "number_of_shards" : 1, + "number_of_replicas" : 0, + "auto_expand_replicas" : "0-1", + "index.priority": 1000, + "index.format": 7 + }, + "mappings" : { + "_doc" : { + "_meta": { + "security-version": "${security.template.version}" + }, + "dynamic" : "strict", + "properties" : { + "doc_type" : { + "type" : "keyword" + }, + "creation_time" : { + "type" : "date", + "format" : "epoch_millis" + }, + "refresh_token" : { + "type" : "object", + "properties" : { + "token" : { + "type" : "keyword" + }, + "refreshed" : { + "type" : "boolean" + }, + "refresh_time": { + "type": "date", + "format": "epoch_millis" + }, + "superseded_by": { + "type": "keyword" + }, + "invalidated" : { + "type" : "boolean" + }, + "client" : { + "type" : "object", + "properties" : { + "type" : { + "type" : "keyword" + }, + "user" : { + "type" : "keyword" + }, + "realm" : { + "type" : "keyword" + } + } + } + } + }, + "access_token" : { + "type" : "object", + "properties" : { + "user_token" : { + "type" : "object", + "properties" : { + "id" : { + "type" : "keyword" + }, + "expiration_time" : { + "type" : "date", + "format" : "epoch_millis" + }, + "version" : { + "type" : "integer" + }, + "metadata" : { + "type" : "object", + "dynamic" : false + }, + "authentication" : { + "type" : "binary" + } + } + }, + "invalidated" : { + "type" : "boolean" + }, + "realm" : { + "type" : "keyword" + } + } + } + } + } + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 625e5ddf47c..78f9623f4fb 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -601,14 +601,14 @@ public class ReservedRolesStoreTests extends ESTestCase { private void assertMonitoringOnRestrictedIndices(Role role) { final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); final MetaData metaData = new MetaData.Builder() .put(new IndexMetaData.Builder(internalSecurityIndex) .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); @@ -616,10 +616,10 @@ public class ReservedRolesStoreTests extends ESTestCase { GetSettingsAction.NAME, IndicesShardStoresAction.NAME, UpgradeStatusAction.NAME, RecoveryAction.NAME); for (final String indexMonitoringActionName : indexMonitoringActionNamesList) { final Map authzMap = role.indices().authorize(indexMonitoringActionName, - Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_INDEX_NAME), + Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), metaData.getAliasAndIndexLookup(), fieldPermissionsCache); assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true)); - assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_INDEX_NAME).isGranted(), is(true)); + assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true)); } } @@ -713,8 +713,8 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false)); final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); final MetaData metaData = new MetaData.Builder() .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) @@ -731,7 +731,7 @@ public class ReservedRolesStoreTests extends ESTestCase { .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); @@ -753,8 +753,8 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(authzMap.get("aaaaaa").isGranted(), is(true)); assertThat(authzMap.get("b").isGranted(), is(true)); authzMap = superuserRole.indices().authorize(randomFrom(IndexAction.NAME, DeleteIndexAction.NAME, SearchAction.NAME), - Sets.newHashSet(RestrictedIndicesNames.SECURITY_INDEX_NAME), lookup, fieldPermissionsCache); - assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_INDEX_NAME).isGranted(), is(true)); + Sets.newHashSet(RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup, fieldPermissionsCache); + assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true)); assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true)); assertTrue(superuserRole.indices().check(SearchAction.NAME)); assertFalse(superuserRole.indices().check("unknown")); @@ -762,7 +762,7 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(superuserRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(true)); assertThat(superuserRole.indices().allowedIndicesMatcher(randomFrom(IndexAction.NAME, DeleteIndexAction.NAME, SearchAction.NAME)) - .test(RestrictedIndicesNames.SECURITY_INDEX_NAME), is(true)); + .test(RestrictedIndicesNames.SECURITY_MAIN_ALIAS), is(true)); assertThat(superuserRole.indices().allowedIndicesMatcher(randomFrom(IndexAction.NAME, DeleteIndexAction.NAME, SearchAction.NAME)) .test(internalSecurityIndex), is(true)); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 3010c3637b6..a48cbd0f556 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -258,9 +258,9 @@ import static java.util.Collections.singletonList; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING; import static org.elasticsearch.xpack.core.XPackSettings.API_KEY_SERVICE_ENABLED_SETTING; import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_INDEX_FORMAT; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_TEMPLATE_NAME; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7; public class Security extends Plugin implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin, MapperPlugin, ExtensiblePlugin { @@ -406,9 +406,10 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw components.add(auditTrailService); this.auditTrailService.set(auditTrailService); - securityIndex.set(SecurityIndexManager.buildSecurityIndexManager(client, clusterService)); + securityIndex.set(SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService)); - final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(), clusterService); + final TokenService tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex.get(), + SecurityIndexManager.buildSecurityTokensIndexManager(client, clusterService), clusterService); this.tokenService.set(tokenService); components.add(tokenService); @@ -948,7 +949,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw public UnaryOperator> getIndexTemplateMetaDataUpgrader() { return templates -> { // .security index is not managed by using templates anymore - templates.remove(SECURITY_TEMPLATE_NAME); + templates.remove(SECURITY_MAIN_TEMPLATE_7); templates.remove("security_audit_log"); return templates; }; @@ -1015,9 +1016,9 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw @Override public void accept(DiscoveryNode node, ClusterState state) { if (state.getNodes().getMinNodeVersion().before(Version.V_7_0_0)) { - IndexMetaData indexMetaData = state.getMetaData().getIndices().get(SECURITY_INDEX_NAME); - if (indexMetaData != null && INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()) < INTERNAL_INDEX_FORMAT) { - throw new IllegalStateException("Security index is not on the current version [" + INTERNAL_INDEX_FORMAT + "] - " + + IndexMetaData indexMetaData = state.getMetaData().getIndices().get(SECURITY_MAIN_ALIAS); + if (indexMetaData != null && INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()) < INTERNAL_MAIN_INDEX_FORMAT) { + throw new IllegalStateException("Security index is not on the current version [" + INTERNAL_MAIN_INDEX_FORMAT + "] - " + "The Upgrade API must be run for 7.x nodes to join the cluster"); } } 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 980a39a1866..8849acb2c42 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 @@ -98,7 +98,7 @@ import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; 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.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; public class ApiKeyService { @@ -207,7 +207,7 @@ public class ApiKeyService { .should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expiration_time"))); boolQuery.filter(expiredQuery); - final SearchRequest searchRequest = client.prepareSearch(SECURITY_INDEX_NAME) + final SearchRequest searchRequest = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(boolQuery) .setVersion(false) @@ -286,7 +286,7 @@ public class ApiKeyService { .endObject() .endObject(); final IndexRequest indexRequest = - client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME) + client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME) .setSource(builder) .setRefreshPolicy(request.getRefreshPolicy()) .request(); @@ -319,7 +319,7 @@ public class ApiKeyService { if (credentials != null) { final GetRequest getRequest = client - .prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, credentials.getId()) + .prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, credentials.getId()) .setFetchSource(true) .request(); executeAsyncWithOrigin(ctx, SECURITY_ORIGIN, getRequest, ActionListener.wrap(response -> { @@ -727,7 +727,7 @@ public class ApiKeyService { expiredQuery.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expiration_time"))); boolQuery.filter(expiredQuery); } - final SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) + final SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(boolQuery) .setVersion(false) @@ -801,7 +801,7 @@ public class ApiKeyService { BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); for (String apiKeyId : apiKeyIds) { UpdateRequest request = client - .prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, apiKeyId) + .prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, apiKeyId) .setDoc(Collections.singletonMap("api_key_invalidated", true)) .request(); bulkRequestBuilder.add(request); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredApiKeysRemover.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredApiKeysRemover.java index 2cb9969337f..755224671e1 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredApiKeysRemover.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ExpiredApiKeysRemover.java @@ -22,7 +22,7 @@ import org.elasticsearch.index.reindex.DeleteByQueryRequest; import org.elasticsearch.index.reindex.ScrollableHitSource; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool.Names; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import java.time.Duration; import java.time.Instant; @@ -51,7 +51,7 @@ public final class ExpiredApiKeysRemover extends AbstractRunnable { @Override public void doRun() { - DeleteByQueryRequest expiredDbq = new DeleteByQueryRequest(SecurityIndexManager.SECURITY_INDEX_NAME); + DeleteByQueryRequest expiredDbq = new DeleteByQueryRequest(RestrictedIndicesNames.SECURITY_MAIN_ALIAS); if (timeout != TimeValue.MINUS_ONE) { expiredDbq.setTimeout(timeout); expiredDbq.getSearchRequest().source().timeout(timeout); 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 c51c712506d..23e7bb2fe0f 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 @@ -26,6 +26,8 @@ import org.elasticsearch.xpack.security.support.SecurityIndexManager; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import static org.elasticsearch.action.support.TransportActions.isShardNotAvailableException; @@ -33,25 +35,44 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; /** - * Responsible for cleaning the invalidated and expired tokens from the security index. - * The document gets deleted if it was created more than 24 hours which is the maximum - * lifetime of a refresh token + * Responsible for cleaning the invalidated and expired tokens from the security indices (`main` and `tokens`). + * The document is deleted if it was created more than {@code #MAXIMUM_TOKEN_LIFETIME_HOURS} hours in the past. */ final class ExpiredTokenRemover extends AbstractRunnable { private static final Logger logger = LogManager.getLogger(ExpiredTokenRemover.class); - private final Client client; - private final AtomicBoolean inProgress = new AtomicBoolean(false); - private final TimeValue timeout; + public static final long MAXIMUM_TOKEN_LIFETIME_HOURS = 24L; - ExpiredTokenRemover(Settings settings, Client client) { + private final Client client; + private final SecurityIndexManager securityMainIndex; + private final SecurityIndexManager securityTokensIndex; + private final AtomicBoolean inProgress; + private final TimeValue timeout; + private boolean checkMainIndexForExpiredTokens; + + ExpiredTokenRemover(Settings settings, Client client, SecurityIndexManager securityMainIndex, + SecurityIndexManager securityTokensIndex) { this.client = client; + this.securityMainIndex = securityMainIndex; + this.securityTokensIndex = securityTokensIndex; + this.inProgress = new AtomicBoolean(false); this.timeout = TokenService.DELETE_TIMEOUT.get(settings); + this.checkMainIndexForExpiredTokens = true; } @Override public void doRun() { - DeleteByQueryRequest expiredDbq = new DeleteByQueryRequest(SecurityIndexManager.SECURITY_INDEX_NAME); + final List indicesWithTokens = new ArrayList<>(); + if (securityTokensIndex.isAvailable()) { + indicesWithTokens.add(securityTokensIndex.aliasName()); + } + if (securityMainIndex.isAvailable() && checkMainIndexForExpiredTokens) { + indicesWithTokens.add(securityMainIndex.aliasName()); + } + if (indicesWithTokens.isEmpty()) { + return; + } + DeleteByQueryRequest expiredDbq = new DeleteByQueryRequest(indicesWithTokens.toArray(new String[0])); if (timeout != TimeValue.MINUS_ONE) { expiredDbq.setTimeout(timeout); expiredDbq.getSearchRequest().source().timeout(timeout); @@ -59,12 +80,20 @@ final class ExpiredTokenRemover extends AbstractRunnable { final Instant now = Instant.now(); expiredDbq .setQuery(QueryBuilders.boolQuery() - .filter(QueryBuilders.termsQuery("doc_type", "token")) - .filter(QueryBuilders.rangeQuery("creation_time").lte(now.minus(24L, ChronoUnit.HOURS).toEpochMilli()))); + .filter(QueryBuilders.termsQuery("doc_type", TokenService.TOKEN_DOC_TYPE)) + .filter(QueryBuilders.rangeQuery("creation_time") + .lte(now.minus(MAXIMUM_TOKEN_LIFETIME_HOURS, ChronoUnit.HOURS).toEpochMilli()))); logger.trace(() -> new ParameterizedMessage("Removing old tokens: [{}]", Strings.toString(expiredDbq))); executeAsyncWithOrigin(client, SECURITY_ORIGIN, DeleteByQueryAction.INSTANCE, expiredDbq, - ActionListener.wrap(r -> { - debugDbqResponse(r); + ActionListener.wrap(bulkResponse -> { + debugDbqResponse(bulkResponse); + // tokens can still linger on the main index for their maximum lifetime after the tokens index has been created, because + // only after the tokens index has been created all nodes will store tokens there and not on the main security index + if (checkMainIndexForExpiredTokens && securityTokensIndex.indexExists() + && securityTokensIndex.getCreationTime().isBefore(now.minus(MAXIMUM_TOKEN_LIFETIME_HOURS, ChronoUnit.HOURS)) + && bulkResponse.getBulkFailures().isEmpty() && bulkResponse.getSearchFailures().isEmpty()) { + checkMainIndexForExpiredTokens = false; + } markComplete(); }, this::onFailure)); } 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 7ff8721953e..0481867b594 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 @@ -69,6 +69,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.iterable.Iterables; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.engine.VersionConflictEngineException; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; @@ -120,10 +121,12 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -137,7 +140,6 @@ import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; 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.SECURITY_INDEX_NAME; import static org.elasticsearch.threadpool.ThreadPool.Names.GENERIC; /** @@ -174,10 +176,15 @@ public final class TokenService { public static final Setting DELETE_TIMEOUT = Setting.timeSetting("xpack.security.authc.token.delete.timeout", TimeValue.MINUS_ONE, Property.NodeScope); - private static final String TOKEN_DOC_TYPE = "token"; + static final String TOKEN_DOC_TYPE = "token"; private static final String TOKEN_DOC_ID_PREFIX = TOKEN_DOC_TYPE + "_"; static final int MINIMUM_BYTES = VERSION_BYTES + SALT_BYTES + IV_BYTES + 1; static final int MINIMUM_BASE64_BYTES = Double.valueOf(Math.ceil((4 * MINIMUM_BYTES) / 3)).intValue(); + static final Version VERSION_TOKENS_INDEX_INTRODUCED = Version.V_7_1_0; + static final Version VERSION_ACCESS_TOKENS_AS_UUIDS = Version.V_7_1_0; + static final Version VERSION_MULTIPLE_CONCURRENT_REFRESHES = Version.V_7_1_0; + // UUIDs are 16 bytes encoded base64 without padding, therefore the length is (16 / 3) * 4 + ((16 % 3) * 8 + 5) / 6 chars + private static final int TOKEN_ID_LENGTH = 22; private static final Logger logger = LogManager.getLogger(TokenService.class); private final SecureRandom secureRandom = new SecureRandom(); @@ -187,7 +194,8 @@ public final class TokenService { private final TimeValue expirationDelay; private final TimeValue deleteInterval; private final Client client; - private final SecurityIndexManager securityIndex; + private final SecurityIndexManager securityMainIndex; + private final SecurityIndexManager securityTokensIndex; private final ExpiredTokenRemover expiredTokenRemover; private final boolean enabled; private volatile TokenKeys keyCache; @@ -196,13 +204,9 @@ public final class TokenService { /** * Creates a new token service - * - * @param settings the node settings - * @param clock the clock that will be used for comparing timestamps - * @param client the client to use when checking for revocations */ - public TokenService(Settings settings, Clock clock, Client client, - SecurityIndexManager securityIndex, ClusterService clusterService) throws GeneralSecurityException { + public TokenService(Settings settings, Clock clock, Client client, SecurityIndexManager securityMainIndex, + SecurityIndexManager securityTokensIndex, ClusterService clusterService) throws GeneralSecurityException { byte[] saltArr = new byte[SALT_BYTES]; secureRandom.nextBytes(saltArr); final SecureString tokenPassphrase = generateTokenKey(); @@ -210,11 +214,12 @@ public final class TokenService { this.clock = clock.withZone(ZoneOffset.UTC); this.expirationDelay = TOKEN_EXPIRATION.get(settings); this.client = client; - this.securityIndex = securityIndex; + this.securityMainIndex = securityMainIndex; + this.securityTokensIndex = securityTokensIndex; this.lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); this.deleteInterval = DELETE_INTERVAL.get(settings); this.enabled = isTokenServiceEnabled(settings); - this.expiredTokenRemover = new ExpiredTokenRemover(settings, client); + this.expiredTokenRemover = new ExpiredTokenRemover(settings, client, securityMainIndex, securityTokensIndex); ensureEncryptionCiphersSupported(); KeyAndCache keyAndCache = new KeyAndCache(new KeyAndTimestamp(tokenPassphrase, createdTimeStamps.incrementAndGet()), new BytesKey(saltArr)); @@ -224,10 +229,6 @@ public final class TokenService { getTokenMetaData(); } - public static Boolean isTokenServiceEnabled(Settings settings) { - return XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings); - } - /** * Creates an access token and optionally a refresh token as well, based on the provided authentication and metadata with an * auto-generated token document id. The created tokens are stored in the security index. @@ -235,16 +236,25 @@ public final class TokenService { public void createOAuth2Tokens(Authentication authentication, Authentication originatingClientAuth, Map metadata, boolean includeRefreshToken, ActionListener> listener) { - createOAuth2Tokens(UUIDs.randomBase64UUID(), authentication, originatingClientAuth, metadata, includeRefreshToken, listener); + // the created token is compatible with the oldest node version in the cluster + final Version tokenVersion = getTokenVersionCompatibility(); + // tokens moved to a separate index in newer versions + final SecurityIndexManager tokensIndex = getTokensIndexForVersion(tokenVersion); + // the id of the created tokens ought be unguessable + final String userTokenId = UUIDs.randomBase64UUID(); + createOAuth2Tokens(userTokenId, tokenVersion, tokensIndex, authentication, originatingClientAuth, metadata, includeRefreshToken, + listener); } /** * Create an access token and optionally a refresh token as well, based on the provided authentication and metadata, with the given * token document id. The created tokens are be stored in the security index. */ - private void createOAuth2Tokens(String userTokenId, Authentication authentication, Authentication originatingClientAuth, - Map metadata, boolean includeRefreshToken, - ActionListener> listener) { + private void createOAuth2Tokens(String userTokenId, Version tokenVersion, SecurityIndexManager tokensIndex, + Authentication authentication, Authentication originatingClientAuth, Map metadata, + boolean includeRefreshToken, ActionListener> listener) { + assert userTokenId.length() == TOKEN_ID_LENGTH : "We assume token ids have a fixed length for nodes of a certain version." + + " When changing the token length, be careful that the inferences about its length still hold."; ensureEnabled(); if (authentication == null) { listener.onFailure(traceLog("create token", new IllegalArgumentException("authentication must be provided"))); @@ -252,53 +262,32 @@ public final class TokenService { listener.onFailure(traceLog("create token", new IllegalArgumentException("originating client authentication must be provided"))); } else { - final Instant created = clock.instant(); - final Instant expiration = getExpirationTime(created); - final Version version = clusterService.state().nodes().getMinNodeVersion(); final Authentication tokenAuth = new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(), - authentication.getLookedUpBy(), version, AuthenticationType.TOKEN, authentication.getMetadata()); - final UserToken userToken = new UserToken(userTokenId, version, tokenAuth, expiration, metadata); + authentication.getLookedUpBy(), tokenVersion, AuthenticationType.TOKEN, authentication.getMetadata()); + final UserToken userToken = new UserToken(userTokenId, tokenVersion, tokenAuth, getExpirationTime(), metadata); + final String plainRefreshToken = includeRefreshToken ? UUIDs.randomBase64UUID() : null; + final BytesReference tokenDocument = createTokenDocument(userToken, plainRefreshToken, originatingClientAuth); final String documentId = getTokenDocumentId(userToken); - final String refreshToken = includeRefreshToken ? UUIDs.randomBase64UUID() : null; - - final IndexRequest request; - try (XContentBuilder builder = XContentFactory.jsonBuilder()) { - builder.startObject(); - builder.field("doc_type", TOKEN_DOC_TYPE); - builder.field("creation_time", created.toEpochMilli()); - if (includeRefreshToken) { - builder.startObject("refresh_token") - .field("token", refreshToken) - .field("invalidated", false) - .field("refreshed", false) - .startObject("client") - .field("type", "unassociated_client") - .field("user", originatingClientAuth.getUser().principal()) - .field("realm", originatingClientAuth.getAuthenticatedBy().getName()) - .endObject() - .endObject(); - } - builder.startObject("access_token") - .field("invalidated", false) - .field("user_token", userToken) - .field("realm", authentication.getAuthenticatedBy().getName()) - .endObject(); - builder.endObject(); - request = client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, documentId) - .setOpType(OpType.CREATE) - .setSource(builder) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request(); - } catch (IOException e) { - // unexpected exception - listener.onFailure(e); - return; - } - securityIndex.prepareIndexIfNeededThenExecute(ex -> listener.onFailure(traceLog("prepare security index", documentId, ex)), - () -> executeAsyncWithOrigin(client, SECURITY_ORIGIN, IndexAction.INSTANCE, request, ActionListener - .wrap(indexResponse -> { + final IndexRequest indexTokenRequest = client.prepareIndex(tokensIndex.aliasName(), SINGLE_MAPPING_NAME, documentId) + .setOpType(OpType.CREATE) + .setSource(tokenDocument, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request(); + tokensIndex.prepareIndexIfNeededThenExecute( + ex -> listener.onFailure(traceLog("prepare tokens index [" + tokensIndex.aliasName() + "]", documentId, ex)), + () -> executeAsyncWithOrigin(client, SECURITY_ORIGIN, IndexAction.INSTANCE, indexTokenRequest, + ActionListener.wrap(indexResponse -> { if (indexResponse.getResult() == Result.CREATED) { - listener.onResponse(new Tuple<>(userToken, refreshToken)); + if (tokenVersion.onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED)) { + final String versionedRefreshToken = plainRefreshToken != null + ? prependVersionAndEncode(tokenVersion, plainRefreshToken) + : null; + listener.onResponse(new Tuple<>(userToken, versionedRefreshToken)); + } else { + // prior versions are not version-prepended, as nodes on those versions don't expect it. + // Such nodes might exist in a mixed cluster during a rolling upgrade. + listener.onResponse(new Tuple<>(userToken, plainRefreshToken)); + } } else { listener.onFailure(traceLog("create token", new ElasticsearchException("failed to create token document [{}]", indexResponse))); @@ -349,20 +338,21 @@ public final class TokenService { } /** - * Gets the UserToken with given id by fetching the corresponding token document + * Gets the {@link UserToken} with the given {@code userTokenId} and {@code tokenVersion} by fetching and parsing the corresponding + * token document. */ - void getUserTokenFromId(String userTokenId, ActionListener listener) { - if (securityIndex.isAvailable() == false) { - logger.warn("failed to get access token [{}] because index is not available", userTokenId); + private void getUserTokenFromId(String userTokenId, Version tokenVersion, ActionListener listener) { + final SecurityIndexManager tokensIndex = getTokensIndexForVersion(tokenVersion); + if (tokensIndex.isAvailable() == false) { + logger.warn("failed to get access token [{}] because index [{}] is not available", userTokenId, tokensIndex.aliasName()); listener.onResponse(null); } else { - securityIndex.checkIndexVersionThenExecute( - ex -> listener.onFailure(traceLog("prepare security index", userTokenId, ex)), - () -> { - final GetRequest getRequest = client.prepareGet(SecurityIndexManager.SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, - getTokenDocumentId(userTokenId)).request(); - Consumer onFailure = ex -> listener.onFailure(traceLog("decode token", userTokenId, ex)); - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest, + final GetRequest getRequest = client.prepareGet(tokensIndex.aliasName(), SINGLE_MAPPING_NAME, + getTokenDocumentId(userTokenId)).request(); + final Consumer onFailure = ex -> listener.onFailure(traceLog("decode token", userTokenId, ex)); + tokensIndex.checkIndexVersionThenExecute( + ex -> listener.onFailure(traceLog("prepare tokens index [" + tokensIndex.aliasName() +"]", userTokenId, ex)), + () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest, ActionListener.wrap(response -> { if (response.isExists()) { Map accessTokenSource = @@ -386,35 +376,36 @@ public final class TokenService { // if the index or the shard is not there / available we assume that // the token is not valid if (isShardNotAvailableException(e)) { - logger.warn("failed to get access token [{}] because index is not available", userTokenId); + logger.warn("failed to get access token [{}] because index [{}] is not available", userTokenId, + tokensIndex.aliasName()); listener.onResponse(null); } else { logger.error(new ParameterizedMessage("failed to get access token [{}]", userTokenId), e); listener.onFailure(e); } - }), client::get); - }); + }), client::get) + ); } } - /* - * If needed, for tokens that were created in a pre 7.1.0 cluster, it asynchronously decodes the token to get the token document Id. - * The process for this is asynchronous as we may need to compute a key, which can be computationally expensive - * so this should not block the current thread, which is typically a network thread. A second reason for being asynchronous is that - * we can restrain the amount of resources consumed by the key computation to a single thread. - * For tokens created in an after 7.1.0 cluster, the token is just the token document Id so this is used directly without decryption + /** + * If needed, for tokens that were created in a pre {@code #VERSION_ACCESS_TOKENS_UUIDS} cluster, it asynchronously decodes the token to + * get the token document id. The process for this is asynchronous as we may need to compute a key, which can be computationally + * expensive so this should not block the current thread, which is typically a network thread. A second reason for being asynchronous is + * that we can restrain the amount of resources consumed by the key computation to a single thread. For tokens created in an after + * {@code #VERSION_ACCESS_TOKENS_UUIDS} cluster, the token is just the token document Id so this is used directly without decryption */ void decodeToken(String token, ActionListener listener) { final byte[] bytes = token.getBytes(StandardCharsets.UTF_8); try (StreamInput in = new InputStreamStreamInput(Base64.getDecoder().wrap(new ByteArrayInputStream(bytes)), bytes.length)) { final Version version = Version.readVersion(in); in.setVersion(version); - if (version.onOrAfter(Version.V_7_1_0)) { - // The token was created in a > 7.1.0 cluster so it contains the tokenId as a String + if (version.onOrAfter(VERSION_ACCESS_TOKENS_AS_UUIDS)) { + // The token was created in a > VERSION_ACCESS_TOKENS_UUIDS cluster so it contains the tokenId as a String String usedTokenId = in.readString(); - getUserTokenFromId(usedTokenId, listener); + getUserTokenFromId(usedTokenId, version, listener); } else { - // The token was created in a < 7.1.0 cluster so we need to decrypt it to get the tokenId + // The token was created in a < VERSION_ACCESS_TOKENS_UUIDS cluster so we need to decrypt it to get the tokenId if (in.available() < MINIMUM_BASE64_BYTES) { logger.debug("invalid token, smaller than [{}] bytes", MINIMUM_BASE64_BYTES); listener.onResponse(null); @@ -433,7 +424,7 @@ public final class TokenService { try { final Cipher cipher = getDecryptionCipher(iv, decodeKey, version, decodedSalt); final String tokenId = decryptTokenId(encryptedTokenId, cipher, version); - getUserTokenFromId(tokenId, listener); + getUserTokenFromId(tokenId, version, listener); } catch (IOException | GeneralSecurityException e) { // could happen with a token that is not ours logger.warn("invalid token", e); @@ -461,30 +452,6 @@ public final class TokenService { } } - private void getKeyAsync(BytesKey decodedSalt, KeyAndCache keyAndCache, ActionListener listener) { - final SecretKey decodeKey = keyAndCache.getKey(decodedSalt); - if (decodeKey != null) { - listener.onResponse(decodeKey); - } else { - /* As a measure of protected against DOS, we can pass requests requiring a key - * computation off to a single thread executor. For normal usage, the initial - * request(s) that require a key computation will be delayed and there will be - * some additional latency. - */ - client.threadPool().executor(THREAD_POOL_NAME) - .submit(new KeyComputingRunnable(decodedSalt, keyAndCache, listener)); - } - } - - private static String decryptTokenId(byte[] encryptedTokenId, Cipher cipher, Version version) throws IOException { - try (ByteArrayInputStream bais = new ByteArrayInputStream(encryptedTokenId); - CipherInputStream cis = new CipherInputStream(bais, cipher); - StreamInput decryptedInput = new InputStreamStreamInput(cis)) { - decryptedInput.setVersion(version); - return decryptedInput.readString(); - } - } - /** * This method performs the steps necessary to invalidate an access token so that it may no longer be * used. The process of invalidation involves performing an update to the token document and setting @@ -501,7 +468,7 @@ public final class TokenService { if (userToken == null) { listener.onFailure(traceLog("invalidate token", accessToken, malformedTokenException())); } else { - indexInvalidation(Collections.singleton(userToken.getId()), backoff, "access_token", null, listener); + indexInvalidation(Collections.singleton(userToken), backoff, "access_token", null, listener); } }, listener::onFailure)); } @@ -509,8 +476,6 @@ public final class TokenService { /** * This method performs the steps necessary to invalidate a token so that it may no longer be used. - * - * @see #invalidateAccessToken(String, ActionListener) */ public void invalidateAccessToken(UserToken userToken, ActionListener listener) { ensureEnabled(); @@ -520,12 +485,12 @@ public final class TokenService { } else { maybeStartTokenRemover(); final Iterator backoff = DEFAULT_BACKOFF.iterator(); - indexInvalidation(Collections.singleton(userToken.getId()), backoff, "access_token", null, listener); + indexInvalidation(Collections.singleton(userToken), backoff, "access_token", null, listener); } } /** - * This method onvalidates a refresh token so that it may no longer be used. Iinvalidation involves performing an update to the token + * This method invalidates a refresh token so that it may no longer be used. Invalidation involves performing an update to the token * document and setting the refresh_token.invalidated field to true * * @param refreshToken The string representation of the refresh token @@ -541,8 +506,8 @@ public final class TokenService { final Iterator backoff = DEFAULT_BACKOFF.iterator(); findTokenFromRefreshToken(refreshToken, backoff, ActionListener.wrap(searchResponse -> { - final String docId = getTokenIdFromDocumentId(searchResponse.getId()); - indexInvalidation(Collections.singletonList(docId), backoff, "refresh_token", null, listener); + final Tuple parsedTokens = parseTokensFromDocument(searchResponse.getSourceAsMap(), null); + indexInvalidation(Collections.singletonList(parsedTokens.v1()), backoff, "refresh_token", null, listener); }, listener::onFailure)); } } @@ -568,7 +533,7 @@ public final class TokenService { logger.warn("No tokens to invalidate for realm [{}] and username [{}]", realmName, username); listener.onResponse(TokensInvalidationResult.emptyResult()); } else { - invalidateAllTokens(tokenTuples.stream().map(t -> t.v1().getId()).collect(Collectors.toList()), listener); + invalidateAllTokens(tokenTuples.stream().map(t -> t.v1()).collect(Collectors.toList()), listener); } }, listener::onFailure)); } else { @@ -581,7 +546,7 @@ public final class TokenService { logger.warn("No tokens to invalidate for realm [{}] and username [{}]", realmName, username); listener.onResponse(TokensInvalidationResult.emptyResult()); } else { - invalidateAllTokens(tokenTuples.stream().map(t -> t.v1().getId()).collect(Collectors.toList()), listener); + invalidateAllTokens(tokenTuples.stream().map(t -> t.v1()).collect(Collectors.toList()), listener); } }, listener::onFailure)); } @@ -592,34 +557,64 @@ public final class TokenService { * Invalidates a collection of access_token and refresh_token that were retrieved by * {@link TokenService#invalidateActiveTokensForRealmAndUser} * - * @param accessTokenIds The ids of the access tokens which should be invalidated (along with the respective refresh_token) + * @param userTokens The user tokens for which access and refresh tokens should be invalidated * @param listener the listener to notify upon completion */ - private void invalidateAllTokens(Collection accessTokenIds, ActionListener listener) { + private void invalidateAllTokens(Collection userTokens, ActionListener listener) { maybeStartTokenRemover(); // Invalidate the refresh tokens first so that they cannot be used to get new // access tokens while we invalidate the access tokens we currently know about final Iterator backoff = DEFAULT_BACKOFF.iterator(); - indexInvalidation(accessTokenIds, backoff, "refresh_token", null, ActionListener.wrap(result -> - indexInvalidation(accessTokenIds, backoff, "access_token", result, listener), + indexInvalidation(userTokens, backoff, "refresh_token", null, ActionListener.wrap(result -> + indexInvalidation(userTokens, backoff, "access_token", result, listener), listener::onFailure)); } + /** + * Invalidates access and/or refresh tokens associated to a user token (coexisting in the same token document) + */ + private void indexInvalidation(Collection userTokens, Iterator backoff, String srcPrefix, + @Nullable TokensInvalidationResult previousResult, ActionListener listener) { + final Set idsOfRecentTokens = new HashSet<>(); + final Set idsOfOlderTokens = new HashSet<>(); + for (UserToken userToken : userTokens) { + if (userToken.getVersion().onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED)) { + idsOfRecentTokens.add(userToken.getId()); + } else { + idsOfOlderTokens.add(userToken.getId()); + } + } + if (false == idsOfOlderTokens.isEmpty()) { + indexInvalidation(idsOfOlderTokens, securityMainIndex, backoff, srcPrefix, previousResult, ActionListener.wrap(newResult -> { + if (false == idsOfRecentTokens.isEmpty()) { + // carry-over result of the invalidation for the tokens security index + indexInvalidation(idsOfRecentTokens, securityTokensIndex, backoff, srcPrefix, newResult, listener); + } else { + listener.onResponse(newResult); + } + }, listener::onFailure)); + } else { + indexInvalidation(idsOfRecentTokens, securityTokensIndex, backoff, srcPrefix, previousResult, listener); + } + } + /** * Performs the actual invalidation of a collection of tokens. In case of recoverable errors ( see * {@link TransportActions#isShardNotAvailableException} ) the UpdateRequests to mark the tokens as invalidated are retried using * an exponential backoff policy. * - * @param tokenIds the tokens to invalidate - * @param backoff the amount of time to delay between attempts - * @param srcPrefix the prefix to use when constructing the doc to update, either refresh_token or access_token depending on - * what type of tokens should be invalidated - * @param previousResult if this not the initial attempt for invalidation, it contains the result of invalidating - * tokens up to the point of the retry. This result is added to the result of the current attempt - * @param listener the listener to notify upon completion + * @param tokenIds the tokens to invalidate + * @param tokensIndexManager the manager for the index where the tokens are stored + * @param backoff the amount of time to delay between attempts + * @param srcPrefix the prefix to use when constructing the doc to update, either refresh_token or access_token depending on + * what type of tokens should be invalidated + * @param previousResult if this not the initial attempt for invalidation, it contains the result of invalidating + * tokens up to the point of the retry. This result is added to the result of the current attempt + * @param listener the listener to notify upon completion */ - private void indexInvalidation(Collection tokenIds, Iterator backoff, String srcPrefix, - @Nullable TokensInvalidationResult previousResult, ActionListener listener) { + private void indexInvalidation(Collection tokenIds, SecurityIndexManager tokensIndexManager, Iterator backoff, + String srcPrefix, @Nullable TokensInvalidationResult previousResult, + ActionListener listener) { if (tokenIds.isEmpty()) { logger.warn("No [{}] tokens provided for invalidation", srcPrefix); listener.onFailure(invalidGrantException("No tokens provided for invalidation")); @@ -627,14 +622,15 @@ public final class TokenService { BulkRequestBuilder bulkRequestBuilder = client.prepareBulk(); for (String tokenId : tokenIds) { UpdateRequest request = client - .prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getTokenDocumentId(tokenId)) + .prepareUpdate(tokensIndexManager.aliasName(), SINGLE_MAPPING_NAME, getTokenDocumentId(tokenId)) .setDoc(srcPrefix, Collections.singletonMap("invalidated", true)) .setFetchSource(srcPrefix, null) .request(); bulkRequestBuilder.add(request); } bulkRequestBuilder.setRefreshPolicy(RefreshPolicy.WAIT_UNTIL); - securityIndex.prepareIndexIfNeededThenExecute(ex -> listener.onFailure(traceLog("prepare security index", ex)), + tokensIndexManager.prepareIndexIfNeededThenExecute( + ex -> listener.onFailure(traceLog("prepare index [" + tokensIndexManager.aliasName() + "]", ex)), () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, bulkRequestBuilder.request(), ActionListener.wrap(bulkResponse -> { ArrayList retryTokenDocIds = new ArrayList<>(); @@ -672,8 +668,9 @@ public final class TokenService { retryTokenDocIds.size(), tokenIds.size()); final TokensInvalidationResult incompleteResult = new TokensInvalidationResult(invalidated, previouslyInvalidated, failedRequestResponses); - final Runnable retryWithContextRunnable = client.threadPool().getThreadContext().preserveContext( - () -> indexInvalidation(retryTokenDocIds, backoff, srcPrefix, incompleteResult, listener)); + final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() + .preserveContext(() -> indexInvalidation(retryTokenDocIds, tokensIndexManager, backoff, + srcPrefix, incompleteResult, listener)); client.threadPool().schedule(retryWithContextRunnable, backoff.next(), GENERIC); } else { if (retryTokenDocIds.isEmpty() == false) { @@ -695,7 +692,8 @@ public final class TokenService { if (isShardNotAvailableException(cause) && backoff.hasNext()) { logger.debug("failed to invalidate tokens, retrying "); final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() - .preserveContext(() -> indexInvalidation(tokenIds, backoff, srcPrefix, previousResult, listener)); + .preserveContext(() -> indexInvalidation(tokenIds, tokensIndexManager, backoff, srcPrefix, + previousResult, listener)); client.threadPool().schedule(retryWithContextRunnable, backoff.next(), GENERIC); } else { listener.onFailure(e); @@ -721,38 +719,69 @@ public final class TokenService { } /** - * Performs an asynchronous search request for the token document that contains the {@code refreshToken} and calls the listener with the - * {@link SearchResponse}. In case of recoverable errors the SearchRequest is retried using an exponential backoff policy. + * Inferes the format and version of the passed in {@code refreshToken}. Delegates the actual search of the token document to + * {@code #findTokenFromRefreshToken(String, SecurityIndexManager, Iterator, ActionListener)} . */ private void findTokenFromRefreshToken(String refreshToken, Iterator backoff, ActionListener listener) { + if (refreshToken.length() == TOKEN_ID_LENGTH) { + // first check if token has the old format before the new version-prepended one + logger.debug("Assuming an unversioned refresh token [{}], generated for node versions" + + " prior to the introduction of the version-header format.", refreshToken); + findTokenFromRefreshToken(refreshToken, securityMainIndex, backoff, listener); + } else { + try { + final Tuple versionAndRefreshTokenTuple = unpackVersionAndPayload(refreshToken); + final Version refreshTokenVersion = versionAndRefreshTokenTuple.v1(); + final String unencodedRefreshToken = versionAndRefreshTokenTuple.v2(); + if (false == refreshTokenVersion.onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED) + || unencodedRefreshToken.length() != TOKEN_ID_LENGTH) { + logger.debug("Decoded refresh token [{}] with version [{}] is invalid.", unencodedRefreshToken, refreshTokenVersion); + listener.onFailure(malformedTokenException()); + } else { + findTokenFromRefreshToken(unencodedRefreshToken, securityTokensIndex, backoff, listener); + } + } catch (IOException e) { + logger.debug("Could not decode refresh token [" + refreshToken + "].", e); + listener.onFailure(malformedTokenException()); + } + } + } + + /** + * Performs an asynchronous search request for the token document that contains the {@code refreshToken} and calls the {@code listener} + * with the resulting {@link SearchResponse}. In case of recoverable errors the {@code SearchRequest} is retried using an exponential + * backoff policy. This method requires the tokens index where the token document, pointed to by the refresh token, resides. + */ + private void findTokenFromRefreshToken(String refreshToken, SecurityIndexManager tokensIndexManager, Iterator backoff, + ActionListener listener) { final Consumer onFailure = ex -> listener.onFailure(traceLog("find token by refresh token", refreshToken, ex)); final Consumer maybeRetryOnFailure = ex -> { if (backoff.hasNext()) { final TimeValue backofTimeValue = backoff.next(); logger.debug("retrying after [" + backofTimeValue + "] back off"); final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() - .preserveContext(() -> findTokenFromRefreshToken(refreshToken, backoff, listener)); + .preserveContext(() -> findTokenFromRefreshToken(refreshToken, tokensIndexManager, backoff, listener)); client.threadPool().schedule(retryWithContextRunnable, backofTimeValue, GENERIC); } else { logger.warn("failed to find token from refresh token after all retries"); onFailure.accept(ex); } }; - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); - if (frozenSecurityIndex.indexExists() == false) { - logger.warn("security index does not exist therefore refresh token cannot be validated"); + final SecurityIndexManager frozenTokensIndex = tokensIndexManager.freeze(); + if (frozenTokensIndex.indexExists() == false) { + logger.warn("index [{}] does not exist therefore refresh token cannot be validated", frozenTokensIndex.aliasName()); listener.onFailure(invalidGrantException("could not refresh the requested token")); - } else if (frozenSecurityIndex.isAvailable() == false) { - logger.debug("security index is not available to find token from refresh token, retrying"); + } else if (frozenTokensIndex.isAvailable() == false) { + logger.debug("index [{}] is not available to find token from refresh token, retrying", frozenTokensIndex.aliasName()); maybeRetryOnFailure.accept(invalidGrantException("could not refresh the requested token")); } else { - final SearchRequest request = client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + final SearchRequest request = client.prepareSearch(tokensIndexManager.aliasName()) .setQuery(QueryBuilders.boolQuery() .filter(QueryBuilders.termQuery("doc_type", TOKEN_DOC_TYPE)) .filter(QueryBuilders.termQuery("refresh_token.token", refreshToken))) .seqNoAndPrimaryTerm(true) .request(); - securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> + tokensIndexManager.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, ActionListener.wrap(searchResponse -> { if (searchResponse.isTimedOut()) { @@ -805,79 +834,44 @@ public final class TokenService { final RefreshTokenStatus refreshTokenStatus = checkRefreshResult.v1(); if (refreshTokenStatus.isRefreshed()) { logger.debug("Token document [{}] was recently refreshed, when a new token document [{}] was generated. Reusing that result.", - tokenDocId, refreshTokenStatus.getSupersedingDocId()); - getTokenDocAsync(refreshTokenStatus.getSupersedingDocId(), new ActionListener() { - private final Consumer maybeRetryOnFailure = ex -> { - if (backoff.hasNext()) { - final TimeValue backofTimeValue = backoff.next(); - logger.debug("retrying after [" + backofTimeValue + "] back off"); - final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() - .preserveContext(() -> getTokenDocAsync(refreshTokenStatus.getSupersedingDocId(), this)); - client.threadPool().schedule(retryWithContextRunnable, backofTimeValue, GENERIC); - } else { - logger.warn("back off retries exhausted"); - onFailure.accept(ex); - } - }; - - @Override - public void onResponse(GetResponse response) { - if (response.isExists()) { - logger.debug("found superseding token document [{}] for token document [{}]", - refreshTokenStatus.getSupersedingDocId(), tokenDocId); - final Tuple parsedTokens; - try { - parsedTokens = parseTokensFromDocument(response.getSource(), null); - } catch (IllegalStateException | DateTimeException e) { - logger.error("unable to decode existing user token", e); - listener.onFailure(new ElasticsearchSecurityException("could not refresh the requested token", e)); - return; - } - listener.onResponse(parsedTokens); - } else { - // We retry this since the creation of the superseding token document might already be in flight but not - // yet completed, triggered by a refresh request that came a few milliseconds ago - logger.info("could not find superseding token document [{}] for token document [{}], retrying", - refreshTokenStatus.getSupersedingDocId(), tokenDocId); - maybeRetryOnFailure.accept(invalidGrantException("could not refresh the requested token")); - } - } - - @Override - public void onFailure(Exception e) { - if (isShardNotAvailableException(e)) { - logger.info("could not find superseding token document [{}] for refresh, retrying", - refreshTokenStatus.getSupersedingDocId()); - maybeRetryOnFailure.accept(invalidGrantException("could not refresh the requested token")); - } else { - logger.warn("could not find superseding token document [{}] for refresh", refreshTokenStatus.getSupersedingDocId()); - onFailure.accept(invalidGrantException("could not refresh the requested token")); - } - } - }); + tokenDocId, refreshTokenStatus.getSupersededBy()); + getSupersedingTokenDocAsyncWithRetry(refreshTokenStatus, backoff, listener); } else { final String newUserTokenId = UUIDs.randomBase64UUID(); + final Version newTokenVersion = getTokenVersionCompatibility(); final Map updateMap = new HashMap<>(); updateMap.put("refreshed", true); updateMap.put("refresh_time", clock.instant().toEpochMilli()); - updateMap.put("superseded_by", getTokenDocumentId(newUserTokenId)); + if (newTokenVersion.onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED)) { + // the superseding token document reference is formated as "|"; + // for now, only the ".security-tokens|" is a valid reference format + updateMap.put("superseded_by", securityTokensIndex.aliasName() + "|" + getTokenDocumentId(newUserTokenId)); + } else { + // preservers the format of the reference (without the alias prefix) + // so that old nodes in a mixed cluster can still understand it + updateMap.put("superseded_by", getTokenDocumentId(newUserTokenId)); + } assert seqNo != SequenceNumbers.UNASSIGNED_SEQ_NO : "expected an assigned sequence number"; assert primaryTerm != SequenceNumbers.UNASSIGNED_PRIMARY_TERM : "expected an assigned primary term"; - final UpdateRequestBuilder updateRequest = client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, tokenDocId) + final SecurityIndexManager refreshedTokenIndex = getTokensIndexForVersion(refreshTokenStatus.getVersion()); + final UpdateRequestBuilder updateRequest = client + .prepareUpdate(refreshedTokenIndex.aliasName(), SINGLE_MAPPING_NAME, tokenDocId) .setDoc("refresh_token", updateMap) .setFetchSource(true) .setRefreshPolicy(RefreshPolicy.IMMEDIATE) .setIfSeqNo(seqNo) .setIfPrimaryTerm(primaryTerm); - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, updateRequest.request(), + refreshedTokenIndex.prepareIndexIfNeededThenExecute( + ex -> listener.onFailure(traceLog("prepare index [" + refreshedTokenIndex.aliasName() + "]", ex)), + () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, updateRequest.request(), ActionListener.wrap(updateResponse -> { if (updateResponse.getResult() == DocWriteResponse.Result.UPDATED) { logger.debug(() -> new ParameterizedMessage("updated the original token document to {}", updateResponse.getGetResult().sourceAsMap())); final Tuple parsedTokens = parseTokensFromDocument(source, null); final UserToken toRefreshUserToken = parsedTokens.v1(); - createOAuth2Tokens(newUserTokenId, toRefreshUserToken.getAuthentication(), clientAuth, - toRefreshUserToken.getMetadata(), true, listener); + createOAuth2Tokens(newUserTokenId, newTokenVersion, getTokensIndexForVersion(newTokenVersion), + toRefreshUserToken.getAuthentication(), clientAuth, toRefreshUserToken.getMetadata(), true, listener); } else if (backoff.hasNext()) { logger.info("failed to update the original token document [{}], the update result was [{}]. Retrying", tokenDocId, updateResponse.getResult()); @@ -895,7 +889,7 @@ public final class TokenService { if (cause instanceof VersionConflictEngineException) { // The document has been updated by another thread, get it again. logger.debug("version conflict while updating document [{}], attempting to get it again", tokenDocId); - getTokenDocAsync(tokenDocId, new ActionListener() { + getTokenDocAsync(tokenDocId, refreshedTokenIndex, new ActionListener() { @Override public void onResponse(GetResponse response) { if (response.isExists()) { @@ -913,7 +907,7 @@ public final class TokenService { if (backoff.hasNext()) { logger.info("could not get token document [{}] for refresh, retrying", tokenDocId); final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() - .preserveContext(() -> getTokenDocAsync(tokenDocId, this)); + .preserveContext(() -> getTokenDocAsync(tokenDocId, refreshedTokenIndex, this)); client.threadPool().schedule(retryWithContextRunnable, backoff.next(), GENERIC); } else { logger.warn("could not get token document [{}] for refresh after all retries", tokenDocId); @@ -938,18 +932,97 @@ public final class TokenService { } else { onFailure.accept(e); } - }), client::update); + }), client::update)); } } - private void getTokenDocAsync(String tokenDocId, ActionListener listener) { - final GetRequest getRequest = client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, tokenDocId).request(); - executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest, listener, client::get); + private void getSupersedingTokenDocAsyncWithRetry(RefreshTokenStatus refreshTokenStatus, Iterator backoff, + ActionListener> listener) { + final Consumer onFailure = ex -> listener + .onFailure(traceLog("get superseding token", refreshTokenStatus.getSupersededBy(), ex)); + getSupersedingTokenDocAsync(refreshTokenStatus, new ActionListener() { + private final Consumer maybeRetryOnFailure = ex -> { + if (backoff.hasNext()) { + final TimeValue backofTimeValue = backoff.next(); + logger.debug("retrying after [" + backofTimeValue + "] back off"); + final Runnable retryWithContextRunnable = client.threadPool().getThreadContext() + .preserveContext(() -> getSupersedingTokenDocAsync(refreshTokenStatus, this)); + client.threadPool().schedule(retryWithContextRunnable, backofTimeValue, GENERIC); + } else { + logger.warn("back off retries exhausted"); + onFailure.accept(ex); + } + }; + + @Override + public void onResponse(GetResponse response) { + if (response.isExists()) { + logger.debug("found superseding token document [{}] in index [{}] by following the [{}] reference", response.getId(), + response.getIndex(), refreshTokenStatus.getSupersededBy()); + final Tuple parsedTokens; + try { + parsedTokens = parseTokensFromDocument(response.getSource(), null); + } catch (IllegalStateException | DateTimeException e) { + logger.error("unable to decode existing user token", e); + listener.onFailure(new ElasticsearchSecurityException("could not refresh the requested token", e)); + return; + } + listener.onResponse(parsedTokens); + } else { + // We retry this since the creation of the superseding token document might already be in flight but not + // yet completed, triggered by a refresh request that came a few milliseconds ago + logger.info("could not find superseding token document from [{}] reference, retrying", + refreshTokenStatus.getSupersededBy()); + maybeRetryOnFailure.accept(invalidGrantException("could not refresh the requested token")); + } + } + + @Override + public void onFailure(Exception e) { + if (isShardNotAvailableException(e)) { + logger.info("could not find superseding token document from reference [{}], retrying", + refreshTokenStatus.getSupersededBy()); + maybeRetryOnFailure.accept(invalidGrantException("could not refresh the requested token")); + } else { + logger.warn("could not find superseding token document from reference [{}]", refreshTokenStatus.getSupersededBy()); + onFailure.accept(invalidGrantException("could not refresh the requested token")); + } + } + }); + } + + private void getSupersedingTokenDocAsync(RefreshTokenStatus refreshTokenStatus, ActionListener listener) { + final String supersedingDocReference = refreshTokenStatus.getSupersededBy(); + if (supersedingDocReference.startsWith(securityTokensIndex.aliasName() + "|")) { + // superseding token doc is stored on the new tokens index, irrespective of where the superseded token doc resides + final String supersedingDocId = supersedingDocReference.substring(securityTokensIndex.aliasName().length() + 1); + getTokenDocAsync(supersedingDocId, securityTokensIndex, listener); + } else { + assert false == supersedingDocReference + .contains("|") : "The superseding doc reference appears to contain an alias name but should not"; + getTokenDocAsync(supersedingDocReference, securityMainIndex, listener); + } + } + + private void getTokenDocAsync(String tokenDocId, SecurityIndexManager tokensIndex, ActionListener listener) { + final GetRequest getRequest = client.prepareGet(tokensIndex.aliasName(), SINGLE_MAPPING_NAME, tokenDocId).request(); + tokensIndex.checkIndexVersionThenExecute( + ex -> listener.onFailure(traceLog("prepare tokens index [" + tokensIndex.aliasName() + "]", tokenDocId, ex)), + () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest, listener, client::get)); + } + + private Version getTokenVersionCompatibility() { + // newly minted tokens are compatible with the min node version in the cluster + return clusterService.state().nodes().getMinNodeVersion(); + } + + public static Boolean isTokenServiceEnabled(Settings settings) { + return XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.get(settings); } /** - * A refresh token has a hardcoded maximum lifetime of 24h. This checks if the token document represents a valid token wrt this time - * interval. + * A refresh token has a fixed maximum lifetime of {@code ExpiredTokenRemover#MAXIMUM_TOKEN_LIFETIME_HOURS} hours. This checks if the + * token document represents a valid token wrt this time interval. */ private static Optional checkTokenDocumentExpired(Instant now, Map source) { final Long creationEpochMilli = (Long) source.get("creation_time"); @@ -957,7 +1030,7 @@ public final class TokenService { throw new IllegalStateException("token document is missing creation time value"); } else { final Instant creationTime = Instant.ofEpochMilli(creationEpochMilli); - if (now.isAfter(creationTime.plus(24L, ChronoUnit.HOURS))) { + if (now.isAfter(creationTime.plus(ExpiredTokenRemover.MAXIMUM_TOKEN_LIFETIME_HOURS, ChronoUnit.HOURS))) { return Optional.of(invalidGrantException("token document has expired")); } else { return Optional.empty(); @@ -974,12 +1047,13 @@ public final class TokenService { Authentication clientAuth, Map source) throws IllegalStateException, DateTimeException { final RefreshTokenStatus refreshTokenStatus = RefreshTokenStatus.fromSourceMap(getRefreshTokenSourceMap(source)); final UserToken userToken = UserToken.fromSourceMap(getUserTokenSourceMap(source)); + refreshTokenStatus.setVersion(userToken.getVersion()); final ElasticsearchSecurityException validationException = checkTokenDocumentExpired(now, source).orElseGet(() -> { if (refreshTokenStatus.isInvalidated()) { return invalidGrantException("token has been invalidated"); } else { return checkClientCanRefresh(refreshTokenStatus, clientAuth) - .orElse(checkMultipleRefreshes(now, refreshTokenStatus, userToken).orElse(null)); + .orElse(checkMultipleRefreshes(now, refreshTokenStatus).orElse(null)); } }); return new Tuple<>(refreshTokenStatus, Optional.ofNullable(validationException)); @@ -1032,14 +1106,13 @@ public final class TokenService { * @return An {@code Optional} containing the exception in case this refresh token cannot be reused, or an empty Optional if * refreshing is allowed. */ - private static Optional checkMultipleRefreshes(Instant now, RefreshTokenStatus refreshToken, - UserToken userToken) { - if (refreshToken.isRefreshed()) { - if (userToken.getVersion().onOrAfter(Version.V_7_1_0)) { - if (now.isAfter(refreshToken.getRefreshInstant().plus(30L, ChronoUnit.SECONDS))) { + private static Optional checkMultipleRefreshes(Instant now, RefreshTokenStatus refreshTokenStatus) { + if (refreshTokenStatus.isRefreshed()) { + if (refreshTokenStatus.getVersion().onOrAfter(VERSION_MULTIPLE_CONCURRENT_REFRESHES)) { + if (now.isAfter(refreshTokenStatus.getRefreshInstant().plus(30L, ChronoUnit.SECONDS))) { return Optional.of(invalidGrantException("token has already been refreshed more than 30 seconds in the past")); } - if (now.isBefore(refreshToken.getRefreshInstant().minus(30L, ChronoUnit.SECONDS))) { + if (now.isBefore(refreshTokenStatus.getRefreshInstant().minus(30L, ChronoUnit.SECONDS))) { return Optional .of(invalidGrantException("token has been refreshed more than 30 seconds in the future, clock skew too great")); } @@ -1061,39 +1134,40 @@ public final class TokenService { public void findActiveTokensForRealm(String realmName, @Nullable Predicate> filter, ActionListener>> listener) { ensureEnabled(); - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); if (Strings.isNullOrEmpty(realmName)) { - listener.onFailure(new IllegalArgumentException("Realm name is required")); - } else if (frozenSecurityIndex.indexExists() == false) { - listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); - } else { - final Instant now = clock.instant(); - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .filter(QueryBuilders.termQuery("doc_type", TOKEN_DOC_TYPE)) - .filter(QueryBuilders.termQuery("access_token.realm", realmName)) - .filter(QueryBuilders.boolQuery() - .should(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("access_token.invalidated", false)) - .must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli())) - ) - .should(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("refresh_token.invalidated", false)) - .must(QueryBuilders.rangeQuery("creation_time").gte(now.toEpochMilli() - TimeValue.timeValueHours(24).millis())) - ) - ); - - final SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) - .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) - .setQuery(boolQuery) - .setVersion(false) - .setSize(1000) - .setFetchSource(true) - .request(); - securityIndex.checkIndexVersionThenExecute(listener::onFailure, - () -> ScrollHelper.fetchAllByEntity(client, request, listener, (SearchHit hit) -> filterAndParseHit(hit, filter))); + listener.onFailure(new IllegalArgumentException("realm name is required")); + return; } + sourceIndicesWithTokensAndRun(ActionListener.wrap(indicesWithTokens -> { + if (indicesWithTokens.isEmpty()) { + listener.onResponse(Collections.emptyList()); + } else { + final Instant now = clock.instant(); + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() + .filter(QueryBuilders.termQuery("doc_type", TOKEN_DOC_TYPE)) + .filter(QueryBuilders.termQuery("access_token.realm", realmName)) + .filter(QueryBuilders.boolQuery() + .should(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("access_token.invalidated", false)) + .must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli())) + ) + .should(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("refresh_token.invalidated", false)) + .must(QueryBuilders.rangeQuery("creation_time").gte(now.toEpochMilli() + - TimeValue.timeValueHours(ExpiredTokenRemover.MAXIMUM_TOKEN_LIFETIME_HOURS).millis())) + ) + ); + final SearchRequest request = client.prepareSearch(indicesWithTokens.toArray(new String[0])) + .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) + .setQuery(boolQuery) + .setVersion(false) + .setSize(1000) + .setFetchSource(true) + .request(); + ScrollHelper.fetchAllByEntity(client, request, listener, (SearchHit hit) -> filterAndParseHit(hit, filter)); + } + }, listener::onFailure)); + } /** @@ -1105,38 +1179,117 @@ public final class TokenService { */ public void findActiveTokensForUser(String username, ActionListener>> listener) { ensureEnabled(); - final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); if (Strings.isNullOrEmpty(username)) { listener.onFailure(new IllegalArgumentException("username is required")); - } else if (frozenSecurityIndex.indexExists() == false) { - listener.onResponse(Collections.emptyList()); - } else if (frozenSecurityIndex.isAvailable() == false) { - listener.onFailure(frozenSecurityIndex.getUnavailableReason()); - } else { - final Instant now = clock.instant(); - final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() - .filter(QueryBuilders.termQuery("doc_type", TOKEN_DOC_TYPE)) - .filter(QueryBuilders.boolQuery() - .should(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("access_token.invalidated", false)) - .must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli())) - ) - .should(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("refresh_token.invalidated", false)) - .must(QueryBuilders.rangeQuery("creation_time").gte(now.toEpochMilli() - TimeValue.timeValueHours(24).millis())) - ) - ); + return; + } + sourceIndicesWithTokensAndRun(ActionListener.wrap(indicesWithTokens -> { + if (indicesWithTokens.isEmpty()) { + listener.onResponse(Collections.emptyList()); + } else { + final Instant now = clock.instant(); + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() + .filter(QueryBuilders.termQuery("doc_type", TOKEN_DOC_TYPE)) + .filter(QueryBuilders.boolQuery() + .should(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("access_token.invalidated", false)) + .must(QueryBuilders.rangeQuery("access_token.user_token.expiration_time").gte(now.toEpochMilli())) + ) + .should(QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("refresh_token.invalidated", false)) + .must(QueryBuilders.rangeQuery("creation_time").gte(now.toEpochMilli() + - TimeValue.timeValueHours(ExpiredTokenRemover.MAXIMUM_TOKEN_LIFETIME_HOURS).millis())) + ) + ); + final SearchRequest request = client.prepareSearch(indicesWithTokens.toArray(new String[0])) + .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) + .setQuery(boolQuery) + .setVersion(false) + .setSize(1000) + .setFetchSource(true) + .request(); + ScrollHelper.fetchAllByEntity(client, request, listener, (SearchHit hit) -> filterAndParseHit(hit, isOfUser(username))); + } + }, listener::onFailure)); + } - final SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) - .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) - .setQuery(boolQuery) - .setVersion(false) - .setSize(1000) - .setFetchSource(true) - .request(); - securityIndex.checkIndexVersionThenExecute(listener::onFailure, - () -> ScrollHelper.fetchAllByEntity(client, request, listener, - (SearchHit hit) -> filterAndParseHit(hit, isOfUser(username)))); + /** + * Security tokens were traditionally stored on the main security index but after version {@code #VERSION_TOKENS_INDEX_INTRODUCED} they + * have been stored on a dedicated separate index. This move has been implemented without requiring user intervention, so the newly + * created tokens started to be created in the new index, while the old tokens were still usable out of the main security index, subject + * to their maximum lifetime of {@code ExpiredTokenRemover#MAXIMUM_TOKEN_LIFETIME_HOURS} hours. Once the dedicated tokens index has been + * automatically created, all the onwards created tokens will be stored inside it. This function returns the list of the indices names + * that might contain tokens. Unless there are availability or version issues, the dedicated tokens index always contains tokens. The + * main security index might contain tokens if the tokens index has not been created yet, or if it has been created recently so + * that there might still be tokens that have not yet exceeded their maximum lifetime. + */ + private void sourceIndicesWithTokensAndRun(ActionListener> listener) { + final List indicesWithTokens = new ArrayList<>(2); + final SecurityIndexManager frozenTokensIndex = securityTokensIndex.freeze(); + if (frozenTokensIndex.indexExists()) { + // an existing tokens index always contains tokens (if available and version allows) + if (false == frozenTokensIndex.isAvailable()) { + listener.onFailure(frozenTokensIndex.getUnavailableReason()); + return; + } + if (false == frozenTokensIndex.isIndexUpToDate()) { + listener.onFailure(new IllegalStateException( + "Index [" + frozenTokensIndex.aliasName() + "] is not on the current version. Features relying on the index" + + " will not be available until the upgrade API is run on the index")); + return; + } + indicesWithTokens.add(frozenTokensIndex.aliasName()); + } + final SecurityIndexManager frozenMainIndex = securityMainIndex.freeze(); + 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()); + return; + } + if (false == frozenMainIndex.isIndexUpToDate()) { + listener.onFailure(new IllegalStateException( + "Index [" + frozenMainIndex.aliasName() + "] is not on the current version. Features relying on the index" + + " will not be available until the upgrade API is run on the index")); + return; + } + indicesWithTokens.add(frozenMainIndex.aliasName()); + } + } + listener.onResponse(indicesWithTokens); + } + + private BytesReference createTokenDocument(UserToken userToken, @Nullable String refreshToken, + @Nullable Authentication originatingClientAuth) { + assert refreshToken == null || originatingClientAuth != null : "non-null refresh token " + refreshToken + + " requires non-null client authn " + originatingClientAuth; + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject(); + builder.field("doc_type", TOKEN_DOC_TYPE); + builder.field("creation_time", getCreationTime(userToken.getExpirationTime()).toEpochMilli()); + if (refreshToken != null) { + builder.startObject("refresh_token") + .field("token", refreshToken) + .field("invalidated", false) + .field("refreshed", false) + .startObject("client") + .field("type", "unassociated_client") + .field("user", originatingClientAuth.getUser().principal()) + .field("realm", originatingClientAuth.getAuthenticatedBy().getName()) + .endObject() + .endObject(); + } + builder.startObject("access_token") + .field("invalidated", false) + .field("user_token", userToken) + .field("realm", userToken.getAuthentication().getAuthenticatedBy().getName()) + .endObject(); + builder.endObject(); + return BytesReference.bytes(builder); + } catch (IOException e) { + throw new RuntimeException("Unexpected exception when constructing a JSON document.", e); } } @@ -1174,13 +1327,21 @@ public final class TokenService { */ private Tuple parseTokensFromDocument(Map source, @Nullable Predicate> filter) throws IllegalStateException, DateTimeException { - final String refreshToken = (String) ((Map) source.get("refresh_token")).get("token"); + final String plainRefreshToken = (String) ((Map) source.get("refresh_token")).get("token"); final Map userTokenSource = (Map) ((Map) source.get("access_token")).get("user_token"); if (null != filter && filter.test(userTokenSource) == false) { return null; } - return new Tuple<>(UserToken.fromSourceMap(userTokenSource), refreshToken); + final UserToken userToken = UserToken.fromSourceMap(userTokenSource); + if (userToken.getVersion().onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED)) { + final String versionedRefreshToken = plainRefreshToken != null ? + prependVersionAndEncode(userToken.getVersion(), plainRefreshToken) : null; + return new Tuple<>(userToken, versionedRefreshToken); + } else { + // do not prepend version to refresh token as the audience node version cannot deal with it + return new Tuple<>(userToken, plainRefreshToken); + } } private static String getTokenDocumentId(UserToken userToken) { @@ -1205,22 +1366,39 @@ public final class TokenService { } } + /** + * In version {@code #VERSION_TOKENS_INDEX_INTRODUCED} security tokens were moved into a separate index, away from the other entities in + * the main security index, due to their ephemeral nature. They moved "seamlessly" - without manual user intervention. In this way, new + * tokens are created in the new index, while the existing ones were left in place - to be accessed from the old index - and due to be + * removed automatically by the {@code ExpiredTokenRemover} periodic job. Therefore, in general, when searching for a token we need to + * consider both the new and the old indices. + */ + private SecurityIndexManager getTokensIndexForVersion(Version version) { + if (version.onOrAfter(VERSION_TOKENS_INDEX_INTRODUCED)) { + return securityTokensIndex; + } else { + return securityMainIndex; + } + } + /** * Checks if the access token has been explicitly invalidated */ private void checkIfTokenIsValid(UserToken userToken, ActionListener listener) { - Instant currentTime = clock.instant(); - if (currentTime.isAfter(userToken.getExpirationTime())) { + if (clock.instant().isAfter(userToken.getExpirationTime())) { listener.onFailure(traceLog("validate token", userToken.getId(), expiredTokenException())); - } else if (securityIndex.indexExists() == false) { + return; + } + final SecurityIndexManager tokensIndex = getTokensIndexForVersion(userToken.getVersion()); + if (tokensIndex.indexExists() == false) { // index doesn't exist so the token is considered invalid as we cannot verify its validity - logger.warn("failed to validate access token because the security index doesn't exist"); + logger.warn("failed to validate access token because the index [" + tokensIndex.aliasName() + "] doesn't exist"); listener.onResponse(null); } else { - securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { - final GetRequest getRequest = client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getTokenDocumentId(userToken)) - .request(); - Consumer onFailure = ex -> listener.onFailure(traceLog("check token state", userToken.getId(), ex)); + final GetRequest getRequest = client + .prepareGet(tokensIndex.aliasName(), SINGLE_MAPPING_NAME, getTokenDocumentId(userToken)).request(); + Consumer onFailure = ex -> listener.onFailure(traceLog("check token state", userToken.getId(), ex)); + tokensIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, getRequest, ActionListener.wrap(response -> { if (response.isExists()) { @@ -1260,16 +1438,18 @@ public final class TokenService { return expirationDelay; } - private Instant getExpirationTime(Instant now) { - return now.plusSeconds(expirationDelay.getSeconds()); + private Instant getExpirationTime() { + return clock.instant().plusSeconds(expirationDelay.getSeconds()); + } + + private Instant getCreationTime(Instant expire) { + return expire.minusSeconds(expirationDelay.getSeconds()); } private void maybeStartTokenRemover() { - if (securityIndex.isAvailable()) { - if (client.threadPool().relativeTimeInMillis() - lastExpirationRunMs > deleteInterval.getMillis()) { - expiredTokenRemover.submit(client.threadPool()); - lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); - } + if (client.threadPool().relativeTimeInMillis() - lastExpirationRunMs > deleteInterval.getMillis()) { + expiredTokenRemover.submit(client.threadPool()); + lastExpirationRunMs = client.threadPool().relativeTimeInMillis(); } } @@ -1287,12 +1467,12 @@ public final class TokenService { } /** - * Serializes a token to a String containing the version of the node that created the token and - * either an encrypted representation of the token id for versions earlier to 7.0.0 or the token ie - * itself for versions after 7.0.0 + * Serializes a token to a String containing the minimum compatible node version for decoding it back and either an encrypted + * representation of the token id for versions earlier to {@code #VERSION_ACCESS_TOKENS_UUIDS} or the token itself for versions after + * {@code #VERSION_ACCESS_TOKENS_UUIDS} */ public String getAccessTokenAsString(UserToken userToken) throws IOException, GeneralSecurityException { - if (clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.V_7_1_0)) { + if (userToken.getVersion().onOrAfter(VERSION_ACCESS_TOKENS_AS_UUIDS)) { try (ByteArrayOutputStream os = new ByteArrayOutputStream(MINIMUM_BASE64_BYTES); OutputStream base64 = Base64.getEncoder().wrap(os); StreamOutput out = new OutputStreamStreamOutput(base64)) { @@ -1325,6 +1505,33 @@ public final class TokenService { } } + private static String prependVersionAndEncode(Version version, String payload) { + try (ByteArrayOutputStream os = new ByteArrayOutputStream(); + OutputStream base64 = Base64.getEncoder().wrap(os); + StreamOutput out = new OutputStreamStreamOutput(base64)) { + out.setVersion(version); + Version.writeVersion(version, out); + out.writeString(payload); + return new String(os.toByteArray(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Unexpected exception when working with small in-memory streams", e); + } + } + + // public for testing + /** + * Unpacks a base64 encoded pair of a version tag and String payload. + */ + public static Tuple unpackVersionAndPayload(String encodedPack) throws IOException { + final byte[] bytes = encodedPack.getBytes(StandardCharsets.UTF_8); + try (StreamInput in = new InputStreamStreamInput(Base64.getDecoder().wrap(new ByteArrayInputStream(bytes)), bytes.length)) { + final Version version = Version.readVersion(in); + in.setVersion(version); + final String payload = in.readString(); + return new Tuple(version, payload); + } + } + private void ensureEncryptionCiphersSupported() throws NoSuchPaddingException, NoSuchAlgorithmException { Cipher.getInstance(ENCRYPTION_CIPHER); SecretKeyFactory.getInstance(KDF_ALGORITHM); @@ -1344,6 +1551,30 @@ public final class TokenService { return cipher; } + private void getKeyAsync(BytesKey decodedSalt, KeyAndCache keyAndCache, ActionListener listener) { + final SecretKey decodeKey = keyAndCache.getKey(decodedSalt); + if (decodeKey != null) { + listener.onResponse(decodeKey); + } else { + /* As a measure of protected against DOS, we can pass requests requiring a key + * computation off to a single thread executor. For normal usage, the initial + * request(s) that require a key computation will be delayed and there will be + * some additional latency. + */ + client.threadPool().executor(THREAD_POOL_NAME) + .submit(new KeyComputingRunnable(decodedSalt, keyAndCache, listener)); + } + } + + private static String decryptTokenId(byte[] encryptedTokenId, Cipher cipher, Version version) throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(encryptedTokenId); + CipherInputStream cis = new CipherInputStream(bais, cipher); + StreamInput decryptedInput = new InputStreamStreamInput(cis)) { + decryptedInput.setVersion(version); + return decryptedInput.readString(); + } + } + private Cipher getDecryptionCipher(byte[] iv, SecretKey key, Version version, BytesKey salt) throws GeneralSecurityException { Cipher cipher = Cipher.getInstance(ENCRYPTION_CIPHER); cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv), secureRandom); @@ -1838,16 +2069,17 @@ public final class TokenService { private final String associatedRealm; private final boolean refreshed; @Nullable private final Instant refreshInstant; - @Nullable private final String supersededByDocId; + @Nullable private final String supersededBy; + private Version version; private RefreshTokenStatus(boolean invalidated, String associatedUser, String associatedRealm, boolean refreshed, - Instant refreshInstant, String supersededByDocId) { + Instant refreshInstant, String supersededBy) { this.invalidated = invalidated; this.associatedUser = associatedUser; this.associatedRealm = associatedRealm; this.refreshed = refreshed; this.refreshInstant = refreshInstant; - this.supersededByDocId = supersededByDocId; + this.supersededBy = supersededBy; } boolean isInvalidated() { @@ -1870,8 +2102,16 @@ public final class TokenService { return refreshInstant; } - @Nullable String getSupersedingDocId() { - return supersededByDocId; + @Nullable String getSupersededBy() { + return supersededBy; + } + + Version getVersion() { + return version; + } + + void setVersion(Version version) { + this.version = version; } static RefreshTokenStatus fromSourceMap(Map refreshTokenSource) { 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 a0c579dd881..0270e31216c 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 @@ -66,7 +66,7 @@ import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; 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.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; /** * NativeUsersStore is a store for users that reads from an Elasticsearch index. This store is responsible for fetching the full @@ -146,7 +146,7 @@ public class NativeUsersStore { } final Supplier supplier = client.threadPool().getThreadContext().newRestorableContext(false); try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) { - SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) + SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(query) .setSize(1000) @@ -171,7 +171,7 @@ public class NativeUsersStore { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareSearch(SECURITY_INDEX_NAME) + client.prepareSearch(SECURITY_MAIN_ALIAS) .setQuery(QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), USER_DOC_TYPE)) .setSize(0) .setTrackTotalHits(true) @@ -205,7 +205,7 @@ public class NativeUsersStore { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, user)).request(), + client.prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, user)).request(), new ActionListener() { @Override public void onResponse(GetResponse response) { @@ -245,7 +245,7 @@ public class NativeUsersStore { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(docType, username)) + client.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(docType, username)) .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.PASSWORD.getPreferredName(), String.valueOf(request.passwordHash())) .setRefreshPolicy(request.getRefreshPolicy()).request(), @@ -283,7 +283,7 @@ public class NativeUsersStore { private void createReservedUser(String username, char[] passwordHash, RefreshPolicy refresh, ActionListener listener) { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) + client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) .setSource(Fields.PASSWORD.getPreferredName(), String.valueOf(passwordHash), Fields.ENABLED.getPreferredName(), true, Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE) .setRefreshPolicy(refresh).request(), @@ -323,7 +323,7 @@ public class NativeUsersStore { // We must have an existing document securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) + client.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.USERNAME.getPreferredName(), putUserRequest.username(), Fields.ROLES.getPreferredName(), putUserRequest.roles(), @@ -367,7 +367,7 @@ public class NativeUsersStore { assert putUserRequest.passwordHash() != null; securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) + client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, putUserRequest.username())) .setSource(Fields.USERNAME.getPreferredName(), putUserRequest.username(), Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), Fields.ROLES.getPreferredName(), putUserRequest.roles(), @@ -410,7 +410,7 @@ public class NativeUsersStore { final ActionListener listener) { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, username)) + client.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, username)) .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) .setRefreshPolicy(refreshPolicy) .request(), @@ -444,7 +444,7 @@ public class NativeUsersStore { boolean clearCache, final ActionListener listener) { securityIndex.prepareIndexIfNeededThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) + client.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) .setDoc(Requests.INDEX_CONTENT_TYPE, Fields.ENABLED.getPreferredName(), enabled) .setUpsert(XContentType.JSON, Fields.PASSWORD.getPreferredName(), "", @@ -479,7 +479,7 @@ public class NativeUsersStore { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { DeleteRequest request = client - .prepareDelete(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())) + .prepareDelete(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(USER_DOC_TYPE, deleteUserRequest.username())) .request(); request.setRefreshPolicy(deleteUserRequest.getRefreshPolicy()); executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, @@ -526,7 +526,7 @@ public class NativeUsersStore { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) + client.prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForUser(RESERVED_USER_TYPE, username)) .request(), new ActionListener() { @Override @@ -571,7 +571,7 @@ public class NativeUsersStore { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareSearch(SECURITY_INDEX_NAME) + client.prepareSearch(SECURITY_MAIN_ALIAS) .setTrackTotalHits(true) .setQuery(QueryBuilders.termQuery(Fields.TYPE.getPreferredName(), RESERVED_USER_TYPE)) .setFetchSource(true).request(), 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 ab860ed058d..1b6da7f68ca 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 @@ -35,6 +35,7 @@ import org.elasticsearch.xpack.core.security.action.rolemapping.PutRoleMappingRe import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel; import org.elasticsearch.xpack.core.security.client.SecurityClient; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.authc.support.CachingRealm; import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; @@ -60,13 +61,13 @@ import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; 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.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isIndexDeleted; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.isMoveFromRedToNonRed; /** * This store reads + writes {@link ExpressionRoleMapping role mappings} in an Elasticsearch - * {@link SecurityIndexManager#SECURITY_INDEX_NAME index}. + * {@link RestrictedIndicesNames#SECURITY_MAIN_ALIAS index}. *
* The store is responsible for all read and write operations as well as * {@link #resolveRoles(UserData, ActionListener) resolving roles}. @@ -131,7 +132,7 @@ public class NativeRoleMappingStore implements UserRoleMapper { final QueryBuilder query = QueryBuilders.termQuery(DOC_TYPE_FIELD, DOC_TYPE_ROLE_MAPPING); final Supplier supplier = client.threadPool().getThreadContext().newRestorableContext(false); try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) { - SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) + SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(query) .setSize(1000) @@ -143,7 +144,7 @@ public class NativeRoleMappingStore implements UserRoleMapper { listener.onResponse(mappings.stream().filter(Objects::nonNull).collect(Collectors.toList())), ex -> { logger.error(new ParameterizedMessage("failed to load role mappings from index [{}] skipping all mappings.", - SECURITY_INDEX_NAME), ex); + SECURITY_MAIN_ALIAS), ex); listener.onResponse(Collections.emptyList()); })), doc -> buildMapping(getNameFromId(doc.getId()), doc.getSourceRef())); @@ -202,7 +203,7 @@ public class NativeRoleMappingStore implements UserRoleMapper { return; } executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForName(mapping.getName())) + client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForName(mapping.getName())) .setSource(xContentBuilder) .setRefreshPolicy(request.getRefreshPolicy()) .request(), @@ -231,7 +232,7 @@ public class NativeRoleMappingStore implements UserRoleMapper { } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareDelete(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForName(request.getName())) + client.prepareDelete(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForName(request.getName())) .setRefreshPolicy(request.getRefreshPolicy()) .request(), new ActionListener() { @@ -286,7 +287,7 @@ public class NativeRoleMappingStore implements UserRoleMapper { logger.info("The security index is not yet available - no role mappings can be loaded"); if (logger.isDebugEnabled()) { logger.debug("Security Index [{}] [exists: {}] [available: {}] [mapping up to date: {}]", - SECURITY_INDEX_NAME, + SECURITY_MAIN_ALIAS, securityIndex.indexExists(), securityIndex.isAvailable(), securityIndex.isMappingUpToDate() 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 6a29f1de0e1..7e70f6116ce 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 @@ -65,7 +65,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; 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.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; /** * {@code NativePrivilegeStore} is a store that reads/writes {@link ApplicationPrivilegeDescriptor} objects, @@ -129,7 +129,7 @@ public class NativePrivilegeStore { } final Supplier supplier = client.threadPool().getThreadContext().newRestorableContext(false); try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) { - SearchRequest request = client.prepareSearch(SECURITY_INDEX_NAME) + SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(query) .setSize(1000) @@ -201,7 +201,7 @@ public class NativePrivilegeStore { } else { securityIndexManager.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, toDocId(application, name)) + client.prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, toDocId(application, name)) .request(), new ActionListener() { @Override @@ -253,7 +253,7 @@ public class NativePrivilegeStore { final String name = privilege.getName(); final XContentBuilder xContentBuilder = privilege.toXContent(jsonBuilder(), true); ClientHelper.executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, toDocId(privilege.getApplication(), name)) + client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, toDocId(privilege.getApplication(), name)) .setSource(xContentBuilder) .setRefreshPolicy(refreshPolicy) .request(), listener, client::index); @@ -284,7 +284,7 @@ public class NativePrivilegeStore { }, listener::onFailure), names.size()); for (String name : names) { ClientHelper.executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareDelete(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, toDocId(application, name)) + client.prepareDelete(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, toDocId(application, name)) .setRefreshPolicy(refreshPolicy) .request(), groupListener, client::delete); } 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 8bd4f9cc898..4554e9e8e82 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 @@ -70,7 +70,7 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; import static org.elasticsearch.xpack.core.security.SecurityField.setting; import static org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ROLE_TYPE; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; /** * NativeRolesStore is a {@code RolesStore} that, instead of reading from a @@ -124,7 +124,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< QueryBuilder query = QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE); final Supplier supplier = client.threadPool().getThreadContext().newRestorableContext(false); try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) { - SearchRequest request = client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS) .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings)) .setQuery(query) .setSize(1000) @@ -142,7 +142,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { final String[] roleIds = names.stream().map(NativeRolesStore::getIdForRole).toArray(String[]::new); - MultiGetRequest multiGetRequest = client.prepareMultiGet().add(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, roleIds).request(); + MultiGetRequest multiGetRequest = client.prepareMultiGet().add(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, roleIds).request(); executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, multiGetRequest, ActionListener.wrap(mGetResponse -> { final MultiGetItemResponse[] responses = mGetResponse.getResponses(); @@ -179,7 +179,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< } else { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> { DeleteRequest request = client - .prepareDelete(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForRole(deleteRoleRequest.name())).request(); + .prepareDelete(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForRole(deleteRoleRequest.name())).request(); request.setRefreshPolicy(deleteRoleRequest.getRefreshPolicy()); executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, request, new ActionListener() { @@ -219,7 +219,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< listener.onFailure(e); return; } - final IndexRequest indexRequest = client.prepareIndex(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForRole(role.getName())) + final IndexRequest indexRequest = client.prepareIndex(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForRole(role.getName())) .setSource(xContentBuilder) .setRefreshPolicy(request.getRefreshPolicy()) .request(); @@ -253,11 +253,11 @@ public class NativeRolesStore implements BiConsumer, ActionListener< securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, client.prepareMultiSearch() - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .add(client.prepareSearch(SECURITY_MAIN_ALIAS) .setQuery(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) .setTrackTotalHits(true) .setSize(0)) - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .add(client.prepareSearch(SECURITY_MAIN_ALIAS) .setQuery(QueryBuilders.boolQuery() .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) .must(QueryBuilders.boolQuery() @@ -268,7 +268,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< .setTrackTotalHits(true) .setSize(0) .setTerminateAfter(1)) - .add(client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + .add(client.prepareSearch(SECURITY_MAIN_ALIAS) .setQuery(QueryBuilders.boolQuery() .must(QueryBuilders.termQuery(RoleDescriptor.Fields.TYPE.getPreferredName(), ROLE_TYPE)) .filter(existsQuery("indices.query"))) @@ -340,7 +340,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< private void executeGetRoleRequest(String role, ActionListener listener) { securityIndex.checkIndexVersionThenExecute(listener::onFailure, () -> executeAsyncWithOrigin(client.threadPool().getThreadContext(), SECURITY_ORIGIN, - client.prepareGet(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, getIdForRole(role)).request(), + client.prepareGet(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, getIdForRole(role)).request(), listener, client::get)); } 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 31cf1f1568f..a96693c4556 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 @@ -53,6 +53,7 @@ import org.elasticsearch.xpack.core.template.TemplateUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.time.Instant; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -77,12 +78,13 @@ import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; */ public class SecurityIndexManager implements ClusterStateListener { - public static final String INTERNAL_SECURITY_INDEX = RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7; - public static final int INTERNAL_INDEX_FORMAT = 6; + public static final int INTERNAL_MAIN_INDEX_FORMAT = 6; + public static final int INTERNAL_TOKENS_INDEX_FORMAT = 7; + public static final String SECURITY_MAIN_TEMPLATE_7 = "security-index-template-7"; + public static final String SECURITY_TOKENS_TEMPLATE_7 = "security-tokens-index-template-7"; public static final String SECURITY_VERSION_STRING = "security-version"; public static final String TEMPLATE_VERSION_PATTERN = Pattern.quote("${security.template.version}"); - public static final String SECURITY_TEMPLATE_NAME = "security-index-template"; - public static final String SECURITY_INDEX_NAME = ".security"; + private static final Logger logger = LogManager.getLogger(SecurityIndexManager.class); private final String aliasName; @@ -95,19 +97,26 @@ public class SecurityIndexManager implements ClusterStateListener { private volatile State indexState; - public static SecurityIndexManager buildSecurityIndexManager(Client client, ClusterService clusterService) { - return new SecurityIndexManager(client, SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX, INTERNAL_INDEX_FORMAT, - SecurityIndexManager::readSecurityTemplateAsBytes, clusterService); + public static SecurityIndexManager buildSecurityMainIndexManager(Client client, ClusterService clusterService) { + return new SecurityIndexManager(client, clusterService, RestrictedIndicesNames.SECURITY_MAIN_ALIAS, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, INTERNAL_MAIN_INDEX_FORMAT, + () -> SecurityIndexManager.readTemplateAsBytes(SECURITY_MAIN_TEMPLATE_7)); } - private SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat, - Supplier mappingSourceSupplier, ClusterService clusterService) { + public static SecurityIndexManager buildSecurityTokensIndexManager(Client client, ClusterService clusterService) { + return new SecurityIndexManager(client, clusterService, RestrictedIndicesNames.SECURITY_TOKENS_ALIAS, + RestrictedIndicesNames.INTERNAL_SECURITY_TOKENS_INDEX_7, INTERNAL_TOKENS_INDEX_FORMAT, + () -> SecurityIndexManager.readTemplateAsBytes(SECURITY_TOKENS_TEMPLATE_7)); + } + + private SecurityIndexManager(Client client, ClusterService clusterService, String aliasName, String internalIndexName, + int internalIndexFormat, Supplier mappingSourceSupplier) { this(client, aliasName, internalIndexName, internalIndexFormat, mappingSourceSupplier, State.UNRECOVERED_STATE); clusterService.addListener(this); } private SecurityIndexManager(Client client, String aliasName, String internalIndexName, int internalIndexFormat, - Supplier mappingSourceSupplier, State indexState) { + Supplier mappingSourceSupplier, State indexState) { this.aliasName = aliasName; this.internalIndexName = internalIndexName; this.internalIndexFormat = internalIndexFormat; @@ -126,8 +135,16 @@ public class SecurityIndexManager implements ClusterStateListener { return currentIndexState.mappingVersion == null || requiredVersion.test(currentIndexState.mappingVersion); } + public String aliasName() { + return aliasName; + } + public boolean indexExists() { - return this.indexState.indexExists; + return this.indexState.indexExists(); + } + + public Instant getCreationTime() { + return this.indexState.creationTime; } /** @@ -156,7 +173,7 @@ public class SecurityIndexManager implements ClusterStateListener { throw new IllegalStateException("caller must make sure to use a frozen state and check indexAvailable"); } - if (localState.indexExists) { + if (localState.indexExists()) { return new UnavailableShardsException(null, "at least one primary shard for the index [" + localState.concreteIndexName + "] is unavailable"); } else { @@ -183,17 +200,17 @@ public class SecurityIndexManager implements ClusterStateListener { } final State previousState = indexState; final IndexMetaData indexMetaData = resolveConcreteIndex(aliasName, event.state().metaData()); - final boolean indexExists = indexMetaData != null; - final boolean isIndexUpToDate = indexExists == false || + final Instant creationTime = indexMetaData != null ? Instant.ofEpochMilli(indexMetaData.getCreationDate()) : null; + final boolean isIndexUpToDate = indexMetaData == null || INDEX_FORMAT_SETTING.get(indexMetaData.getSettings()).intValue() == internalIndexFormat; final boolean indexAvailable = checkIndexAvailable(event.state()); - final boolean mappingIsUpToDate = indexExists == false || checkIndexMappingUpToDate(event.state()); + final boolean mappingIsUpToDate = indexMetaData == null || checkIndexMappingUpToDate(event.state()); final Version mappingVersion = oldestIndexMappingVersion(event.state()); final ClusterHealthStatus indexStatus = indexMetaData == null ? null : new ClusterIndexHealth(indexMetaData, event.state().getRoutingTable().index(indexMetaData.getIndex())).getStatus(); final String concreteIndexName = indexMetaData == null ? internalIndexName : indexMetaData.getIndex().getName(); - final State newState = new State(indexExists, isIndexUpToDate, indexAvailable, mappingIsUpToDate, mappingVersion, concreteIndexName, - indexStatus); + final State newState = new State(creationTime, isIndexUpToDate, indexAvailable, mappingIsUpToDate, mappingVersion, + concreteIndexName, indexStatus); this.indexState = newState; if (newState.equals(previousState) == false) { @@ -304,7 +321,7 @@ public class SecurityIndexManager implements ClusterStateListener { */ public void checkIndexVersionThenExecute(final Consumer consumer, final Runnable andThen) { final State indexState = this.indexState; // use a local copy so all checks execute against the same state! - if (indexState.indexExists && indexState.isIndexUpToDate == false) { + if (indexState.indexExists() && indexState.isIndexUpToDate == false) { consumer.accept(new IllegalStateException( "Index [" + indexState.concreteIndexName + "] is not on the current version. Security features relying on the index" + " will not be available until the upgrade API is run on the index")); @@ -324,11 +341,11 @@ public class SecurityIndexManager implements ClusterStateListener { consumer.accept(new ElasticsearchStatusException( "Cluster state has not been recovered yet, cannot write to the [" + indexState.concreteIndexName + "] index", RestStatus.SERVICE_UNAVAILABLE)); - } else if (indexState.indexExists && indexState.isIndexUpToDate == false) { + } else if (indexState.indexExists() && indexState.isIndexUpToDate == false) { consumer.accept(new IllegalStateException( "Index [" + indexState.concreteIndexName + "] is not on the current version." + "Security features relying on the index will not be available until the upgrade API is run on the index")); - } else if (indexState.indexExists == false) { + } else if (indexState.indexExists() == false) { assert indexState.concreteIndexName != null; logger.info("security index does not exist. Creating [{}] with alias [{}]", indexState.concreteIndexName, this.aliasName); final byte[] mappingSource = mappingSourceSupplier.get(); @@ -396,8 +413,8 @@ public class SecurityIndexManager implements ClusterStateListener { return previousState.indexStatus != null && currentState.indexStatus == null; } - private static byte[] readSecurityTemplateAsBytes() { - return TemplateUtils.loadTemplate("/" + SECURITY_TEMPLATE_NAME + ".json", Version.CURRENT.toString(), + private static byte[] readTemplateAsBytes(String templateName) { + return TemplateUtils.loadTemplate("/" + templateName + ".json", Version.CURRENT.toString(), SecurityIndexManager.TEMPLATE_VERSION_PATTERN).getBytes(StandardCharsets.UTF_8); } @@ -423,8 +440,8 @@ public class SecurityIndexManager implements ClusterStateListener { * State of the security index. */ public static class State { - public static final State UNRECOVERED_STATE = new State(false, false, false, false, null, null, null); - public final boolean indexExists; + public static final State UNRECOVERED_STATE = new State(null, false, false, false, null, null, null); + public final Instant creationTime; public final boolean isIndexUpToDate; public final boolean indexAvailable; public final boolean mappingUpToDate; @@ -432,9 +449,9 @@ public class SecurityIndexManager implements ClusterStateListener { public final String concreteIndexName; public final ClusterHealthStatus indexStatus; - public State(boolean indexExists, boolean isIndexUpToDate, boolean indexAvailable, + public State(Instant creationTime, boolean isIndexUpToDate, boolean indexAvailable, boolean mappingUpToDate, Version mappingVersion, String concreteIndexName, ClusterHealthStatus indexStatus) { - this.indexExists = indexExists; + this.creationTime = creationTime; this.isIndexUpToDate = isIndexUpToDate; this.indexAvailable = indexAvailable; this.mappingUpToDate = mappingUpToDate; @@ -448,7 +465,7 @@ public class SecurityIndexManager implements ClusterStateListener { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; State state = (State) o; - return indexExists == state.indexExists && + return Objects.equals(creationTime, state.creationTime) && isIndexUpToDate == state.isIndexUpToDate && indexAvailable == state.indexAvailable && mappingUpToDate == state.mappingUpToDate && @@ -457,9 +474,13 @@ public class SecurityIndexManager implements ClusterStateListener { indexStatus == state.indexStatus; } + public boolean indexExists() { + return creationTime != null; + } + @Override public int hashCode() { - return Objects.hash(indexExists, isIndexUpToDate, indexAvailable, mappingUpToDate, mappingVersion, concreteIndexName, + return Objects.hash(creationTime, isIndexUpToDate, indexAvailable, mappingUpToDate, mappingVersion, concreteIndexName, indexStatus); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java index c7f5123c6a1..6d7eacfe26c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java @@ -13,8 +13,8 @@ import org.elasticsearch.xpack.core.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.core.security.action.role.PutRoleResponse; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; import org.elasticsearch.xpack.core.security.client.SecurityClient; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.Before; import org.junit.BeforeClass; @@ -56,7 +56,7 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { logger.debug("--> created role [{}]", role); } - ensureGreen(SecurityIndexManager.SECURITY_INDEX_NAME); + ensureGreen(RestrictedIndicesNames.SECURITY_MAIN_ALIAS); final Set rolesSet = new HashSet<>(Arrays.asList(roles)); // warm up the caches on every node diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java index 63f5ace5352..671a94452fa 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/NativeRealmIntegTestCase.java @@ -59,7 +59,7 @@ public abstract class NativeRealmIntegTestCase extends SecurityIntegTestCase { @Override public Set excludeTemplates() { Set templates = Sets.newHashSet(super.excludeTemplates()); - templates.add(SecurityIndexManager.SECURITY_TEMPLATE_NAME); // don't remove the security index template + templates.add(SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7); // don't remove the security index template return templates; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index c3e3bddf10e..bd9d58e6ea5 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -67,7 +67,7 @@ import static org.elasticsearch.test.SecuritySettingsSourceField.TEST_PASSWORD_S import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsCollectionContaining.hasItem; @@ -491,7 +491,7 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint().startObject(); assertTrue("security index mapping not sufficient to read:\n" + Strings.toString(clusterState.toXContent(builder, ToXContent.EMPTY_PARAMS).endObject()), - SecurityIndexManager.checkIndexMappingVersionMatches(SECURITY_INDEX_NAME, clusterState, logger, + SecurityIndexManager.checkIndexMappingVersionMatches(SECURITY_MAIN_ALIAS, clusterState, logger, Version.CURRENT.minimumIndexCompatibilityVersion()::onOrBefore)); Index securityIndex = resolveSecurityIndex(clusterState.metaData()); if (securityIndex != null) { @@ -509,7 +509,7 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); GetIndexRequest getIndexRequest = new GetIndexRequest(); - getIndexRequest.indices(SECURITY_INDEX_NAME); + getIndexRequest.indices(SECURITY_MAIN_ALIAS); getIndexRequest.indicesOptions(IndicesOptions.lenientExpandOpen()); GetIndexResponse getIndexResponse = client.admin().indices().getIndex(getIndexRequest).actionGet(); if (getIndexResponse.getIndices().length > 0) { @@ -520,7 +520,7 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { } private static Index resolveSecurityIndex(MetaData metaData) { - final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(SECURITY_INDEX_NAME); + final AliasOrIndex aliasOrIndex = metaData.getAliasAndIndexLookup().get(SECURITY_MAIN_ALIAS); if (aliasOrIndex != null) { return aliasOrIndex.getIndices().get(0).getIndex(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 87e1c73b978..47484dcbce2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -68,8 +68,8 @@ import java.util.stream.Collectors; import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING; import static org.elasticsearch.discovery.DiscoveryModule.ZEN2_DISCOVERY_TYPE; import static org.elasticsearch.discovery.DiscoveryModule.ZEN_DISCOVERY_TYPE; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_INDEX_FORMAT; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; @@ -310,8 +310,8 @@ public class SecurityTests extends ESTestCase { BiConsumer joinValidator = security.getJoinValidator(); assertNotNull(joinValidator); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); - IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME) - .settings(settings(Version.V_6_1_0).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_INDEX_FORMAT - 1)) + IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_MAIN_ALIAS) + .settings(settings(Version.V_6_1_0).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_MAIN_INDEX_FORMAT - 1)) .numberOfShards(1).numberOfReplicas(0) .build(); DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), Version.V_6_1_0); @@ -330,8 +330,8 @@ public class SecurityTests extends ESTestCase { BiConsumer joinValidator = security.getJoinValidator(); assertNotNull(joinValidator); DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); - int indexFormat = randomBoolean() ? INTERNAL_INDEX_FORMAT : INTERNAL_INDEX_FORMAT - 1; - IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME) + int indexFormat = randomBoolean() ? INTERNAL_MAIN_INDEX_FORMAT : INTERNAL_MAIN_INDEX_FORMAT - 1; + IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_MAIN_ALIAS) .settings(settings(Version.V_6_1_0).put(INDEX_FORMAT_SETTING.getKey(), indexFormat)) .numberOfShards(1).numberOfReplicas(0) .build(); @@ -349,8 +349,8 @@ public class SecurityTests extends ESTestCase { assertNotNull(joinValidator); Version version = randomBoolean() ? Version.CURRENT : Version.V_6_1_0; DiscoveryNode node = new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT); - IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_INDEX_NAME) - .settings(settings(version).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_INDEX_FORMAT)) + IndexMetaData indexMetaData = IndexMetaData.builder(SECURITY_MAIN_ALIAS) + .settings(settings(version).put(INDEX_FORMAT_SETTING.getKey(), INTERNAL_MAIN_INDEX_FORMAT)) .numberOfShards(1).numberOfReplicas(0) .build(); DiscoveryNode existingOtherNode = new DiscoveryNode("bar", buildNewFakeTransportAddress(), version); 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 ddf17421099..e31ccc67332 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 @@ -167,7 +167,7 @@ public class TransportOpenIdConnectLogoutActionTests extends OpenIdConnectTestCa when(securityIndex.isAvailable()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); 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 de0463ffcaa..63c58c5ce10 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 @@ -195,10 +195,13 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase { }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); when(securityIndex.isAvailable()).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); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); @@ -308,7 +311,8 @@ public class TransportSamlInvalidateSessionActionTests extends SamlTestCase { assertThat(filter1.get(1), instanceOf(TermQueryBuilder.class)); assertThat(((TermQueryBuilder) filter1.get(1)).fieldName(), equalTo("refresh_token.token")); - assertThat(((TermQueryBuilder) filter1.get(1)).value(), equalTo(tokenToInvalidate1.v2())); + assertThat(((TermQueryBuilder) filter1.get(1)).value(), + equalTo(TokenService.unpackVersionAndPayload(tokenToInvalidate1.v2()).v2())); assertThat(bulkRequests.size(), equalTo(4)); // 4 updates (refresh-token + access-token) // Invalidate refresh token 1 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 22b9fb7a111..bd1a20db2f1 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 @@ -203,7 +203,7 @@ public class TransportSamlLogoutActionTests extends SamlTestCase { when(securityIndex.isAvailable()).thenReturn(true); final ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); final TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java index fb139922047..cea1a532e13 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/token/TransportCreateTokenActionTests.java @@ -147,7 +147,8 @@ public class TransportCreateTokenActionTests extends ESTestCase { } public void testClientCredentialsCreatesWithoutRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, clusterService); + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex, + clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); @@ -171,7 +172,8 @@ public class TransportCreateTokenActionTests extends ESTestCase { } public void testPasswordGrantTypeCreatesWithRefreshToken() throws Exception { - final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, clusterService); + final TokenService tokenService = new TokenService(SETTINGS, Clock.systemUTC(), client, securityIndex, securityIndex, + clusterService); Authentication authentication = new Authentication(new User("joe"), new Authentication.RealmRef("realm", "type", "node"), null); authentication.writeToContext(threadPool.getThreadContext()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java index 256bf6d9df5..f6849cae4c1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java @@ -34,7 +34,6 @@ import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.client.SecurityClient; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.junit.After; import org.junit.Before; @@ -53,7 +52,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -336,7 +335,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase { Instant dayBefore = created.minus(1L, ChronoUnit.DAYS); assertTrue(Instant.now().isAfter(dayBefore)); UpdateResponse expirationDateUpdatedResponse = client - .prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, createdApiKeys.get(0).getId()) + .prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, createdApiKeys.get(0).getId()) .setDoc("expiration_time", dayBefore.toEpochMilli()) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .get(); @@ -346,7 +345,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase { // hack doc to modify the expiration time to the week before Instant weekBefore = created.minus(8L, ChronoUnit.DAYS); assertTrue(Instant.now().isAfter(weekBefore)); - expirationDateUpdatedResponse = client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, createdApiKeys.get(1).getId()) + expirationDateUpdatedResponse = client.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, createdApiKeys.get(1).getId()) .setDoc("expiration_time", weekBefore.toEpochMilli()) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .get(); @@ -390,8 +389,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase { private void refreshSecurityIndex() throws Exception { assertBusy(() -> { - final RefreshResponse refreshResponse = client().admin().indices().prepareRefresh(SecurityIndexManager.SECURITY_INDEX_NAME) - .get(); + final RefreshResponse refreshResponse = client().admin().indices().prepareRefresh(SECURITY_MAIN_ALIAS).get(); assertThat(refreshResponse.getFailedShards(), is(0)); }); } 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 63e8578e8c2..a86edb98251 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 @@ -154,7 +154,7 @@ public class AuthenticationServiceTests extends ESTestCase { @SuppressForbidden(reason = "Allow accessing localhost") public void init() throws Exception { concreteSecurityIndexName = randomFrom( - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); token = mock(AuthenticationToken.class); when(token.principal()).thenReturn(randomAlphaOfLength(5)); @@ -220,7 +220,7 @@ public class AuthenticationServiceTests extends ESTestCase { }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); ClusterService clusterService = ClusterServiceUtils.createClusterService(threadPool); apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, securityIndex, clusterService, threadPool); - tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, clusterService); + tokenService = new TokenService(settings, Clock.systemUTC(), client, securityIndex, securityIndex, clusterService); service = new AuthenticationService(settings, realms, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), tokenService, apiKeyService); } @@ -1394,6 +1394,6 @@ public class AuthenticationServiceTests extends ESTestCase { } private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { - return new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, indexStatus); + return new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, indexStatus); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java index 6bcdfc94e99..7bac18cfcfb 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/TokenAuthIntegTests.java @@ -36,7 +36,7 @@ import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; import org.elasticsearch.xpack.core.security.authc.TokenMetaData; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.client.SecurityClient; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.junit.After; import org.junit.Before; @@ -55,7 +55,6 @@ import java.util.stream.Collectors; import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; import static org.hamcrest.Matchers.equalTo; @TestLogging("org.elasticsearch.xpack.security.authz.store.FileRolesStore:DEBUG") @@ -161,7 +160,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { assertThat(invalidateResponse.getResult().getErrors().size(), equalTo(0)); AtomicReference docId = new AtomicReference<>(); assertBusy(() -> { - SearchResponse searchResponse = client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + SearchResponse searchResponse = client.prepareSearch(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS) .setSource(SearchSourceBuilder.searchSource() .query(QueryBuilders.termQuery("doc_type", "token"))) .setSize(1) @@ -174,7 +173,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { // hack doc to modify the creation time to the day before Instant yesterday = created.minus(36L, ChronoUnit.HOURS); assertTrue(Instant.now().isAfter(yesterday)); - client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, docId.get()) + client.prepareUpdate(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS, SINGLE_MAPPING_NAME, docId.get()) .setDoc("creation_time", yesterday.toEpochMilli()) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .get(); @@ -192,8 +191,8 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { assertEquals("token malformed", e.getMessage()); } } - client.admin().indices().prepareRefresh(SecurityIndexManager.SECURITY_INDEX_NAME).get(); - SearchResponse searchResponse = client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + client.admin().indices().prepareRefresh(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS).get(); + SearchResponse searchResponse = client.prepareSearch(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS) .setSource(SearchSourceBuilder.searchSource() .query(QueryBuilders.termQuery("doc_type", "token"))) .setTerminateAfter(1) @@ -358,10 +357,10 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { // We now have two documents, the original(now refreshed) token doc and the new one with the new access doc AtomicReference docId = new AtomicReference<>(); assertBusy(() -> { - SearchResponse searchResponse = client.prepareSearch(SecurityIndexManager.SECURITY_INDEX_NAME) + SearchResponse searchResponse = client.prepareSearch(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS) .setSource(SearchSourceBuilder.searchSource() .query(QueryBuilders.boolQuery() - .must(QueryBuilders.termQuery("doc_type", "token")) + .must(QueryBuilders.termQuery("doc_type", TokenService.TOKEN_DOC_TYPE)) .must(QueryBuilders.termQuery("refresh_token.refreshed", "true")))) .setSize(1) .setTerminateAfter(1) @@ -374,7 +373,7 @@ public class TokenAuthIntegTests extends SecurityIntegTestCase { Instant refreshed = Instant.now(); Instant aWhileAgo = refreshed.minus(50L, ChronoUnit.SECONDS); assertTrue(Instant.now().isAfter(aWhileAgo)); - UpdateResponse updateResponse = client.prepareUpdate(SECURITY_INDEX_NAME, SINGLE_MAPPING_NAME, docId.get()) + UpdateResponse updateResponse = client.prepareUpdate(RestrictedIndicesNames.SECURITY_TOKENS_ALIAS, SINGLE_MAPPING_NAME, docId.get()) .setDoc("refresh_token", Collections.singletonMap("refresh_time", aWhileAgo.toEpochMilli())) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .setFetchSource("refresh_token", Strings.EMPTY_STRING) 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 3d050bd2af7..494b8070c57 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 @@ -21,6 +21,9 @@ import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.action.update.UpdateRequestBuilder; import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; import org.elasticsearch.common.UUIDs; @@ -51,6 +54,7 @@ import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationRes import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.watcher.watch.ClockMock; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -74,6 +78,7 @@ import javax.crypto.SecretKey; import static java.time.Clock.systemUTC; import static org.elasticsearch.repositories.ESBlobStoreTestCase.randomBytes; +import static org.elasticsearch.test.ClusterServiceUtils.setState; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; @@ -92,8 +97,10 @@ public class TokenServiceTests extends ESTestCase { .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build(); private Client client; - private SecurityIndexManager securityIndex; + private SecurityIndexManager securityMainIndex; + private SecurityIndexManager securityTokensIndex; private ClusterService clusterService; + private DiscoveryNode oldNode; private Settings tokenServiceEnabledSettings = Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true).build(); @@ -121,20 +128,21 @@ public class TokenServiceTests extends ESTestCase { }).when(client).execute(eq(IndexAction.INSTANCE), any(IndexRequest.class), any(ActionListener.class)); // setup lifecycle service - securityIndex = mock(SecurityIndexManager.class); - doAnswer(invocationOnMock -> { - Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; - runnable.run(); - return null; - }).when(securityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); - doAnswer(invocationOnMock -> { - Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; - runnable.run(); - return null; - }).when(securityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); - when(securityIndex.indexExists()).thenReturn(true); - when(securityIndex.isAvailable()).thenReturn(true); + this.securityMainIndex = mockSecurityManager(); + this.securityTokensIndex = mockSecurityManager(); this.clusterService = ClusterServiceUtils.createClusterService(threadPool); + // version 7.1 was an "inflection" point in the Token Service development (access_tokens as UUIDS, multiple concurrent refreshes, + // tokens docs on a separate index), let's test the TokenService works in a mixed cluster with nodes with versions prior to these + // developments + if (randomBoolean()) { + oldNode = addAnotherDataNodeWithVersion(this.clusterService, randomFrom(Version.V_6_7_0, Version.V_7_0_0)); + } + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + clusterService.close(); } @BeforeClass @@ -151,7 +159,8 @@ public class TokenServiceTests extends ESTestCase { } public void testAttachAndGetToken() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -172,8 +181,8 @@ public class TokenServiceTests extends ESTestCase { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // verify a second separate token service with its own salt can also verify - TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex - , clusterService); + TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); anotherService.refreshMetaData(tokenService.getTokenMetaData()); PlainActionFuture future = new PlainActionFuture<>(); anotherService.getAndValidateToken(requestContext, future); @@ -183,7 +192,8 @@ public class TokenServiceTests extends ESTestCase { } public void testInvalidAuthorizationHeader() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); String token = randomFrom("", " "); String authScheme = randomFrom("Bearer ", "BEARER ", "bearer ", "Basic "); @@ -198,7 +208,8 @@ public class TokenServiceTests extends ESTestCase { } public void testRotateKey() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -251,12 +262,14 @@ public class TokenServiceTests extends ESTestCase { } public void testKeyExchange() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); int numRotations = randomIntBetween(1, 5); for (int i = 0; i < numRotations; i++) { rotateKeys(tokenService); } - TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService otherTokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); otherTokenService.refreshMetaData(tokenService.getTokenMetaData()); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); @@ -272,7 +285,7 @@ public class TokenServiceTests extends ESTestCase { PlainActionFuture future = new PlainActionFuture<>(); otherTokenService.getAndValidateToken(requestContext, future); UserToken serialized = future.get(); - assertAuthentication(authentication, serialized.getAuthentication()); + assertEquals(authentication, serialized.getAuthentication()); } rotateKeys(tokenService); @@ -288,7 +301,8 @@ public class TokenServiceTests extends ESTestCase { } public void testPruneKeys() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -350,7 +364,8 @@ public class TokenServiceTests extends ESTestCase { } public void testPassphraseWorks() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -371,8 +386,8 @@ public class TokenServiceTests extends ESTestCase { try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { // verify a second separate token service with its own passphrase cannot verify - TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, - clusterService); + TokenService anotherService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); PlainActionFuture future = new PlainActionFuture<>(); anotherService.getAndValidateToken(requestContext, future); assertNull(future.get()); @@ -380,7 +395,8 @@ public class TokenServiceTests extends ESTestCase { } public void testGetTokenWhenKeyCacheHasExpired() throws Exception { - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); @@ -393,9 +409,9 @@ public class TokenServiceTests extends ESTestCase { } public void testInvalidatedToken() throws Exception { - when(securityIndex.indexExists()).thenReturn(true); - TokenService tokenService = - new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + when(securityMainIndex.indexExists()).thenReturn(true); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -449,7 +465,8 @@ public class TokenServiceTests extends ESTestCase { public void testTokenExpiry() throws Exception { ClockMock clock = ClockMock.frozen(); - TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, clock, client, securityMainIndex, securityTokensIndex, + clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -502,7 +519,7 @@ public class TokenServiceTests extends ESTestCase { TokenService tokenService = new TokenService(Settings.builder() .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), false) .build(), - Clock.systemUTC(), client, securityIndex, clusterService); + Clock.systemUTC(), client, securityMainIndex, securityTokensIndex, clusterService); IllegalStateException e = expectThrows(IllegalStateException.class, () -> tokenService.createOAuth2Tokens(null, null, null, true, null)); assertEquals("tokens are not enabled", e.getMessage()); @@ -545,7 +562,8 @@ public class TokenServiceTests extends ESTestCase { final int numBytes = randomIntBetween(1, TokenService.MINIMUM_BYTES + 32); final byte[] randomBytes = new byte[numBytes]; random().nextBytes(randomBytes); - TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(Settings.EMPTY, systemUTC(), client, securityMainIndex, securityTokensIndex, + clusterService); ThreadContext requestContext = new ThreadContext(Settings.EMPTY); requestContext.putHeader("Authorization", "Bearer " + Base64.getEncoder().encodeToString(randomBytes)); @@ -558,8 +576,8 @@ public class TokenServiceTests extends ESTestCase { } public void testIndexNotAvailable() throws Exception { - TokenService tokenService = - new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); PlainActionFuture> tokenFuture = new PlainActionFuture<>(); tokenService.createOAuth2Tokens(authentication, authentication, Collections.emptyMap(), true, tokenFuture); @@ -576,34 +594,44 @@ public class TokenServiceTests extends ESTestCase { return Void.TYPE; }).when(client).get(any(GetRequest.class), any(ActionListener.class)); + final SecurityIndexManager tokensIndex; + if (oldNode != null) { + tokensIndex = securityMainIndex; + when(securityTokensIndex.isAvailable()).thenReturn(false); + when(securityTokensIndex.indexExists()).thenReturn(false); + } else { + tokensIndex = securityTokensIndex; + when(securityMainIndex.isAvailable()).thenReturn(false); + when(securityMainIndex.indexExists()).thenReturn(false); + } try (ThreadContext.StoredContext ignore = requestContext.newStoredContext(true)) { PlainActionFuture future = new PlainActionFuture<>(); tokenService.getAndValidateToken(requestContext, future); assertNull(future.get()); - when(securityIndex.isAvailable()).thenReturn(false); - when(securityIndex.indexExists()).thenReturn(true); + when(tokensIndex.isAvailable()).thenReturn(false); + when(tokensIndex.indexExists()).thenReturn(true); future = new PlainActionFuture<>(); tokenService.getAndValidateToken(requestContext, future); assertNull(future.get()); - when(securityIndex.indexExists()).thenReturn(false); + when(tokensIndex.indexExists()).thenReturn(false); future = new PlainActionFuture<>(); tokenService.getAndValidateToken(requestContext, future); assertNull(future.get()); - when(securityIndex.isAvailable()).thenReturn(true); - when(securityIndex.indexExists()).thenReturn(true); + when(tokensIndex.isAvailable()).thenReturn(true); + when(tokensIndex.indexExists()).thenReturn(true); mockGetTokenFromId(token, false); future = new PlainActionFuture<>(); tokenService.getAndValidateToken(requestContext, future); - assertEquals(token.getAuthentication(), future.get().getAuthentication()); + assertEquals(future.get().getAuthentication(), token.getAuthentication()); } } public void testGetAuthenticationWorksWithExpiredUserToken() throws Exception { - TokenService tokenService = - new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, securityIndex, clusterService); + TokenService tokenService = new TokenService(tokenServiceEnabledSettings, Clock.systemUTC(), client, securityMainIndex, + securityTokensIndex, clusterService); Authentication authentication = new Authentication(new User("joe", "admin"), new RealmRef("native_realm", "native", "node1"), null); UserToken expired = new UserToken(authentication, Instant.now().minus(3L, ChronoUnit.DAYS)); mockGetTokenFromId(expired, false); @@ -672,4 +700,33 @@ public class TokenServiceTests extends ESTestCase { } } + private SecurityIndexManager mockSecurityManager() { + SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class); + doAnswer(invocationOnMock -> { + Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; + runnable.run(); + return null; + }).when(mockSecurityIndex).prepareIndexIfNeededThenExecute(any(Consumer.class), any(Runnable.class)); + doAnswer(invocationOnMock -> { + Runnable runnable = (Runnable) invocationOnMock.getArguments()[1]; + runnable.run(); + return null; + }).when(mockSecurityIndex).checkIndexVersionThenExecute(any(Consumer.class), any(Runnable.class)); + when(mockSecurityIndex.indexExists()).thenReturn(true); + when(mockSecurityIndex.isAvailable()).thenReturn(true); + return mockSecurityIndex; + } + + private DiscoveryNode addAnotherDataNodeWithVersion(ClusterService clusterService, Version version) { + final ClusterState currentState = clusterService.state(); + final DiscoveryNodes.Builder discoBuilder = DiscoveryNodes.builder(currentState.getNodes()); + final DiscoveryNode anotherDataNode = new DiscoveryNode("another_data_node#" + version, buildNewFakeTransportAddress(), + Collections.emptyMap(), Collections.singleton(DiscoveryNode.Role.DATA), version); + discoBuilder.add(anotherDataNode); + final ClusterState.Builder newStateBuilder = ClusterState.builder(currentState); + newStateBuilder.nodes(discoBuilder); + setState(clusterService, newStateBuilder.build()); + return anotherDataNode; + } + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 650ccc55e41..a73fc93f32e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -15,7 +15,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.common.CharArrays; import org.elasticsearch.xpack.core.security.client.SecurityClient; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.junit.BeforeClass; import java.nio.charset.StandardCharsets; @@ -85,7 +85,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { addedUsers.add(uname); } logger.error("--> waiting for .security index"); - ensureGreen(SecurityIndexManager.SECURITY_INDEX_NAME); + ensureGreen(RestrictedIndicesNames.SECURITY_MAIN_ALIAS); MockTerminal t = new MockTerminal(); String username = nodeClientUsername(); @@ -136,7 +136,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { addedRoles.add(rname); } logger.error("--> waiting for .security index"); - ensureGreen(SecurityIndexManager.SECURITY_INDEX_NAME); + ensureGreen(RestrictedIndicesNames.SECURITY_MAIN_ALIAS); MockTerminal t = new MockTerminal(); String username = nodeClientUsername(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index d547fe5a839..9e7371f95ed 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -45,13 +45,13 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.client.SecurityClient; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; import org.elasticsearch.xpack.core.security.user.KibanaUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.Before; import org.junit.BeforeClass; @@ -67,8 +67,8 @@ import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDI import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_SECURITY_INDEX; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.contains; @@ -146,7 +146,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating user"); c.preparePutUser("joe", "s3kirt".toCharArray(), hasher, "role1", "user").get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> retrieving user"); GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertTrue("user should exist", resp.hasUsers()); @@ -201,7 +201,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .metadata(metadata) .get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> retrieving role"); GetRolesResponse resp = c.prepareGetRoles().names("test_role").get(); assertTrue("role should exist", resp.hasRoles()); @@ -252,7 +252,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> retrieving user"); GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertTrue("user should exist", resp.hasUsers()); @@ -273,7 +273,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> retrieving user"); GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertTrue("user should exist", resp.hasUsers()); @@ -309,7 +309,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutUser("joe", "s3krit".toCharArray(), hasher, SecuritySettingsSource.TEST_ROLE).get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> retrieving user"); GetUsersResponse resp = c.prepareGetUsers("joe").get(); assertTrue("user should exist", resp.hasUsers()); @@ -347,7 +347,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); if (authenticate) { final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); @@ -396,7 +396,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating user"); securityClient().preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role", "snapshot_user").get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); logger.info("--> creating repository"); assertAcked(client().admin().cluster() .preparePutRepository("test-repo") @@ -410,10 +410,10 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .prepareCreateSnapshot("test-repo", "test-snap-1") .setWaitForCompletion(true) .setIncludeGlobalState(false) - .setIndices(SECURITY_INDEX_NAME) + .setIndices(SECURITY_MAIN_ALIAS) .get().getSnapshotInfo(); assertThat(snapshotInfo.state(), is(SnapshotState.SUCCESS)); - assertThat(snapshotInfo.indices(), contains(SecurityIndexManager.INTERNAL_SECURITY_INDEX)); + assertThat(snapshotInfo.indices(), contains(INTERNAL_SECURITY_MAIN_INDEX_7)); deleteSecurityIndex(); // the realm cache should clear itself but we don't wish to race it securityClient().prepareClearRealmCache().get(); @@ -430,7 +430,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { RestoreSnapshotResponse response = client().admin().cluster().prepareRestoreSnapshot("test-repo", "test-snap-1") .setWaitForCompletion(true).setIncludeAliases(true).get(); assertThat(response.status(), equalTo(RestStatus.OK)); - assertThat(response.getRestoreInfo().indices(), contains(SecurityIndexManager.INTERNAL_SECURITY_INDEX)); + assertThat(response.getRestoreInfo().indices(), contains(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7)); // the realm cache should clear itself but we don't wish to race it securityClient().prepareClearRealmCache().get(); // users and roles are retrievable @@ -460,7 +460,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .get(); c.preparePutUser("joe", "s3krit".toCharArray(), hasher, "test_role").get(); logger.error("--> waiting for .security index"); - ensureGreen(SECURITY_INDEX_NAME); + ensureGreen(SECURITY_MAIN_ALIAS); final String token = basicAuthHeaderValue("joe", new SecureString("s3krit".toCharArray())); ClusterHealthResponse response = client().filterWithHeader(Collections.singletonMap("Authorization", token)).admin().cluster() @@ -591,12 +591,12 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { .get(); } - IndicesStatsResponse response = client().admin().indices().prepareStats("foo", SECURITY_INDEX_NAME).get(); + IndicesStatsResponse response = client().admin().indices().prepareStats("foo", SECURITY_MAIN_ALIAS).get(); assertThat(response.getFailedShards(), is(0)); assertThat(response.getIndices().size(), is(2)); - assertThat(response.getIndices().get(INTERNAL_SECURITY_INDEX), notNullValue()); - assertThat(response.getIndices().get(INTERNAL_SECURITY_INDEX).getIndex(), - is(INTERNAL_SECURITY_INDEX)); + assertThat(response.getIndices().get(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7), notNullValue()); + assertThat(response.getIndices().get(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7).getIndex(), + is(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7)); } public void testOperationsOnReservedUsers() throws Exception { 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 fb0d55c75cd..28625f20627 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 @@ -15,6 +15,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import java.time.Instant; import java.util.concurrent.atomic.AtomicInteger; import static org.mockito.Mockito.mock; @@ -23,10 +24,10 @@ import static org.mockito.Mockito.when; public class NativeRealmTests extends ESTestCase { private final String concreteSecurityIndexName = randomFrom( - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { - return new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, indexStatus); + return new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, indexStatus); } public void testCacheClearOnIndexHealthChange() { 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 ab82d18f2e0..4cbf5307d3e 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 @@ -27,6 +27,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.APMSystemUser; import org.elasticsearch.xpack.core.security.user.BeatsSystemUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; @@ -111,7 +112,7 @@ public class NativeUsersStoreTests extends ESTestCase { values.put(PASSWORD_FIELD, BLANK_PASSWORD); final GetResult result = new GetResult( - SecurityIndexManager.SECURITY_INDEX_NAME, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, MapperService.SINGLE_MAPPING_NAME, NativeUsersStore.getIdForUser(NativeUsersStore.RESERVED_USER_TYPE, randomAlphaOfLength(12)), 0, 1, 1L, @@ -180,7 +181,7 @@ public class NativeUsersStoreTests extends ESTestCase { nativeUsersStore.verifyPassword(username, password, future); final GetResult getResult = new GetResult( - SecurityIndexManager.SECURITY_INDEX_NAME, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, MapperService.SINGLE_MAPPING_NAME, NativeUsersStore.getIdForUser(NativeUsersStore.USER_DOC_TYPE, username), UNASSIGNED_SEQ_NO, 0, 1L, @@ -222,7 +223,7 @@ public class NativeUsersStoreTests extends ESTestCase { values.put(User.Fields.TYPE.getPreferredName(), NativeUsersStore.USER_DOC_TYPE); final BytesReference source = BytesReference.bytes(jsonBuilder().map(values)); final GetResult getResult = new GetResult( - SecurityIndexManager.SECURITY_INDEX_NAME, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, MapperService.SINGLE_MAPPING_NAME, NativeUsersStore.getIdForUser(NativeUsersStore.USER_DOC_TYPE, username), 0, 1, 1L, @@ -230,7 +231,6 @@ public class NativeUsersStoreTests extends ESTestCase { source, Collections.emptyMap()); - actionRespond(GetRequest.class, new GetResponse(getResult)); } 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 e96284ba154..6bb6e0c7b58 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 @@ -37,6 +37,7 @@ import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.hamcrest.Matchers; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -53,7 +54,7 @@ import static org.mockito.Mockito.when; public class NativeRoleMappingStoreTests extends ESTestCase { private final String concreteSecurityIndexName = randomFrom( - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); public void testResolveRoles() throws Exception { // Does match DN @@ -137,7 +138,7 @@ public class NativeRoleMappingStoreTests extends ESTestCase { } private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { - return new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, indexStatus); + return new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, indexStatus); } public void testCacheClearOnIndexHealthChange() { @@ -182,13 +183,13 @@ public class NativeRoleMappingStoreTests extends ESTestCase { final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation); store.onSecurityIndexStateChange( - new SecurityIndexManager.State(true, false, true, true, null, concreteSecurityIndexName, null), - new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, null)); + new SecurityIndexManager.State(Instant.now(), false, true, true, null, concreteSecurityIndexName, null), + new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, null)); assertEquals(1, numInvalidation.get()); store.onSecurityIndexStateChange( - new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, null), - new SecurityIndexManager.State(true, false, true, true, null, concreteSecurityIndexName, null)); + new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, null), + new SecurityIndexManager.State(Instant.now(), false, true, true, null, concreteSecurityIndexName, null)); assertEquals(2, numInvalidation.get()); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 29d02326cd2..b370c8e2b6b 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -156,8 +156,8 @@ import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationExce import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationException; import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationExceptionRunAs; import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_SECURITY_INDEX; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.endsWith; @@ -864,8 +864,8 @@ public class AuthorizationServiceTests extends ESTestCase { ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() - .put(new IndexMetaData.Builder(INTERNAL_SECURITY_INDEX) - .putAlias(new AliasMetaData.Builder(SECURITY_INDEX_NAME).build()) + .put(new IndexMetaData.Builder(INTERNAL_SECURITY_MAIN_INDEX_7) + .putAlias(new AliasMetaData.Builder(SECURITY_MAIN_ALIAS).build()) .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1) .numberOfReplicas(0) @@ -874,31 +874,33 @@ public class AuthorizationServiceTests extends ESTestCase { final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); + requests.add(new Tuple<>(BulkAction.NAME + "[s]", + new DeleteRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "id"))); requests.add( - new Tuple<>(BulkAction.NAME + "[s]", new DeleteRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "id"))); - requests.add(new Tuple<>(UpdateAction.NAME, new UpdateRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "id"))); - requests.add(new Tuple<>(BulkAction.NAME + "[s]", new IndexRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); - requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new Tuple<>(UpdateAction.NAME, new UpdateRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "id"))); + requests.add( + new Tuple<>(BulkAction.NAME + "[s]", new IndexRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); + requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(TermVectorsAction.NAME, - new TermVectorsRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "type", "id"))); - requests.add(new Tuple<>(GetAction.NAME, new GetRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "id"))); + new TermVectorsRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "type", "id"))); + requests.add(new Tuple<>(GetAction.NAME, new GetRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "id"))); requests.add(new Tuple<>(IndicesAliasesAction.NAME, new IndicesAliasesRequest() - .addAliasAction(AliasActions.add().alias("security_alias").index(INTERNAL_SECURITY_INDEX)))); + .addAliasAction(AliasActions.add().alias("security_alias").index(INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(UpdateSettingsAction.NAME, - new UpdateSettingsRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new UpdateSettingsRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); // cannot execute monitor operations requests.add(new Tuple<>(IndicesStatsAction.NAME, - new IndicesStatsRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); - requests.add( - new Tuple<>(RecoveryAction.NAME, new RecoveryRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new IndicesStatsRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); + requests.add(new Tuple<>(RecoveryAction.NAME, + new RecoveryRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(IndicesSegmentsAction.NAME, - new IndicesSegmentsRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new IndicesSegmentsRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(GetSettingsAction.NAME, - new GetSettingsRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new GetSettingsRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(IndicesShardStoresAction.NAME, - new IndicesShardStoresRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new IndicesShardStoresRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(UpgradeStatusAction.NAME, - new UpgradeStatusRequest().indices(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new UpgradeStatusRequest().indices(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); for (Tuple requestTuple : requests) { String action = requestTuple.v1(); @@ -912,13 +914,13 @@ public class AuthorizationServiceTests extends ESTestCase { } // we should allow waiting for the health of the index or any index if the user has this permission - ClusterHealthRequest request = new ClusterHealthRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)); + ClusterHealthRequest request = new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)); authorize(authentication, ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), authzInfoRoles(new String[]{role.getName()})); // multiple indices - request = new ClusterHealthRequest(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX, "foo", "bar"); + request = new ClusterHealthRequest(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7, "foo", "bar"); authorize(authentication, ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(ClusterHealthAction.NAME), eq(request), authzInfoRoles(new String[]{role.getName()})); @@ -940,8 +942,8 @@ public class AuthorizationServiceTests extends ESTestCase { ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() - .put(new IndexMetaData.Builder(INTERNAL_SECURITY_INDEX) - .putAlias(new AliasMetaData.Builder(SECURITY_INDEX_NAME).build()) + .put(new IndexMetaData.Builder(INTERNAL_SECURITY_MAIN_INDEX_7) + .putAlias(new AliasMetaData.Builder(SECURITY_MAIN_ALIAS).build()) .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1) .numberOfReplicas(0) @@ -949,12 +951,12 @@ public class AuthorizationServiceTests extends ESTestCase { .build()); List> requests = new ArrayList<>(); - requests.add(new Tuple<>(IndicesStatsAction.NAME, new IndicesStatsRequest().indices(SECURITY_INDEX_NAME))); - requests.add(new Tuple<>(RecoveryAction.NAME, new RecoveryRequest().indices(SECURITY_INDEX_NAME))); - requests.add(new Tuple<>(IndicesSegmentsAction.NAME, new IndicesSegmentsRequest().indices(SECURITY_INDEX_NAME))); - requests.add(new Tuple<>(GetSettingsAction.NAME, new GetSettingsRequest().indices(SECURITY_INDEX_NAME))); - requests.add(new Tuple<>(IndicesShardStoresAction.NAME, new IndicesShardStoresRequest().indices(SECURITY_INDEX_NAME))); - requests.add(new Tuple<>(UpgradeStatusAction.NAME, new UpgradeStatusRequest().indices(SECURITY_INDEX_NAME))); + requests.add(new Tuple<>(IndicesStatsAction.NAME, new IndicesStatsRequest().indices(SECURITY_MAIN_ALIAS))); + requests.add(new Tuple<>(RecoveryAction.NAME, new RecoveryRequest().indices(SECURITY_MAIN_ALIAS))); + requests.add(new Tuple<>(IndicesSegmentsAction.NAME, new IndicesSegmentsRequest().indices(SECURITY_MAIN_ALIAS))); + requests.add(new Tuple<>(GetSettingsAction.NAME, new GetSettingsRequest().indices(SECURITY_MAIN_ALIAS))); + requests.add(new Tuple<>(IndicesShardStoresAction.NAME, new IndicesShardStoresRequest().indices(SECURITY_MAIN_ALIAS))); + requests.add(new Tuple<>(UpgradeStatusAction.NAME, new UpgradeStatusRequest().indices(SECURITY_MAIN_ALIAS))); for (final Tuple requestTuple : requests) { final String action = requestTuple.v1(); @@ -984,8 +986,8 @@ public class AuthorizationServiceTests extends ESTestCase { ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() - .put(new IndexMetaData.Builder(INTERNAL_SECURITY_INDEX) - .putAlias(new AliasMetaData.Builder(SECURITY_INDEX_NAME).build()) + .put(new IndexMetaData.Builder(INTERNAL_SECURITY_MAIN_INDEX_7) + .putAlias(new AliasMetaData.Builder(SECURITY_MAIN_ALIAS).build()) .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1) .numberOfReplicas(0) @@ -994,25 +996,28 @@ public class AuthorizationServiceTests extends ESTestCase { final String requestId = AuditUtil.getOrGenerateRequestId(threadContext); List> requests = new ArrayList<>(); - requests.add(new Tuple<>(DeleteAction.NAME, new DeleteRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "id"))); - requests.add(new Tuple<>(BulkAction.NAME + "[s]", - createBulkShardRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), DeleteRequest::new))); - requests.add(new Tuple<>(UpdateAction.NAME, new UpdateRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "id"))); - requests.add(new Tuple<>(IndexAction.NAME, new IndexRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); - requests.add(new Tuple<>(BulkAction.NAME + "[s]", - createBulkShardRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), IndexRequest::new))); - requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); - requests.add(new Tuple<>(TermVectorsAction.NAME, - new TermVectorsRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "type", "id"))); - requests.add(new Tuple<>(GetAction.NAME, new GetRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "type", "id"))); - requests.add(new Tuple<>(TermVectorsAction.NAME, - new TermVectorsRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "type", "id"))); - requests.add(new Tuple<>(IndicesAliasesAction.NAME, - new IndicesAliasesRequest().addAliasAction(AliasActions.add().alias("security_alias").index(INTERNAL_SECURITY_INDEX)))); requests.add( - new Tuple<>(ClusterHealthAction.NAME, new ClusterHealthRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX)))); + new Tuple<>(DeleteAction.NAME, new DeleteRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "id"))); + requests.add(new Tuple<>(BulkAction.NAME + "[s]", + createBulkShardRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), DeleteRequest::new))); + requests.add( + new Tuple<>(UpdateAction.NAME, new UpdateRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "id"))); + requests.add(new Tuple<>(IndexAction.NAME, new IndexRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); + requests.add(new Tuple<>(BulkAction.NAME + "[s]", + createBulkShardRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), IndexRequest::new))); + requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); + requests.add(new Tuple<>(TermVectorsAction.NAME, + new TermVectorsRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "type", "id"))); + requests.add( + new Tuple<>(GetAction.NAME, new GetRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "type", "id"))); + requests.add(new Tuple<>(TermVectorsAction.NAME, + new TermVectorsRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "type", "id"))); + requests.add(new Tuple<>(IndicesAliasesAction.NAME, new IndicesAliasesRequest() + .addAliasAction(AliasActions.add().alias("security_alias").index(INTERNAL_SECURITY_MAIN_INDEX_7)))); requests.add(new Tuple<>(ClusterHealthAction.NAME, - new ClusterHealthRequest(randomFrom(SECURITY_INDEX_NAME, INTERNAL_SECURITY_INDEX), "foo", "bar"))); + new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7)))); + requests.add(new Tuple<>(ClusterHealthAction.NAME, + new ClusterHealthRequest(randomFrom(SECURITY_MAIN_ALIAS, INTERNAL_SECURITY_MAIN_INDEX_7), "foo", "bar"))); for (final Tuple requestTuple : requests) { final String action = requestTuple.v1(); @@ -1033,8 +1038,8 @@ public class AuthorizationServiceTests extends ESTestCase { ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() - .put(new IndexMetaData.Builder(INTERNAL_SECURITY_INDEX) - .putAlias(new AliasMetaData.Builder(SECURITY_INDEX_NAME).build()) + .put(new IndexMetaData.Builder(INTERNAL_SECURITY_MAIN_INDEX_7) + .putAlias(new AliasMetaData.Builder(SECURITY_MAIN_ALIAS).build()) .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1) .numberOfReplicas(0) @@ -1046,7 +1051,7 @@ public class AuthorizationServiceTests extends ESTestCase { SearchRequest request = new SearchRequest("_all"); authorize(authentication, action, request); verify(auditTrail).accessGranted(eq(requestId), eq(authentication), eq(action), eq(request), authzInfoRoles(superuser.roles())); - assertThat(request.indices(), arrayContainingInAnyOrder(INTERNAL_SECURITY_INDEX, SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContainingInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, SECURITY_MAIN_ALIAS)); } public void testCompositeActionsAreImmediatelyRejected() { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java index 202c9cb715f..51dba4e4c23 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java @@ -24,7 +24,6 @@ import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import java.util.List; import java.util.Set; @@ -47,8 +46,8 @@ public class AuthorizedIndicesTests extends ESTestCase { RoleDescriptor bRole = new RoleDescriptor("b", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("READ").build() }, null); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); MetaData metaData = MetaData.builder() .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) @@ -65,7 +64,7 @@ public class AuthorizedIndicesTests extends ESTestCase { .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); final PlainActionFuture future = new PlainActionFuture<>(); @@ -78,7 +77,7 @@ public class AuthorizedIndicesTests extends ESTestCase { assertFalse(list.contains("bbbbb")); assertFalse(list.contains("ba")); assertThat(list, not(contains(internalSecurityIndex))); - assertThat(list, not(contains(RestrictedIndicesNames.SECURITY_INDEX_NAME))); + assertThat(list, not(contains(RestrictedIndicesNames.SECURITY_MAIN_ALIAS))); } public void testAuthorizedIndicesUserWithSomeRolesEmptyMetaData() { @@ -101,8 +100,8 @@ public class AuthorizedIndicesTests extends ESTestCase { .cluster(ClusterPrivilege.ALL) .build(); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); MetaData metaData = MetaData.builder() .put(new IndexMetaData.Builder("an-index").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) .put(new IndexMetaData.Builder("another-index").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) @@ -111,7 +110,7 @@ public class AuthorizedIndicesTests extends ESTestCase { .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); @@ -119,7 +118,7 @@ public class AuthorizedIndicesTests extends ESTestCase { RBACEngine.resolveAuthorizedIndicesFromRole(role, SearchAction.NAME, metaData.getAliasAndIndexLookup()); assertThat(authorizedIndices, containsInAnyOrder("an-index", "another-index")); assertThat(authorizedIndices, not(contains(internalSecurityIndex))); - assertThat(authorizedIndices, not(contains(RestrictedIndicesNames.SECURITY_INDEX_NAME))); + assertThat(authorizedIndices, not(contains(RestrictedIndicesNames.SECURITY_MAIN_ALIAS))); } public void testSecurityIndicesAreNotRemovedFromUnrestrictedRole() { @@ -128,8 +127,8 @@ public class AuthorizedIndicesTests extends ESTestCase { .cluster(ClusterPrivilege.ALL) .build(); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); MetaData metaData = MetaData.builder() .put(new IndexMetaData.Builder("an-index").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) .put(new IndexMetaData.Builder("another-index").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) @@ -137,18 +136,18 @@ public class AuthorizedIndicesTests extends ESTestCase { .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); List authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(role, SearchAction.NAME, metaData.getAliasAndIndexLookup()); assertThat(authorizedIndices, containsInAnyOrder( - "an-index", "another-index", SecurityIndexManager.SECURITY_INDEX_NAME, internalSecurityIndex)); + "an-index", "another-index", RestrictedIndicesNames.SECURITY_MAIN_ALIAS, internalSecurityIndex)); List authorizedIndicesSuperUser = RBACEngine.resolveAuthorizedIndicesFromRole(role, SearchAction.NAME, metaData.getAliasAndIndexLookup()); assertThat(authorizedIndicesSuperUser, containsInAnyOrder( - "an-index", "another-index", SecurityIndexManager.SECURITY_INDEX_NAME, internalSecurityIndex)); + "an-index", "another-index", RestrictedIndicesNames.SECURITY_MAIN_ALIAS, internalSecurityIndex)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java index dc32580980e..665b70a8881 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java @@ -64,7 +64,6 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.security.user.XPackSecurityUser; import org.elasticsearch.xpack.core.security.user.XPackUser; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.elasticsearch.xpack.security.test.SecurityTestUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; @@ -79,7 +78,7 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.contains; @@ -119,7 +118,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { indexNameExpressionResolver = new IndexNameExpressionResolver(); final boolean withAlias = randomBoolean(); - final String securityIndexName = SECURITY_INDEX_NAME + (withAlias ? "-" + randomAlphaOfLength(5) : ""); + final String securityIndexName = SECURITY_MAIN_ALIAS + (withAlias ? "-" + randomAlphaOfLength(5) : ""); MetaData metaData = MetaData.builder() .put(indexBuilder("foo").putAlias(AliasMetaData.builder("foofoobar")) .putAlias(AliasMetaData.builder("foounauthorized")).settings(settings)) @@ -1222,14 +1221,14 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { { final List authorizedIndices = buildAuthorizedIndices(XPackSecurityUser.INSTANCE, SearchAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - assertThat(indices, hasItem(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(indices, hasItem(SECURITY_MAIN_ALIAS)); } { IndicesAliasesRequest aliasesRequest = new IndicesAliasesRequest(); - aliasesRequest.addAliasAction(AliasActions.add().alias("security_alias").index(SECURITY_INDEX_NAME)); + aliasesRequest.addAliasAction(AliasActions.add().alias("security_alias").index(SECURITY_MAIN_ALIAS)); final List authorizedIndices = buildAuthorizedIndices(XPackSecurityUser.INSTANCE, IndicesAliasesAction.NAME); List indices = resolveIndices(aliasesRequest, authorizedIndices).getLocal(); - assertThat(indices, hasItem(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(indices, hasItem(SECURITY_MAIN_ALIAS)); } } @@ -1237,7 +1236,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { SearchRequest request = new SearchRequest(); final List authorizedIndices = buildAuthorizedIndices(XPackUser.INSTANCE, SearchAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - assertThat(indices, not(hasItem(SecurityIndexManager.SECURITY_INDEX_NAME))); + assertThat(indices, not(hasItem(SECURITY_MAIN_ALIAS))); } public void testNonXPackUserAccessingSecurityIndex() { @@ -1249,7 +1248,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { SearchRequest request = new SearchRequest(); final List authorizedIndices = buildAuthorizedIndices(allAccessUser, SearchAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - assertThat(indices, not(hasItem(SecurityIndexManager.SECURITY_INDEX_NAME))); + assertThat(indices, not(hasItem(SECURITY_MAIN_ALIAS))); } { @@ -1257,7 +1256,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { aliasesRequest.addAliasAction(AliasActions.add().alias("security_alias1").index("*")); final List authorizedIndices = buildAuthorizedIndices(allAccessUser, IndicesAliasesAction.NAME); List indices = resolveIndices(aliasesRequest, authorizedIndices).getLocal(); - assertThat(indices, not(hasItem(SecurityIndexManager.SECURITY_INDEX_NAME))); + assertThat(indices, not(hasItem(SECURITY_MAIN_ALIAS))); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java index 8a79bc86f67..66ea23b518c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/SnapshotUserRoleIntegTests.java @@ -22,8 +22,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.Locale; -import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7; -import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; @@ -56,7 +56,7 @@ public class SnapshotUserRoleIntegTests extends NativeRealmIntegTestCase { final String snapshotUserToken = basicAuthHeaderValue(user, new SecureString(password)); client = client().filterWithHeader(Collections.singletonMap("Authorization", snapshotUserToken)); securityClient().preparePutUser(user, password, Hasher.BCRYPT, "snapshot_user").get(); - ensureGreen(INTERNAL_SECURITY_INDEX_7); + ensureGreen(INTERNAL_SECURITY_MAIN_INDEX_7); } public void testSnapshotUserRoleCanSnapshotAndSeeAllIndices() { @@ -67,17 +67,17 @@ public class SnapshotUserRoleIntegTests extends NativeRealmIntegTestCase { assertThat(getRepositoriesResponse.repositories().get(0).name(), is("repo")); // view all indices, including restricted ones final GetIndexResponse getIndexResponse = client.admin().indices().prepareGetIndex().setIndices(randomFrom("_all", "*")).get(); - assertThat(Arrays.asList(getIndexResponse.indices()), containsInAnyOrder(INTERNAL_SECURITY_INDEX_7, ordinaryIndex)); + assertThat(Arrays.asList(getIndexResponse.indices()), containsInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, ordinaryIndex)); // create snapshot that includes restricted indices final CreateSnapshotResponse snapshotResponse = client.admin().cluster().prepareCreateSnapshot("repo", "snap") .setIndices(randomFrom("_all", "*")).setWaitForCompletion(true).get(); assertThat(snapshotResponse.getSnapshotInfo().state(), is(SnapshotState.SUCCESS)); - assertThat(snapshotResponse.getSnapshotInfo().indices(), containsInAnyOrder(INTERNAL_SECURITY_INDEX_7, ordinaryIndex)); + assertThat(snapshotResponse.getSnapshotInfo().indices(), containsInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, ordinaryIndex)); // view snapshots for repo final GetSnapshotsResponse getSnapshotResponse = client.admin().cluster().prepareGetSnapshots("repo").get(); assertThat(getSnapshotResponse.getSnapshots().size(), is(1)); assertThat(getSnapshotResponse.getSnapshots().get(0).snapshotId().getName(), is("snap")); - assertThat(getSnapshotResponse.getSnapshots().get(0).indices(), containsInAnyOrder(INTERNAL_SECURITY_INDEX_7, ordinaryIndex)); + assertThat(getSnapshotResponse.getSnapshots().get(0).indices(), containsInAnyOrder(INTERNAL_SECURITY_MAIN_INDEX_7, ordinaryIndex)); } public void testSnapshotUserRoleIsReserved() { @@ -112,7 +112,7 @@ public class SnapshotUserRoleIntegTests extends NativeRealmIntegTestCase { () -> client.admin().cluster().prepareDeleteSnapshot("repo", randomAlphaOfLength(4).toLowerCase(Locale.ROOT)).get(), "cluster:admin/snapshot/delete", "snapshot_user"); // try destructive/revealing actions on all indices - for (final String indexToTest : Arrays.asList(INTERNAL_SECURITY_INDEX_7, SECURITY_INDEX_NAME, ordinaryIndex)) { + for (final String indexToTest : Arrays.asList(INTERNAL_SECURITY_MAIN_INDEX_7, SECURITY_MAIN_ALIAS, ordinaryIndex)) { assertThrowsAuthorizationException(() -> client.prepareSearch(indexToTest).get(), "indices:data/read/search", "snapshot_user"); assertThrowsAuthorizationException(() -> client.prepareGet(indexToTest, "doc", "1").get(), "indices:data/read/get", "snapshot_user"); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java index 8f3e6981575..9bd69d3eb1a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java @@ -293,14 +293,14 @@ public class IndicesPermissionTests extends ESTestCase { public void testSecurityIndicesPermissions() { final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); final MetaData metaData = new MetaData.Builder() .put(new IndexMetaData.Builder(internalSecurityIndex) .settings(indexSettings) .numberOfShards(1) .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_INDEX_NAME).build()) + .putAlias(new AliasMetaData.Builder(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).build()) .build(), true) .build(); FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); @@ -309,18 +309,18 @@ public class IndicesPermissionTests extends ESTestCase { // allow_restricted_indices: false IndicesPermission.Group group = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, false, "*"); Map authzMap = new IndicesPermission(group).authorize(SearchAction.NAME, - Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_INDEX_NAME), lookup, + Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup, fieldPermissionsCache); assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(false)); - assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_INDEX_NAME).isGranted(), is(false)); + assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(false)); // allow_restricted_indices: true group = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, true, "*"); authzMap = new IndicesPermission(group).authorize(SearchAction.NAME, - Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_INDEX_NAME), lookup, + Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup, fieldPermissionsCache); assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true)); - assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_INDEX_NAME).isGranted(), is(true)); + assertThat(authzMap.get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).isGranted(), is(true)); } private static FieldPermissionsDefinition fieldPermissionDef(String[] granted, String[] denied) { 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 ba2ba455a50..a39545f3a9b 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 @@ -57,6 +57,7 @@ import org.elasticsearch.xpack.security.authc.ApiKeyService.ApiKeyRoleDescriptor import org.elasticsearch.xpack.security.support.SecurityIndexManager; import java.io.IOException; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -98,7 +99,7 @@ public class CompositeRolesStoreTests extends ESTestCase { private final FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY); private final String concreteSecurityIndexName = randomFrom( - RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_INDEX_7); + RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7); public void testRolesWhenDlsFlsUnlicensed() throws IOException { XPackLicenseState licenseState = mock(XPackLicenseState.class); @@ -762,7 +763,7 @@ public class CompositeRolesStoreTests extends ESTestCase { } private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) { - return new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, indexStatus); + return new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, indexStatus); } public void testCacheClearOnIndexHealthChange() { @@ -837,13 +838,13 @@ public class CompositeRolesStoreTests extends ESTestCase { }; compositeRolesStore.onSecurityIndexStateChange( - new SecurityIndexManager.State(true, false, true, true, null, concreteSecurityIndexName, null), - new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, null)); + new SecurityIndexManager.State(Instant.now(), false, true, true, null, concreteSecurityIndexName, null), + new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, null)); assertEquals(1, numInvalidation.get()); compositeRolesStore.onSecurityIndexStateChange( - new SecurityIndexManager.State(true, true, true, true, null, concreteSecurityIndexName, null), - new SecurityIndexManager.State(true, false, true, true, null, concreteSecurityIndexName, null)); + new SecurityIndexManager.State(Instant.now(), true, true, true, null, concreteSecurityIndexName, null), + new SecurityIndexManager.State(Instant.now(), false, true, true, null, concreteSecurityIndexName, null)); assertEquals(2, numInvalidation.get()); } 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 a8337488549..7f7a262131b 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 @@ -39,6 +39,7 @@ import org.elasticsearch.test.client.NoOpClient; import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheRequest; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.hamcrest.Matchers; import org.junit.After; @@ -125,7 +126,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(GetRequest.class)); GetRequest request = (GetRequest) requests.get(0); - assertThat(request.index(), equalTo(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.index(), equalTo(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); assertThat(request.type(), equalTo(MapperService.SINGLE_MAPPING_NAME)); assertThat(request.id(), equalTo("application-privilege_myapp:admin")); @@ -143,7 +144,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(GetRequest.class)); GetRequest request = (GetRequest) requests.get(0); - assertThat(request.index(), equalTo(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.index(), equalTo(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); assertThat(request.type(), equalTo(MapperService.SINGLE_MAPPING_NAME)); assertThat(request.id(), equalTo("application-privilege_myapp:admin")); @@ -166,7 +167,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(SearchRequest.class)); SearchRequest request = (SearchRequest) requests.get(0); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); final String query = Strings.toString(request.source().query()); assertThat(query, containsString("{\"terms\":{\"application\":[\"myapp\",\"yourapp\"]")); @@ -187,7 +188,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(SearchRequest.class)); SearchRequest request = (SearchRequest) requests.get(0); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); final String query = Strings.toString(request.source().query()); assertThat(query, containsString("{\"bool\":{\"filter\":[{\"terms\":{\"application\":[\"yourapp\"]")); @@ -207,7 +208,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(SearchRequest.class)); SearchRequest request = (SearchRequest) requests.get(0); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); final String query = Strings.toString(request.source().query()); assertThat(query, containsString("{\"exists\":{\"field\":\"application\"")); @@ -232,7 +233,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(requests, iterableWithSize(1)); assertThat(requests.get(0), instanceOf(SearchRequest.class)); SearchRequest request = (SearchRequest) requests.get(0); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); final String query = Strings.toString(request.source().query()); assertThat(query, containsString("{\"term\":{\"type\":{\"value\":\"application-privilege\"")); @@ -268,7 +269,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { for (int i = 0; i < putPrivileges.size(); i++) { ApplicationPrivilegeDescriptor privilege = putPrivileges.get(i); IndexRequest request = indexRequests.get(i); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); assertThat(request.type(), equalTo(MapperService.SINGLE_MAPPING_NAME)); assertThat(request.id(), equalTo( "application-privilege_" + privilege.getApplication() + ":" + privilege.getName() @@ -277,7 +278,7 @@ public class NativePrivilegeStoreTests extends ESTestCase { assertThat(request.source(), equalTo(BytesReference.bytes(builder))); final boolean created = privilege.getName().equals("user") == false; indexListener.onResponse(new IndexResponse( - new ShardId(SecurityIndexManager.SECURITY_INDEX_NAME, uuid, i), + new ShardId(RestrictedIndicesNames.SECURITY_MAIN_ALIAS, uuid, i), request.type(), request.id(), 1, 1, 1, created )); } @@ -313,12 +314,12 @@ public class NativePrivilegeStoreTests extends ESTestCase { for (int i = 0; i < privilegeNames.size(); i++) { String name = privilegeNames.get(i); DeleteRequest request = deletes.get(i); - assertThat(request.indices(), arrayContaining(SecurityIndexManager.SECURITY_INDEX_NAME)); + assertThat(request.indices(), arrayContaining(RestrictedIndicesNames.SECURITY_MAIN_ALIAS)); assertThat(request.type(), equalTo(MapperService.SINGLE_MAPPING_NAME)); assertThat(request.id(), equalTo("application-privilege_app1:" + name)); final boolean found = name.equals("p2") == false; deleteListener.onResponse(new DeleteResponse( - new ShardId(SecurityIndexManager.SECURITY_INDEX_NAME, uuid, i), + new ShardId(RestrictedIndicesNames.SECURITY_MAIN_ALIAS, uuid, i), request.type(), request.id(), 1, 1, 1, found )); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java index d204085a41f..39c65fb8782 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java @@ -56,7 +56,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; @@ -187,7 +187,7 @@ public class NativeRolesStoreTests extends ESTestCase { final ClusterService clusterService = mock(ClusterService.class); final XPackLicenseState licenseState = mock(XPackLicenseState.class); final AtomicBoolean methodCalled = new AtomicBoolean(false); - final SecurityIndexManager securityIndex = SecurityIndexManager.buildSecurityIndexManager(client, clusterService); + final SecurityIndexManager securityIndex = SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService); final NativeRolesStore rolesStore = new NativeRolesStore(Settings.EMPTY, client, licenseState, securityIndex) { @Override void innerPutRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener listener) { @@ -247,7 +247,7 @@ public class NativeRolesStoreTests extends ESTestCase { private ClusterState getClusterStateWithSecurityIndex() { final boolean withAlias = randomBoolean(); - final String securityIndexName = SECURITY_INDEX_NAME + (withAlias ? "-" + randomAlphaOfLength(5) : ""); + final String securityIndexName = SECURITY_MAIN_ALIAS + (withAlias ? "-" + randomAlphaOfLength(5) : ""); Settings settings = Settings.builder() .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) @@ -256,7 +256,7 @@ public class NativeRolesStoreTests extends ESTestCase { .build(); MetaData metaData = MetaData.builder() .put(IndexMetaData.builder(securityIndexName).settings(settings)) - .put(new IndexTemplateMetaData(SecurityIndexManager.SECURITY_TEMPLATE_NAME, 0, 0, + .put(new IndexTemplateMetaData(SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7, 0, 0, Collections.singletonList(securityIndexName), Settings.EMPTY, ImmutableOpenMap.of(), ImmutableOpenMap.of())) .build(); 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 95c5dc96a50..3dd5395b1fe 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 @@ -29,6 +29,7 @@ import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexTemplateMetaData; import org.elasticsearch.cluster.metadata.MetaData; @@ -52,12 +53,12 @@ import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.security.test.SecurityTestUtils; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.template.TemplateUtils; import org.hamcrest.Matchers; import org.junit.Before; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_TEMPLATE_NAME; +import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7; import static org.elasticsearch.xpack.security.support.SecurityIndexManager.TEMPLATE_VERSION_PATTERN; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -71,7 +72,6 @@ public class SecurityIndexManagerTests extends ESTestCase { private static final ClusterName CLUSTER_NAME = new ClusterName("security-index-manager-tests"); private static final ClusterState EMPTY_CLUSTER_STATE = new ClusterState.Builder(CLUSTER_NAME).build(); - private static final String INDEX_NAME = ".security"; private static final String TEMPLATE_NAME = "SecurityIndexManagerTests-template"; private SecurityIndexManager manager; private Map, Map>> actions; @@ -96,13 +96,14 @@ public class SecurityIndexManagerTests extends ESTestCase { actions.put(action, map); } }; - manager = SecurityIndexManager.buildSecurityIndexManager(client, clusterService); + manager = SecurityIndexManager.buildSecurityMainIndexManager(client, clusterService); } public void testIndexWithUpToDateMappingAndTemplate() throws IOException { assertInitialState(); - final ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME); + final ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); @@ -114,8 +115,9 @@ public class SecurityIndexManagerTests extends ESTestCase { public void testIndexWithoutPrimaryShards() throws IOException { assertInitialState(); - final ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME); - Index index = new Index(INDEX_NAME, UUID.randomUUID().toString()); + final ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME); + Index index = new Index(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, UUID.randomUUID().toString()); ShardRouting shardRouting = ShardRouting.newUnassigned(new ShardId(index, 0), true, RecoverySource.ExistingStoreRecoverySource.INSTANCE, new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "")); String nodeId = ESTestCase.randomAlphaOfLength(8); @@ -147,7 +149,8 @@ public class SecurityIndexManagerTests extends ESTestCase { manager.addIndexStateListener(listener); // index doesn't exist and now exists - final ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME); + final ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); @@ -171,7 +174,7 @@ public class SecurityIndexManagerTests extends ESTestCase { previousState.set(null); currentState.set(null); ClusterState previousClusterState = clusterStateBuilder.build(); - Index prevIndex = previousClusterState.getRoutingTable().index(INDEX_NAME).getIndex(); + Index prevIndex = previousClusterState.getRoutingTable().index(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7).getIndex(); clusterStateBuilder.routingTable(RoutingTable.builder() .add(IndexRoutingTable.builder(prevIndex) .addIndexShard(new IndexShardRoutingTable.Builder(new ShardId(prevIndex, 0)) @@ -231,8 +234,8 @@ public class SecurityIndexManagerTests extends ESTestCase { prepareException.set(null); prepareRunnableCalled.set(false); // state recovered with index - ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME, - SecurityIndexManager.INTERNAL_INDEX_FORMAT); + ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); manager.prepareIndexIfNeededThenExecute(ex -> { @@ -255,8 +258,8 @@ public class SecurityIndexManagerTests extends ESTestCase { assertThat(manager.isStateRecovered(), is(false)); assertThat(listenerCalled.get(), is(false)); // state recovered with index - ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME, - SecurityIndexManager.INTERNAL_INDEX_FORMAT); + ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); assertThat(manager.isStateRecovered(), is(true)); @@ -278,8 +281,8 @@ public class SecurityIndexManagerTests extends ESTestCase { assertTrue(manager.isIndexUpToDate()); // index doesn't exist and now exists with wrong format - ClusterState.Builder clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME, - SecurityIndexManager.INTERNAL_INDEX_FORMAT - 1); + ClusterState.Builder clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT - 1); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); assertTrue(listenerCalled.get()); @@ -295,7 +298,8 @@ public class SecurityIndexManagerTests extends ESTestCase { listenerCalled.set(false); // index doesn't exist and now exists with correct format - clusterStateBuilder = createClusterState(INDEX_NAME, TEMPLATE_NAME, SecurityIndexManager.INTERNAL_INDEX_FORMAT); + clusterStateBuilder = createClusterState(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7, + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, TEMPLATE_NAME, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT); markShardsAvailable(clusterStateBuilder); manager.clusterChanged(event(clusterStateBuilder)); assertTrue(listenerCalled.get()); @@ -317,18 +321,19 @@ public class SecurityIndexManagerTests extends ESTestCase { assertThat(manager.isStateRecovered(), Matchers.equalTo(true)); } - public static ClusterState.Builder createClusterState(String indexName, String templateName) throws IOException { - return createClusterState(indexName, templateName, templateName, SecurityIndexManager.INTERNAL_INDEX_FORMAT); + public static ClusterState.Builder createClusterState(String indexName, String aliasName, String templateName) throws IOException { + return createClusterState(indexName, aliasName, templateName, templateName, SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT); } - public static ClusterState.Builder createClusterState(String indexName, String templateName, int format) throws IOException { - return createClusterState(indexName, templateName, templateName, format); - } - - private static ClusterState.Builder createClusterState(String indexName, String templateName, String buildMappingFrom, int format) + public static ClusterState.Builder createClusterState(String indexName, String aliasName, String templateName, int format) throws IOException { + return createClusterState(indexName, aliasName, templateName, templateName, format); + } + + private static ClusterState.Builder createClusterState(String indexName, String aliasName, String templateName, String buildMappingFrom, + int format) throws IOException { IndexTemplateMetaData.Builder templateBuilder = getIndexTemplateMetaData(templateName); - IndexMetaData.Builder indexMeta = getIndexMetadata(indexName, buildMappingFrom, format); + IndexMetaData.Builder indexMeta = getIndexMetadata(indexName, aliasName, buildMappingFrom, format); MetaData.Builder metaDataBuilder = new MetaData.Builder(); metaDataBuilder.put(templateBuilder); @@ -338,7 +343,7 @@ public class SecurityIndexManagerTests extends ESTestCase { } private void markShardsAvailable(ClusterState.Builder clusterStateBuilder) { - clusterStateBuilder.routingTable(SecurityTestUtils.buildIndexRoutingTable(INDEX_NAME)); + clusterStateBuilder.routingTable(SecurityTestUtils.buildIndexRoutingTable(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7)); } private static ClusterState state() { @@ -349,7 +354,8 @@ public class SecurityIndexManagerTests extends ESTestCase { .build(); } - private static IndexMetaData.Builder getIndexMetadata(String indexName, String templateName, int format) throws IOException { + private static IndexMetaData.Builder getIndexMetadata(String indexName, String aliasName, String templateName, int format) + throws IOException { IndexMetaData.Builder indexMetaData = IndexMetaData.builder(indexName); indexMetaData.settings(Settings.builder() .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) @@ -357,7 +363,7 @@ public class SecurityIndexManagerTests extends ESTestCase { .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.INDEX_FORMAT_SETTING.getKey(), format) .build()); - + indexMetaData.putAlias(AliasMetaData.builder(aliasName).build()); final Map mappings = getTemplateMappings(templateName); for (Map.Entry entry : mappings.entrySet()) { indexMetaData.putMapping(entry.getKey(), entry.getValue()); @@ -389,7 +395,7 @@ public class SecurityIndexManagerTests extends ESTestCase { } public void testMappingVersionMatching() throws IOException { - String templateString = "/" + SECURITY_TEMPLATE_NAME + ".json"; + String templateString = "/" + SECURITY_MAIN_TEMPLATE_7 + ".json"; ClusterState.Builder clusterStateBuilder = createClusterStateWithMappingAndTemplate(templateString); manager.clusterChanged(new ClusterChangedEvent("test-event", clusterStateBuilder.build(), EMPTY_CLUSTER_STATE)); assertTrue(manager.checkMappingVersion(Version.CURRENT.minimumIndexCompatibilityVersion()::before)); @@ -397,17 +403,19 @@ public class SecurityIndexManagerTests extends ESTestCase { } public void testMissingVersionMappingThrowsError() throws IOException { - String templateString = "/missing-version-" + SECURITY_TEMPLATE_NAME + ".json"; + String templateString = "/missing-version-security-index-template.json"; ClusterState.Builder clusterStateBuilder = createClusterStateWithMappingAndTemplate(templateString); final ClusterState clusterState = clusterStateBuilder.build(); IllegalStateException exception = expectThrows(IllegalStateException.class, - () -> SecurityIndexManager.checkIndexMappingVersionMatches(SECURITY_INDEX_NAME, clusterState, logger, Version.CURRENT::equals)); - assertEquals("Cannot read security-version string in index " + SECURITY_INDEX_NAME, exception.getMessage()); + () -> SecurityIndexManager.checkIndexMappingVersionMatches(RestrictedIndicesNames.SECURITY_MAIN_ALIAS, + clusterState, logger, Version.CURRENT::equals)); + assertEquals("Cannot read security-version string in index " + RestrictedIndicesNames.SECURITY_MAIN_ALIAS, + exception.getMessage()); } public void testIndexTemplateIsIdentifiedAsUpToDate() throws IOException { ClusterState.Builder clusterStateBuilder = createClusterStateWithTemplate( - "/" + SECURITY_TEMPLATE_NAME + ".json" + "/" + SECURITY_MAIN_TEMPLATE_7 + ".json" ); manager.clusterChanged(new ClusterChangedEvent("test-event", clusterStateBuilder.build(), EMPTY_CLUSTER_STATE)); // No upgrade actions run @@ -415,20 +423,20 @@ public class SecurityIndexManagerTests extends ESTestCase { } public void testIndexTemplateVersionMatching() throws Exception { - String templateString = "/" + SECURITY_TEMPLATE_NAME + ".json"; + String templateString = "/" + SECURITY_MAIN_TEMPLATE_7 + ".json"; ClusterState.Builder clusterStateBuilder = createClusterStateWithTemplate(templateString); final ClusterState clusterState = clusterStateBuilder.build(); assertTrue(SecurityIndexManager.checkTemplateExistsAndVersionMatches( - SecurityIndexManager.SECURITY_TEMPLATE_NAME, clusterState, logger, + SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7, clusterState, logger, Version.V_6_0_0::before)); assertFalse(SecurityIndexManager.checkTemplateExistsAndVersionMatches( - SecurityIndexManager.SECURITY_TEMPLATE_NAME, clusterState, logger, + SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7, clusterState, logger, Version.V_6_0_0::after)); } public void testUpToDateMappingsAreIdentifiedAsUpToDate() throws IOException { - String securityTemplateString = "/" + SECURITY_TEMPLATE_NAME + ".json"; + String securityTemplateString = "/" + SECURITY_MAIN_TEMPLATE_7 + ".json"; ClusterState.Builder clusterStateBuilder = createClusterStateWithMappingAndTemplate(securityTemplateString); manager.clusterChanged(new ClusterChangedEvent("test-event", clusterStateBuilder.build(), EMPTY_CLUSTER_STATE)); @@ -438,8 +446,8 @@ public class SecurityIndexManagerTests extends ESTestCase { public void testMissingIndexIsIdentifiedAsUpToDate() throws IOException { final ClusterName clusterName = new ClusterName("test-cluster"); final ClusterState.Builder clusterStateBuilder = ClusterState.builder(clusterName); - String mappingString = "/" + SECURITY_TEMPLATE_NAME + ".json"; - IndexTemplateMetaData.Builder templateMeta = getIndexTemplateMetaData(SECURITY_TEMPLATE_NAME, mappingString); + String mappingString = "/" + SECURITY_MAIN_TEMPLATE_7 + ".json"; + IndexTemplateMetaData.Builder templateMeta = getIndexTemplateMetaData(SECURITY_MAIN_TEMPLATE_7, mappingString); MetaData.Builder builder = new MetaData.Builder(clusterStateBuilder.build().getMetaData()); builder.put(templateMeta); clusterStateBuilder.metaData(builder); @@ -450,24 +458,24 @@ public class SecurityIndexManagerTests extends ESTestCase { private ClusterState.Builder createClusterStateWithTemplate(String securityTemplateString) throws IOException { // add the correct mapping no matter what the template - ClusterState clusterState = createClusterStateWithIndex("/" + SECURITY_TEMPLATE_NAME + ".json").build(); + ClusterState clusterState = createClusterStateWithIndex("/" + SECURITY_MAIN_TEMPLATE_7 + ".json").build(); final MetaData.Builder metaDataBuilder = new MetaData.Builder(clusterState.metaData()); - metaDataBuilder.put(getIndexTemplateMetaData(SECURITY_TEMPLATE_NAME, securityTemplateString)); + metaDataBuilder.put(getIndexTemplateMetaData(SECURITY_MAIN_TEMPLATE_7, securityTemplateString)); return ClusterState.builder(clusterState).metaData(metaDataBuilder); } private ClusterState.Builder createClusterStateWithMapping(String securityTemplateString) throws IOException { final ClusterState clusterState = createClusterStateWithIndex(securityTemplateString).build(); final String indexName = clusterState.metaData().getAliasAndIndexLookup() - .get(SECURITY_INDEX_NAME).getIndices().get(0).getIndex().getName(); + .get(RestrictedIndicesNames.SECURITY_MAIN_ALIAS).getIndices().get(0).getIndex().getName(); return ClusterState.builder(clusterState).routingTable(SecurityTestUtils.buildIndexRoutingTable(indexName)); } private ClusterState.Builder createClusterStateWithMappingAndTemplate(String securityTemplateString) throws IOException { ClusterState.Builder clusterStateBuilder = createClusterStateWithMapping(securityTemplateString); MetaData.Builder metaDataBuilder = new MetaData.Builder(clusterStateBuilder.build().metaData()); - String securityMappingString = "/" + SECURITY_TEMPLATE_NAME + ".json"; - IndexTemplateMetaData.Builder securityTemplateMeta = getIndexTemplateMetaData(SECURITY_TEMPLATE_NAME, securityMappingString); + String securityMappingString = "/" + SECURITY_MAIN_TEMPLATE_7 + ".json"; + IndexTemplateMetaData.Builder securityTemplateMeta = getIndexTemplateMetaData(SECURITY_MAIN_TEMPLATE_7, securityMappingString); metaDataBuilder.put(securityTemplateMeta); return clusterStateBuilder.metaData(metaDataBuilder); } @@ -493,7 +501,8 @@ public class SecurityIndexManagerTests extends ESTestCase { private ClusterState.Builder createClusterStateWithIndex(String securityTemplate) throws IOException { final MetaData.Builder metaDataBuilder = new MetaData.Builder(); final boolean withAlias = randomBoolean(); - final String securityIndexName = SECURITY_INDEX_NAME + (withAlias ? "-" + randomAlphaOfLength(5) : ""); + final String securityIndexName = RestrictedIndicesNames.SECURITY_MAIN_ALIAS + + (withAlias ? "-" + randomAlphaOfLength(5) : ""); metaDataBuilder.put(createIndexMetadata(securityIndexName, securityTemplate)); ClusterState.Builder clusterStateBuilder = ClusterState.builder(state()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java index 12474b7a04d..0ee5ff21ee1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/test/SecurityTestUtils.java @@ -35,7 +35,7 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; -import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_INDEX_NAME; +import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS; public class SecurityTestUtils { @@ -83,7 +83,7 @@ public class SecurityTestUtils { * Adds the index alias {@code .security} to the underlying concrete index. */ public static MetaData addAliasToMetaData(MetaData metaData, String indexName) { - AliasMetaData aliasMetaData = AliasMetaData.newAliasMetaDataBuilder(SECURITY_INDEX_NAME).build(); + AliasMetaData aliasMetaData = AliasMetaData.newAliasMetaDataBuilder(SECURITY_MAIN_ALIAS).build(); MetaData.Builder metaDataBuilder = new MetaData.Builder(metaData); IndexMetaData indexMetaData = metaData.index(indexName); metaDataBuilder.put(IndexMetaData.builder(indexMetaData).putAlias(aliasMetaData)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java index a295e47b6d7..c8b20e1b460 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java @@ -14,7 +14,6 @@ import org.elasticsearch.xpack.core.security.index.IndexAuditTrailField; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.XPackUser; import org.elasticsearch.xpack.security.audit.index.IndexNameResolver; -import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.hamcrest.Matchers; import org.joda.time.DateTime; @@ -32,8 +31,6 @@ public class XPackUserTests extends ESTestCase { public void testXPackUserCannotAccessRestrictedIndices() { final String action = randomFrom(GetAction.NAME, SearchAction.NAME, IndexAction.NAME); final Predicate predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action); - assertThat(predicate.test(SecurityIndexManager.SECURITY_INDEX_NAME), Matchers.is(false)); - assertThat(predicate.test(SecurityIndexManager.INTERNAL_SECURITY_INDEX), Matchers.is(false)); for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) { assertThat(predicate.test(index), Matchers.is(false)); } diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/13_security-tokens_read.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/13_security-tokens_read.yml new file mode 100644 index 00000000000..183731e1ba8 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/13_security-tokens_read.yml @@ -0,0 +1,161 @@ +--- +setup: + - skip: + features: headers + + - do: + cluster.health: + wait_for_status: yellow + + - do: + security.put_role: + name: "all_access" + body: > + { + "cluster": [ "all" ], + "indices": [ + { "names": ["*"], "privileges": ["all"] } + ] + } + + - do: + security.put_user: + username: "test_user" + body: > + { + "password" : "x-pack-test-password", + "roles" : [ "all_access" ], + "full_name" : "user with all possible privileges (but not superuser)" + } + +--- +teardown: + - do: + security.delete_user: + username: "test_user" + ignore: 404 + + - do: + security.delete_role: + name: "all_access" + ignore: 404 + +--- +"Test get security tokens index metadata": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + indices.get: + index: ".security-tokens" + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + indices.get: + index: ".security-tokens" + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + indices.get: + index: ".secu*rity*tokens" + - length: { $body: 0 } + + - do: + headers: + Authorization: Bearer ${token} + indices.get: + index: ".secu*rity*tokens" + - length: { $body: 0 } + +--- +"Test get security document": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + get: + index: ".security-tokens" + id: token_${token} + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + get: + index: ".security-tokens" + id: token_${token} + +--- +"Test search security tokens index": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + search: + rest_total_hits_as_int: true + index: ".security-tokens" + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: ".security-tokens" + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + search: + rest_total_hits_as_int: true + index: ".secu*rity*tokens" + - match: { hits.total: 0 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: ".secu*rity*tokens" + - match: { hits.total: 0 } + diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/14_security-tokens-7_read.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/14_security-tokens-7_read.yml new file mode 100644 index 00000000000..97cff4ba980 --- /dev/null +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/security/hidden-index/14_security-tokens-7_read.yml @@ -0,0 +1,161 @@ +--- +setup: + - skip: + features: headers + + - do: + cluster.health: + wait_for_status: yellow + + - do: + security.put_role: + name: "all_access" + body: > + { + "cluster": [ "all" ], + "indices": [ + { "names": ["*"], "privileges": ["all"] } + ] + } + + - do: + security.put_user: + username: "test_user" + body: > + { + "password" : "x-pack-test-password", + "roles" : [ "all_access" ], + "full_name" : "user with all possible privileges (but not superuser)" + } + +--- +teardown: + - do: + security.delete_user: + username: "test_user" + ignore: 404 + + - do: + security.delete_role: + name: "all_access" + ignore: 404 + +--- +"Test get security tokens index metadata": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + indices.get: + index: ".security-tokens-7" + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + indices.get: + index: ".security-tokens-7" + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + indices.get: + index: ".secu*rity*tokens-7" + - length: { $body: 0 } + + - do: + headers: + Authorization: Bearer ${token} + indices.get: + index: ".secu*rity*tokens-7" + - length: { $body: 0 } + +--- +"Test get security document": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + get: + index: ".security-tokens-7" + id: token_${token} + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + get: + index: ".security-tokens-7" + id: token_${token} + +--- +"Test search security tokens index": + + - do: + security.get_token: + body: + grant_type: "password" + username: "test_user" + password: "x-pack-test-password" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - match: { expires_in: 1200 } + - is_false: scope + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + search: + rest_total_hits_as_int: true + index: ".security-tokens-7" + + - do: + catch: forbidden + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: ".security-tokens-7" + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + search: + rest_total_hits_as_int: true + index: ".secu*rity*tokens-7" + - match: { hits.total: 0 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: ".secu*rity*tokens-7" + - match: { hits.total: 0 } + diff --git a/x-pack/qa/rolling-upgrade/build.gradle b/x-pack/qa/rolling-upgrade/build.gradle index 56daa59367d..8b5d1f1ff72 100644 --- a/x-pack/qa/rolling-upgrade/build.gradle +++ b/x-pack/qa/rolling-upgrade/build.gradle @@ -119,6 +119,8 @@ for (Version version : bwcVersions.wireCompatible) { setting 'xpack.security.enabled', 'true' setting 'xpack.security.transport.ssl.enabled', 'true' setting 'xpack.security.authc.token.enabled', 'true' + setting 'xpack.security.authc.token.timeout', '60m' + setting 'logger.org.elasticsearch.xpack.security.authc.TokenService', 'trace' setting 'xpack.security.audit.enabled', 'true' if (project.inFipsJvm) { setting 'xpack.security.transport.ssl.key', 'testnode.pem' @@ -184,6 +186,8 @@ for (Version version : bwcVersions.wireCompatible) { setting 'xpack.license.self_generated.type', 'trial' setting 'xpack.security.enabled', 'true' setting 'xpack.security.transport.ssl.enabled', 'true' + setting 'xpack.security.authc.token.timeout', '60m' + setting 'logger.org.elasticsearch.xpack.security.authc.TokenService', 'trace' if (project.inFipsJvm) { setting 'xpack.security.transport.ssl.key', 'testnode.pem' setting 'xpack.security.transport.ssl.certificate', 'testnode.crt' diff --git a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java index 0a8e9354d98..2245fa3ea1d 100644 --- a/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java +++ b/x-pack/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/TokenBackwardsCompatibilityIT.java @@ -16,9 +16,14 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.rest.yaml.ObjectPath; +import org.junit.After; +import org.junit.Before; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,7 +32,33 @@ import static org.hamcrest.Matchers.equalTo; public class TokenBackwardsCompatibilityIT extends AbstractUpgradeTestCase { - public void testGeneratingTokenInOldCluster() throws Exception { + private Collection twoClients = null; + + @Before + private void collectClientsByVersion() throws IOException { + Map clientsByVersion = getRestClientByVersion(); + if (clientsByVersion.size() == 2) { + // usual case, clients have different versions + twoClients = clientsByVersion.values(); + } else { + assert clientsByVersion.size() == 1 : "A rolling upgrade has a maximum of two distinct node versions, found: " + + clientsByVersion.keySet(); + // tests assumes exactly two clients to simplify some logic + twoClients = new ArrayList<>(); + twoClients.add(clientsByVersion.values().iterator().next()); + twoClients.add(clientsByVersion.values().iterator().next()); + } + } + + @After + private void closeClientsByVersion() throws IOException { + for (RestClient client : twoClients) { + client.close(); + } + twoClients = null; + } + + public void testGeneratingTokensInOldCluster() throws Exception { assumeTrue("this test should only run against the old cluster", CLUSTER_TYPE == ClusterType.OLD); { Version minimumIndexCompatibilityVersion = Version.CURRENT.minimumIndexCompatibilityVersion(); @@ -48,188 +79,363 @@ public class TokenBackwardsCompatibilityIT extends AbstractUpgradeTestCase { client().performRequest(createTemplate); } } + // Creates two access and refresh tokens and stores them in the token_backwards_compatibility_it index to be used for tests in the + // mixed/upgraded clusters + Map responseMap = createTokens(client(), "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); - // Create a couple of tokens and store them in the token_backwards_compatibility_it index to be used for tests in the mixed/upgraded - // clusters - Request createTokenRequest = new Request("POST", "/_security/oauth2/token"); - createTokenRequest.setJsonEntity( - "{\n" + - " \"username\": \"test_user\",\n" + - " \"password\": \"x-pack-test-password\",\n" + - " \"grant_type\": \"password\"\n" + - "}"); - Response response = client().performRequest(createTokenRequest); - assertOK(response); - Map responseMap = entityAsMap(response); - String token = (String) responseMap.get("access_token"); - assertNotNull(token); - assertTokenWorks(token); + storeTokens(client(), 1, accessToken, refreshToken); - // In this test either all or none tests or on a specific version: - Request indexRequest1 = new Request("PUT", "token_backwards_compatibility_it/_doc/old_cluster_token1"); - indexRequest1.setJsonEntity( - "{\n" + - " \"token\": \"" + token + "\"\n" + - "}"); - Response indexResponse1 = client().performRequest(indexRequest1); - assertOK(indexResponse1); - Request createSecondTokenRequest = new Request("POST", "/_security/oauth2/token"); - createSecondTokenRequest.setEntity(createTokenRequest.getEntity()); - response = client().performRequest(createSecondTokenRequest); - responseMap = entityAsMap(response); - token = (String) responseMap.get("access_token"); - assertNotNull(token); - assertTokenWorks(token); - Request indexRequest2 = new Request("PUT", "token_backwards_compatibility_it/_doc/old_cluster_token2"); - indexRequest2.setJsonEntity( - "{\n" + - " \"token\": \"" + token + "\"\n" + - "}"); - Response indexResponse2 = client().performRequest(indexRequest2); - assertOK(indexResponse2); + responseMap = createTokens(client(), "test_user", "x-pack-test-password"); + accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + + storeTokens(client(), 2, accessToken, refreshToken); } - public void testTokenWorksInMixedCluster() throws Exception { + public void testRefreshingTokensInOldCluster() throws Exception { + assumeTrue("this test should only run against the old cluster", CLUSTER_TYPE == ClusterType.OLD); + { + Version minimumIndexCompatibilityVersion = Version.CURRENT.minimumIndexCompatibilityVersion(); + assertThat("this branch is not needed if we aren't compatible with 6.0", + minimumIndexCompatibilityVersion.onOrBefore(Version.V_6_0_0), equalTo(true)); + if (minimumIndexCompatibilityVersion.before(Version.V_7_0_0)) { + XContentBuilder template = jsonBuilder(); + template.startObject(); + { + template.field("index_patterns", "*"); + template.startObject("settings"); + template.field("number_of_shards", 5); + template.endObject(); + } + template.endObject(); + Request createTemplate = new Request("PUT", "/_template/template"); + createTemplate.setJsonEntity(Strings.toString(template)); + client().performRequest(createTemplate); + } + } + // Creates access and refresh tokens and uses the refresh token. The new resulting tokens are used in different phases + Map responseMap = createTokens(client(), "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + + storeTokens(client(), 3, accessToken, refreshToken); + + // refresh the token just created. The old token is invalid (tested further) and the new refresh token is tested in the upgraded + // cluster + Map refreshResponseMap = refreshToken(client(), refreshToken); + String refreshedAccessToken = (String) refreshResponseMap.get("access_token"); + String refreshedRefreshToken = (String) refreshResponseMap.get("refresh_token"); + assertNotNull(refreshedAccessToken); + assertNotNull(refreshedRefreshToken); + assertAccessTokenWorks(refreshedAccessToken); + // assert previous access token still works + assertAccessTokenWorks(accessToken); + + storeTokens(client(), 4, refreshedAccessToken, refreshedRefreshToken); + } + + public void testInvalidatingTokensInOldCluster() throws Exception { + assumeTrue("this test should only run against the old cluster", CLUSTER_TYPE == ClusterType.OLD); + { + Version minimumIndexCompatibilityVersion = Version.CURRENT.minimumIndexCompatibilityVersion(); + assertThat("this branch is not needed if we aren't compatible with 6.0", + minimumIndexCompatibilityVersion.onOrBefore(Version.V_6_0_0), equalTo(true)); + if (minimumIndexCompatibilityVersion.before(Version.V_7_0_0)) { + XContentBuilder template = jsonBuilder(); + template.startObject(); + { + template.field("index_patterns", "*"); + template.startObject("settings"); + template.field("number_of_shards", 5); + template.endObject(); + } + template.endObject(); + Request createTemplate = new Request("PUT", "/_template/template"); + createTemplate.setJsonEntity(Strings.toString(template)); + client().performRequest(createTemplate); + } + } + // Creates access and refresh tokens and tries to use the access tokens several times + Map responseMap = createTokens(client(), "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + + storeTokens(client(), 5, accessToken, refreshToken); + + // invalidate access token + invalidateAccessToken(client(), accessToken); + assertAccessTokenDoesNotWork(accessToken); + // invalidate refresh token + invalidateRefreshToken(client(), refreshToken); + assertRefreshTokenInvalidated(refreshToken); + } + + public void testAccessTokensWorkInMixedCluster() throws Exception { // Verify that an old token continues to work during all stages of the rolling upgrade assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); - Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token1"); - Response getResponse = client().performRequest(getRequest); - assertOK(getResponse); - Map source = (Map) entityAsMap(getResponse).get("_source"); - assertTokenWorks((String) source.get("token")); - } - - public void testInvalidatingTokenInMixedCluster() throws Exception { - // Verify that we can invalidate a token in a mixed cluster - assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); - Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token2"); - Response getResponse = client().performRequest(getRequest); - assertOK(getResponse); - Map source = (Map) entityAsMap(getResponse).get("_source"); - String token = (String) source.get("token"); - // The token might be already invalidated by running testInvalidatingTokenInMixedCluster in a previous stage - // we don't try to assert it works before invalidating. This case is handled by testTokenWorksInMixedCluster - Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); - invalidateRequest.setJsonEntity("{\"token\": \"" + token + "\"}"); - invalidateRequest.addParameter("error_trace", "true"); - client().performRequest(invalidateRequest); - assertTokenDoesNotWork(token); - } - - public void testMixedClusterWithUpgradedMaster() throws Exception { - assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); - assumeTrue("the master must be on the latest version before we can write", isMasterOnLatestVersion()); - - // create token and refresh on version that supports it - Request createTokenRequest = new Request("POST", "/_security/oauth2/token"); - createTokenRequest.setJsonEntity( - "{\n" + - " \"username\": \"test_user\",\n" + - " \"password\": \"x-pack-test-password\",\n" + - " \"grant_type\": \"password\"\n" + - "}"); - try (RestClient client = getRestClientForCurrentVersionNodesOnly()) { - Response response = client.performRequest(createTokenRequest); - Map responseMap = entityAsMap(response); - String accessToken = (String) responseMap.get("access_token"); - String refreshToken = (String) responseMap.get("refresh_token"); - assertNotNull(accessToken); - assertNotNull(refreshToken); - assertTokenWorks(accessToken); - - Request tokenRefreshRequest = new Request("POST", "/_security/oauth2/token"); - tokenRefreshRequest.setJsonEntity( - "{\n" + - " \"refresh_token\": \"" + refreshToken + "\",\n" + - " \"grant_type\": \"refresh_token\"\n" + - "}"); - response = client.performRequest(tokenRefreshRequest); - responseMap = entityAsMap(response); - String updatedAccessToken = (String) responseMap.get("access_token"); - String updatedRefreshToken = (String) responseMap.get("refresh_token"); - assertNotNull(updatedAccessToken); - assertNotNull(updatedRefreshToken); - assertTokenWorks(updatedAccessToken); - assertTokenWorks(accessToken); - assertNotEquals(accessToken, updatedAccessToken); - assertNotEquals(refreshToken, updatedRefreshToken); - // Invalidate the new access token and ensure that it no longer works - Request invalidateTokenRequest = new Request("DELETE", "/_security/oauth2/token"); - invalidateTokenRequest.setJsonEntity( - "{\n" + - " \"token\": \"" + updatedAccessToken + "\"\n" + - "}"); - Response invalidateTokenResponse = client.performRequest(invalidateTokenRequest); - assertOK(invalidateTokenResponse); - assertTokenDoesNotWork(updatedAccessToken); + for (int tokenIdx : Arrays.asList(1, 3, 4)) { // 2 is invalidated in another mixed-cluster test, 5 is invalidated in the old cluster + Map source = retrieveStoredTokens(client(), tokenIdx); + assertAccessTokenWorks((String) source.get("token")); } } - public void testUpgradedCluster() throws Exception { + public void testTokensStayInvalidatedInMixedCluster() throws Exception { + // Verify that an old, invalidated token remains invalidated during all stages of the rolling upgrade + assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); + Map source = retrieveStoredTokens(client(), 5); + assertAccessTokenDoesNotWork((String) source.get("token")); + assertRefreshTokenInvalidated((String) source.get("refresh_token")); + } + + public void testGeneratingTokensInMixedCluster() throws Exception { + assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); + // Creates two access and refresh tokens and stores them in the token_backwards_compatibility_it index to be used for tests in the + // mixed/upgraded clusters + int generatedTokenIdxDuringMixed = 10; + for (RestClient client : twoClients) { + Map responseMap = createTokens(client, "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + + storeTokens(client(), generatedTokenIdxDuringMixed++, accessToken, refreshToken); + + responseMap = createTokens(client, "test_user", "x-pack-test-password"); + accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + + storeTokens(client(), generatedTokenIdxDuringMixed++, accessToken, refreshToken); + } + } + + public void testRefreshingTokensInMixedCluster() throws Exception { + // verify new nodes can refresh tokens created by old nodes and vice versa + assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); + for (RestClient client1 : twoClients) { + Map responseMap = createTokens(client1, "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + for (RestClient client2 : twoClients) { + responseMap = refreshToken(client2, refreshToken); + accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); + } + } + } + + public void testInvalidatingTokensInMixedCluster() throws Exception { + // Verify that we can invalidate an access and refresh token in a mixed cluster + assumeTrue("this test should only run against the mixed cluster", CLUSTER_TYPE == ClusterType.MIXED); + Map source = retrieveStoredTokens(client(), 2); + String accessToken = (String) source.get("token"); + String refreshToken = (String) source.get("refresh_token"); + // The token might be already invalidated by running testInvalidatingTokenInMixedCluster in a previous stage + // we don't try to assert it works before invalidating. This case is handled by testTokenWorksInMixedCluster + invalidateAccessToken(client(), accessToken); + assertAccessTokenDoesNotWork(accessToken); + // invalidate refresh token + invalidateRefreshToken(client(), refreshToken); + assertRefreshTokenInvalidated(refreshToken); + } + + public void testTokensStayInvalidatedInUpgradedCluster() throws Exception { assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); - // Use an old token to authenticate, then invalidate it and verify that it can no longer be used - Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token1"); - Response getResponse = client().performRequest(getRequest); - assertOK(getResponse); - Map source = (Map) entityAsMap(getResponse).get("_source"); - final String token = (String) source.get("token"); - - Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); - invalidateRequest.setJsonEntity("{\"token\": \"" + token + "\"}"); - invalidateRequest.addParameter("error_trace", "true"); - Response invalidationResponse = client().performRequest(invalidateRequest); - assertOK(invalidationResponse); - assertTokenDoesNotWork(token); + for (int tokenIdx : Arrays.asList(2, 5)) { + Map source = retrieveStoredTokens(client(), tokenIdx); + assertAccessTokenDoesNotWork((String) source.get("token")); + assertRefreshTokenInvalidated((String) source.get("refresh_token")); + } } - private void assertTokenWorks(String token) throws IOException { - Request request = new Request("GET", "/_security/_authenticate"); - RequestOptions.Builder options = request.getOptions().toBuilder(); - options.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); - request.setOptions(options); - Response authenticateResponse = client().performRequest(request); - assertOK(authenticateResponse); - assertEquals("test_user", entityAsMap(authenticateResponse).get("username")); + public void testAccessTokensWorkInUpgradedCluster() throws Exception { + assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); + for (int tokenIdx : Arrays.asList(3, 4, 10, 12)) { + Map source = retrieveStoredTokens(client(), tokenIdx); + assertAccessTokenWorks((String) source.get("token")); + } } - private void assertTokenDoesNotWork(String token) { - Request request = new Request("GET", "/_security/_authenticate"); - RequestOptions.Builder options = request.getOptions().toBuilder(); - options.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); - request.setOptions(options); - ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(request)); - assertEquals(401, e.getResponse().getStatusLine().getStatusCode()); - Response response = e.getResponse(); - assertEquals("Bearer realm=\"security\", error=\"invalid_token\", error_description=\"The access token expired\"", - response.getHeader("WWW-Authenticate")); + public void testGeneratingTokensInUpgradedCluster() throws Exception { + assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); + Map responseMap = createTokens(client(), "test_user", "x-pack-test-password"); + String accessToken = (String) responseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) responseMap.get("refresh_token"); + assertNotNull(refreshToken); } - private boolean isMasterOnLatestVersion() throws Exception { - Response response = client().performRequest(new Request("GET", "_cluster/state")); - assertOK(response); - final String masterNodeId = ObjectPath.createFromResponse(response).evaluate("master_node"); - response = client().performRequest(new Request("GET", "_nodes")); - assertOK(response); - ObjectPath objectPath = ObjectPath.createFromResponse(response); - logger.info("Master node is on version: " + objectPath.evaluate("nodes." + masterNodeId + ".version")); - return Version.CURRENT.equals(Version.fromString(objectPath.evaluate("nodes." + masterNodeId + ".version"))); + public void testRefreshingTokensInUpgradedCluster() throws Exception { + assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); + for (int tokenIdx : Arrays.asList(4, 10, 12)) { + Map source = retrieveStoredTokens(client(), tokenIdx); + Map refreshedResponseMap = refreshToken(client(), (String) source.get("refresh_token")); + String accessToken = (String) refreshedResponseMap.get("access_token"); + assertNotNull(accessToken); + assertAccessTokenWorks(accessToken); + String refreshToken = (String) refreshedResponseMap.get("refresh_token"); + assertNotNull(refreshToken); + } } - private RestClient getRestClientForCurrentVersionNodesOnly() throws IOException { + public void testInvalidatingTokensInUpgradedCluster() throws Exception { + assumeTrue("this test should only run against the upgraded cluster", CLUSTER_TYPE == ClusterType.UPGRADED); + for (int tokenIdx : Arrays.asList(1, 11, 13)) { + Map source = retrieveStoredTokens(client(), tokenIdx); + String accessToken = (String) source.get("token"); + String refreshToken = (String) source.get("refresh_token"); + // invalidate access token + invalidateAccessToken(client(), accessToken); + assertAccessTokenDoesNotWork(accessToken); + // invalidate refresh token + invalidateRefreshToken(client(), refreshToken); + assertRefreshTokenInvalidated(refreshToken); + } + } + + private void assertAccessTokenWorks(String token) throws IOException { + for (RestClient client : twoClients) { + Request request = new Request("GET", "/_security/_authenticate"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + options.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); + request.setOptions(options); + Response authenticateResponse = client.performRequest(request); + assertOK(authenticateResponse); + assertEquals("test_user", entityAsMap(authenticateResponse).get("username")); + } + } + + private void assertAccessTokenDoesNotWork(String token) throws IOException { + for (RestClient client : twoClients) { + Request request = new Request("GET", "/_security/_authenticate"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + options.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); + request.setOptions(options); + ResponseException e = expectThrows(ResponseException.class, () -> client.performRequest(request)); + assertEquals(401, e.getResponse().getStatusLine().getStatusCode()); + Response response = e.getResponse(); + assertEquals("Bearer realm=\"security\", error=\"invalid_token\", error_description=\"The access token expired\"", + response.getHeader("WWW-Authenticate")); + } + } + + private void assertRefreshTokenInvalidated(String refreshToken) throws IOException { + for (RestClient client : twoClients) { + Request refreshTokenRequest = new Request("POST", "/_security/oauth2/token"); + refreshTokenRequest.setJsonEntity( + "{\n" + + " \"refresh_token\": \"" + refreshToken + "\",\n" + + " \"grant_type\": \"refresh_token\"\n" + + "}"); + ResponseException e = expectThrows(ResponseException.class, () -> client.performRequest(refreshTokenRequest)); + assertEquals(400, e.getResponse().getStatusLine().getStatusCode()); + Response response = e.getResponse(); + Map responseMap = entityAsMap(response); + assertEquals("invalid_grant", responseMap.get("error")); + assertEquals("token has been invalidated", responseMap.get("error_description")); + } + } + + private Map getRestClientByVersion() throws IOException { Response response = client().performRequest(new Request("GET", "_nodes")); assertOK(response); ObjectPath objectPath = ObjectPath.createFromResponse(response); Map nodesAsMap = objectPath.evaluate("nodes"); - List hosts = new ArrayList<>(); + Map> hostsByVersion = new HashMap<>(); for (Map.Entry entry : nodesAsMap.entrySet()) { Map nodeDetails = (Map) entry.getValue(); Version version = Version.fromString((String) nodeDetails.get("version")); - if (Version.CURRENT.equals(version)) { - Map httpInfo = (Map) nodeDetails.get("http"); - hosts.add(HttpHost.create((String) httpInfo.get("publish_address"))); - } + Map httpInfo = (Map) nodeDetails.get("http"); + hostsByVersion.computeIfAbsent(version, k -> new ArrayList<>()).add(HttpHost.create((String) httpInfo.get("publish_address"))); } + Map clientsByVersion = new HashMap<>(); + for (Map.Entry> entry : hostsByVersion.entrySet()) { + clientsByVersion.put(entry.getKey(), buildClient(restClientSettings(), entry.getValue().toArray(new HttpHost[0]))); + } + return clientsByVersion; + } - return buildClient(restClientSettings(), hosts.toArray(new HttpHost[0])); + private Map createTokens(RestClient client, String username, String password) throws IOException { + final Request createTokenRequest = new Request("POST", "/_security/oauth2/token"); + createTokenRequest.setJsonEntity( + "{\n" + + " \"username\": \"" + username + "\",\n" + + " \"password\": \"" + password + "\",\n" + + " \"grant_type\": \"password\"\n" + + "}"); + Response response = client().performRequest(createTokenRequest); + assertOK(response); + return entityAsMap(response); + } + + private void storeTokens(RestClient client, int idx, String accessToken, String refreshToken) throws IOException { + final Request indexRequest = new Request("PUT", "token_backwards_compatibility_it/_doc/old_cluster_token" + idx); + indexRequest.setJsonEntity( + "{\n" + + " \"token\": \"" + accessToken + "\",\n" + + " \"refresh_token\": \"" + refreshToken + "\"\n" + + "}"); + Response indexResponse1 = client.performRequest(indexRequest); + assertOK(indexResponse1); + } + + private Map retrieveStoredTokens(RestClient client, int tokenIdx) throws IOException { + Request getRequest = new Request("GET", "token_backwards_compatibility_it/_doc/old_cluster_token" + tokenIdx); + Response getResponse = client().performRequest(getRequest); + assertOK(getResponse); + return (Map) entityAsMap(getResponse).get("_source"); + } + + private Map refreshToken(RestClient client, String refreshToken) throws IOException { + final Request refreshTokenRequest = new Request("POST", "/_security/oauth2/token"); + refreshTokenRequest.setJsonEntity( + "{\n" + + " \"refresh_token\": \"" + refreshToken + "\",\n" + + " \"grant_type\": \"refresh_token\"\n" + + "}"); + Response refreshResponse = client.performRequest(refreshTokenRequest); + assertOK(refreshResponse); + return entityAsMap(refreshResponse); + } + + private void invalidateAccessToken(RestClient client, String accessToken) throws IOException { + Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); + invalidateRequest.setJsonEntity("{\"token\": \"" + accessToken + "\"}"); + invalidateRequest.addParameter("error_trace", "true"); + Response invalidateResponse = client.performRequest(invalidateRequest); + assertOK(invalidateResponse); + } + + private void invalidateRefreshToken(RestClient client, String refreshToken) throws IOException { + Request invalidateRequest = new Request("DELETE", "/_security/oauth2/token"); + invalidateRequest.setJsonEntity("{\"refresh_token\": \"" + refreshToken + "\"}"); + invalidateRequest.addParameter("error_trace", "true"); + Response invalidateResponse = client.performRequest(invalidateRequest); + assertOK(invalidateResponse); } } diff --git a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/50_token_auth.yml b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/50_token_auth.yml index aae5f308597..f426d9b2525 100644 --- a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/50_token_auth.yml +++ b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/50_token_auth.yml @@ -3,14 +3,17 @@ - skip: features: headers + - do: + cluster.health: + wait_for_status: yellow + - do: get: index: token_index - type: doc id: "6" - match: { _index: token_index } - - match: { _type: doc } + - match: { _type: _doc } - match: { _id: "6" } - is_true: _source.token - set: { _source.token : token } @@ -24,6 +27,59 @@ - match: { roles.0: "superuser" } - match: { full_name: "Token User" } + # call three times because the client rotates the nodes + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + +--- +"Get the indexed refreshed access token and use if to authenticate": + - skip: + features: headers + + - do: + get: + index: token_index + id: "7" + + - match: { _index: token_index } + - match: { _type: _doc } + - match: { _id: "7" } + - is_true: _source.token + - set: { _source.token : token } + + - do: + headers: + Authorization: Bearer ${token} + security.authenticate: {} + + - match: { username: "token_user" } + - match: { roles.0: "superuser" } + - match: { full_name: "Token User" } + - do: headers: Authorization: Bearer ${token} @@ -31,7 +87,7 @@ rest_total_hits_as_int: true index: token_index - - match: { hits.total: 6 } + - match: { hits.total: 8 } - do: headers: @@ -40,7 +96,7 @@ rest_total_hits_as_int: true index: token_index - - match: { hits.total: 6 } + - match: { hits.total: 8 } - do: headers: @@ -49,5 +105,79 @@ rest_total_hits_as_int: true index: token_index - - match: { hits.total: 6 } + - match: { hits.total: 8 } +--- +"Get the indexed refresh token and use it to get another access token and authenticate": + - skip: + features: headers + + - do: + get: + index: token_index + id: "8" + + - match: { _index: token_index } + - match: { _type: _doc } + - match: { _id: "8" } + - is_true: _source.token + - set: { _source.token : refresh_token } + + - do: + security.get_token: + body: + grant_type: "refresh_token" + refresh_token: "${refresh_token}" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - is_true: refresh_token + - set: { refresh_token: refresh_token } + - match: { expires_in: 3600 } + - is_false: scope + + - do: + headers: + Authorization: Bearer ${token} + security.authenticate: {} + + - match: { username: "token_user" } + - match: { roles.0: "superuser" } + - match: { full_name: "Token User" } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } + + # overwrite the used refresh token with the new one + - do: + headers: + Authorization: Bearer ${token} + index: + index: token_index + id: "8" + body: { "token" : "${refresh_token}"} diff --git a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/50_token_auth.yml b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/50_token_auth.yml index 02fa0f31ce3..e4d0eb8757f 100644 --- a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/50_token_auth.yml +++ b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/50_token_auth.yml @@ -27,7 +27,9 @@ - match: { type: "Bearer" } - is_true: access_token - set: { access_token: token } - - match: { expires_in: 1200 } + - is_true: refresh_token + - set: { refresh_token: refresh_token } + - match: { expires_in: 3600 } - is_false: scope - do: @@ -54,15 +56,15 @@ bulk: refresh: true body: - - '{"index": {"_index": "token_index", "_type": "doc", "_id" : "1"}}' + - '{"index": {"_index": "token_index", "_type": "_doc", "_id" : "1"}}' - '{"f1": "v1_old", "f2": 0}' - - '{"index": {"_index": "token_index", "_type": "doc", "_id" : "2"}}' + - '{"index": {"_index": "token_index", "_type": "_doc", "_id" : "2"}}' - '{"f1": "v2_old", "f2": 1}' - - '{"index": {"_index": "token_index", "_type": "doc", "_id" : "3"}}' + - '{"index": {"_index": "token_index", "_type": "_doc", "_id" : "3"}}' - '{"f1": "v3_old", "f2": 2}' - - '{"index": {"_index": "token_index", "_type": "doc", "_id" : "4"}}' + - '{"index": {"_index": "token_index", "_type": "_doc", "_id" : "4"}}' - '{"f1": "v4_old", "f2": 3}' - - '{"index": {"_index": "token_index", "_type": "doc", "_id" : "5"}}' + - '{"index": {"_index": "token_index", "_type": "_doc", "_id" : "5"}}' - '{"f1": "v5_old", "f2": 4}' - do: @@ -81,6 +83,48 @@ Authorization: Bearer ${token} index: index: token_index - type: doc id: "6" body: { "token" : "${token}"} + + # refresh token and store it as well + - do: + security.get_token: + body: + grant_type: "refresh_token" + refresh_token: "${refresh_token}" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: refreshed_access_token } + - is_true: refresh_token + - set: { refresh_token: refreshed_refresh_token } + - match: { expires_in: 3600 } + - is_false: scope + + # test refresh token (use it) + - do: + headers: + Authorization: Bearer ${refreshed_access_token} + security.authenticate: {} + + - match: { username: "token_user" } + - match: { roles.0: "superuser" } + - match: { full_name: "Token User" } + + # store the new refreshed access token + - do: + headers: + Authorization: Bearer ${refreshed_access_token} + index: + index: token_index + id: "7" + body: { "token" : "${refreshed_access_token}"} + + # store the refresh token + - do: + headers: + Authorization: Bearer ${refreshed_access_token} + index: + index: token_index + id: "8" + body: { "token" : "${refreshed_refresh_token}"} diff --git a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/50_token_auth.yml b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/50_token_auth.yml index a207c430860..430f94c1064 100644 --- a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/50_token_auth.yml +++ b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/upgraded_cluster/50_token_auth.yml @@ -2,14 +2,18 @@ "Get the indexed token and use if to authenticate": - skip: features: headers + + - do: + cluster.health: + wait_for_status: yellow + - do: get: index: token_index - type: doc id: "6" - match: { _index: token_index } - - match: { _type: doc } + - match: { _type: _doc } - match: { _id: "6" } - is_true: _source.token - set: { _source.token : token } @@ -30,7 +34,7 @@ rest_total_hits_as_int: true index: token_index - - match: { hits.total: 6 } + - match: { hits.total: 8 } # counter example that we are really checking this - do: @@ -40,3 +44,51 @@ search: rest_total_hits_as_int: true index: token_index + +--- +"Get the indexed refresh token and use if to get another access token and authenticate": + - skip: + features: headers + + - do: + get: + index: token_index + id: "8" + + - match: { _index: token_index } + - match: { _type: _doc } + - match: { _id: "8" } + - is_true: _source.token + - set: { _source.token : refresh_token } + + - do: + security.get_token: + body: + grant_type: "refresh_token" + refresh_token: "${refresh_token}" + + - match: { type: "Bearer" } + - is_true: access_token + - set: { access_token: token } + - is_true: refresh_token + - set: { refresh_token: refresh_token } + - match: { expires_in: 3600 } + - is_false: scope + + - do: + headers: + Authorization: Bearer ${token} + security.authenticate: {} + + - match: { username: "token_user" } + - match: { roles.0: "superuser" } + - match: { full_name: "Token User" } + + - do: + headers: + Authorization: Bearer ${token} + search: + rest_total_hits_as_int: true + index: token_index + + - match: { hits.total: 8 } diff --git a/x-pack/qa/third-party/active-directory/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractAdLdapRealmTestCase.java b/x-pack/qa/third-party/active-directory/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractAdLdapRealmTestCase.java index bfec6d100a9..86d411053af 100644 --- a/x-pack/qa/third-party/active-directory/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractAdLdapRealmTestCase.java +++ b/x-pack/qa/third-party/active-directory/src/test/java/org/elasticsearch/xpack/security/authc/ldap/AbstractAdLdapRealmTestCase.java @@ -199,7 +199,7 @@ public abstract class AbstractAdLdapRealmTestCase extends SecurityIntegTestCase @Override public Set excludeTemplates() { Set templates = Sets.newHashSet(super.excludeTemplates()); - templates.add(SecurityIndexManager.SECURITY_TEMPLATE_NAME); // don't remove the security index template + templates.add(SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7); // don't remove the security index template return templates; }