Просмотр исходного кода

System indices treated as restricted indices (#74212)

System indices should be treated as a special set of indices and not be
accessible by all users. The existing security codebase has the notion
of restricted indices, which are currently a subset of system indices.

This change unifies the two concepts by making system indices the set
of restricted indices. This means that going forward, consumers of
system indices will need access to restricted indices.

Our intention is that this will be handled internally as much as
possible.  For example, restricted index access can be used implicitly 
by setting a valid origin on a request or using a system index plugin.
In these cases, the security module will apply internally defined
privileges when necessary.

The main impact of this change for developers is that system index
deletions will require superuser privileges, so we have to make sure
we are using an admin role for test cleanup.

Closes #69298

Co-authored-by: William Brafford <william.brafford@elastic.co>
Co-authored-by: Albert Zaharovits <albert.zaharovits@elastic.co>
Jay Modi 4 лет назад
Родитель
Сommit
22e9d3719d
34 измененных файлов с 886 добавлено и 526 удалено
  1. 11 0
      client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java
  2. 11 0
      client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java
  3. 19 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java
  4. 1 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java
  5. 10 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/MlTestStateCleaner.java
  6. 5 0
      server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java
  7. 44 20
      server/src/main/java/org/elasticsearch/indices/SystemIndices.java
  8. 1 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncTaskIndexService.java
  9. 110 64
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/IndicesPermission.java
  10. 62 23
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java
  11. 2 3
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java
  12. 0 14
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java
  13. 4 5
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/AsyncSearchUser.java
  14. 12 9
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/XPackUser.java
  15. 27 26
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java
  16. 56 54
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java
  17. 107 0
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/test/TestRestrictedIndices.java
  18. 7 2
      x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java
  19. 2 1
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java
  20. 72 37
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java
  21. 3 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java
  22. 2 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java
  23. 13 7
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java
  24. 28 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java
  25. 23 21
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java
  26. 78 44
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java
  27. 2 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptorTests.java
  28. 4 3
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java
  29. 147 18
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java
  30. 15 11
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java
  31. 0 72
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/AsyncSearchUserTests.java
  32. 0 81
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java
  33. 2 0
      x-pack/plugin/sql/qa/server/security/build.gradle
  34. 6 2
      x-pack/plugin/sql/qa/server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security/SqlSecurityTestCase.java

+ 11 - 0
client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java

@@ -59,6 +59,17 @@ public class EnrollmentIT  extends ESRestHighLevelClientTestCase {
             .build();
     }
 
+    /**
+     * Cleanup for these tests requires the admin client to have access to the
+     * truststore, so we use the same settings that we're using for the rest
+     * client in this test class.
+     * @return Settings for the admin client.
+     */
+    @Override
+    protected Settings restAdminSettings() {
+        return restClientSettings();
+    }
+
     public void testEnrollNode() throws Exception {
         final NodeEnrollmentResponse nodeEnrollmentResponse =
             execute(highLevelClient().security()::enrollNode, highLevelClient().security()::enrollNodeAsync, RequestOptions.DEFAULT);

+ 11 - 0
client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java

@@ -52,6 +52,17 @@ public class EnrollmentDocumentationIT extends ESRestHighLevelClientTestCase {
             .build();
     }
 
+    /**
+     * Cleanup for these tests requires the admin client to have access to the
+     * truststore, so we use the same settings that we're using for the rest
+     * client in this test class.
+     * @return Settings for the admin client.
+     */
+    @Override
+    protected Settings restAdminSettings() {
+        return restClientSettings();
+    }
+
     public void testNodeEnrollment() throws Exception {
         RestHighLevelClient client = highLevelClient();
 

+ 19 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java

@@ -22,6 +22,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.client.cluster.RemoteInfoRequest;
 import org.elasticsearch.client.cluster.RemoteInfoResponse;
 import org.elasticsearch.client.indices.CreateIndexRequest;
+import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.core.Booleans;
 import org.elasticsearch.core.CheckedRunnable;
 import org.elasticsearch.common.bytes.BytesReference;
@@ -66,6 +67,7 @@ public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
     protected static final String CONFLICT_PIPELINE_ID = "conflict_pipeline";
 
     private static RestHighLevelClient restHighLevelClient;
+    private static RestHighLevelClient adminRestHighLevelClient;
     private static boolean async = Booleans.parseBoolean(System.getProperty("tests.rest.async", "false"));
 
     @Before
@@ -74,18 +76,35 @@ public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
         if (restHighLevelClient == null) {
             restHighLevelClient = new HighLevelClient(client());
         }
+        if (adminRestHighLevelClient == null) {
+            adminRestHighLevelClient = new HighLevelClient(adminClient());
+        }
     }
 
     @AfterClass
     public static void cleanupClient() throws IOException {
         IOUtils.close(restHighLevelClient);
+        IOUtils.close(adminRestHighLevelClient);
         restHighLevelClient = null;
+        adminRestHighLevelClient = null;
     }
 
     protected static RestHighLevelClient highLevelClient() {
         return restHighLevelClient;
     }
 
+    @Override
+    protected Settings restAdminSettings() {
+        String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray()));
+        return Settings.builder()
+            .put(ThreadContext.PREFIX + ".Authorization", token)
+            .build();
+    }
+
+    protected static RestHighLevelClient adminHighLevelClient() {
+        return adminRestHighLevelClient;
+    }
+
     /**
      * Executes the provided request using either the sync method or its async variant, both provided as functions
      */

+ 1 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java

@@ -274,7 +274,7 @@ public class MachineLearningGetResultsIT extends ESRestHighLevelClientTestCase {
 
     @After
     public void deleteJob() throws IOException {
-        new MlTestStateCleaner(logger, highLevelClient()).clearMlMetadata();
+        new MlTestStateCleaner(logger, adminHighLevelClient()).clearMlMetadata();
     }
 
     public void testGetModelSnapshots() throws IOException {

+ 10 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/MlTestStateCleaner.java

@@ -12,6 +12,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
 import org.elasticsearch.action.ingest.DeletePipelineRequest;
 import org.elasticsearch.client.core.PageParams;
 import org.elasticsearch.client.feature.ResetFeaturesRequest;
+import org.elasticsearch.client.feature.ResetFeaturesResponse;
 import org.elasticsearch.client.ml.GetTrainedModelsStatsRequest;
 
 import java.io.IOException;
@@ -36,7 +37,15 @@ public class MlTestStateCleaner {
     public void clearMlMetadata() throws IOException {
         deleteAllTrainedModelIngestPipelines();
         // This resets all features, not just ML, but they should have been getting reset between tests anyway so it shouldn't matter
-        client.features().resetFeatures(new ResetFeaturesRequest(), RequestOptions.DEFAULT);
+        ResetFeaturesResponse response = client.features().resetFeatures(new ResetFeaturesRequest(), RequestOptions.DEFAULT);
+        if (response.getFeatureResetStatuses().stream().anyMatch(status -> "FAILURE".equals(status.getStatus()))) {
+            logger.warn("Not all feature states could be reset while clearing ML Metadata:");
+            for (ResetFeaturesResponse.ResetFeatureStateStatus status : response.getFeatureResetStatuses()) {
+                if (status.getStatus().equals("FAILURE")) {
+                    logger.warn("Feature {} failed with response: {}", status.getFeatureName(), status.getException());
+                }
+            }
+        }
     }
 
     @SuppressWarnings("unchecked")

+ 5 - 0
server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java

@@ -8,6 +8,7 @@
 
 package org.elasticsearch.cluster.metadata;
 
+import org.apache.lucene.util.automaton.Automaton;
 import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.IndicesRequest;
@@ -786,6 +787,10 @@ public class IndexNameExpressionResolver {
         return systemIndexAccessLevelPredicate;
     }
 
+    public Automaton getSystemNameAutomaton() {
+        return systemIndices.getSystemNameAutomaton();
+    }
+
     public Predicate<String> getNetNewSystemIndexPredicate() {
         return systemIndices::isNetNewSystemIndex;
     }

+ 44 - 20
server/src/main/java/org/elasticsearch/indices/SystemIndices.java

@@ -42,7 +42,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
-import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -66,10 +65,12 @@ public class SystemIndices {
         TASKS_FEATURE_NAME, new Feature(TASKS_FEATURE_NAME, "Manages task results", List.of(TASKS_DESCRIPTOR))
     );
 
-    private final CharacterRunAutomaton systemIndexAutomaton;
-    private final CharacterRunAutomaton systemDataStreamIndicesAutomaton;
+    private final Automaton systemNameAutomaton;
     private final CharacterRunAutomaton netNewSystemIndexAutomaton;
-    private final Predicate<String> systemDataStreamAutomaton;
+    private final CharacterRunAutomaton systemNameRunAutomaton;
+    private final CharacterRunAutomaton systemIndexRunAutomaton;
+    private final CharacterRunAutomaton systemDataStreamIndicesRunAutomaton;
+    private final Predicate<String> systemDataStreamPredicate;
     private final Map<String, Feature> featureDescriptors;
     private final Map<String, CharacterRunAutomaton> productToSystemIndicesMatcher;
     private final ExecutorSelector executorSelector;
@@ -83,12 +84,19 @@ public class SystemIndices {
         featureDescriptors = buildSystemIndexDescriptorMap(pluginAndModulesDescriptors);
         checkForOverlappingPatterns(featureDescriptors);
         checkForDuplicateAliases(this.getSystemIndexDescriptors());
-        this.systemIndexAutomaton = buildIndexCharacterRunAutomaton(featureDescriptors);
+        Automaton systemIndexAutomata = buildIndexAutomaton(featureDescriptors);
+        this.systemIndexRunAutomaton = new CharacterRunAutomaton(systemIndexAutomata);
+        Automaton systemDataStreamIndicesAutomata = buildDataStreamBackingIndicesAutomaton(featureDescriptors);
+        this.systemDataStreamIndicesRunAutomaton = new CharacterRunAutomaton(systemDataStreamIndicesAutomata);
+        this.systemDataStreamPredicate = buildDataStreamNamePredicate(featureDescriptors);
         this.netNewSystemIndexAutomaton = buildNetNewIndexCharacterRunAutomaton(featureDescriptors);
-        this.systemDataStreamIndicesAutomaton = buildDataStreamBackingIndicesAutomaton(featureDescriptors);
-        this.systemDataStreamAutomaton = buildDataStreamNamePredicate(featureDescriptors);
         this.productToSystemIndicesMatcher = getProductToSystemIndicesMap(featureDescriptors);
         this.executorSelector = new ExecutorSelector(this);
+        this.systemNameAutomaton = MinimizationOperations.minimize(
+            Operations.union(List.of(systemIndexAutomata, systemDataStreamIndicesAutomata, buildDataStreamAutomaton(featureDescriptors))),
+            Integer.MAX_VALUE
+        );
+        this.systemNameRunAutomaton = new CharacterRunAutomaton(systemNameAutomaton);
     }
 
     private static void checkForDuplicateAliases(Collection<SystemIndexDescriptor> descriptors) {
@@ -150,7 +158,7 @@ public class SystemIndices {
      * is checked against index names, aliases, data stream names, and the names of indices that back a system data stream.
      */
     public boolean isSystemName(String name) {
-        return isSystemIndex(name) || isSystemDataStream(name) || isSystemIndexBackingDataStream(name);
+        return systemNameRunAutomaton.run(name);
     }
 
     /**
@@ -169,7 +177,7 @@ public class SystemIndices {
      * @return true if the index name matches a pattern from a {@link SystemIndexDescriptor}
      */
     public boolean isSystemIndex(String indexName) {
-        return systemIndexAutomaton.run(indexName);
+        return systemIndexRunAutomaton.run(indexName);
     }
 
     /**
@@ -177,14 +185,22 @@ public class SystemIndices {
      * {@link SystemDataStreamDescriptor}
      */
     public boolean isSystemDataStream(String name) {
-        return systemDataStreamAutomaton.test(name);
+        return systemDataStreamPredicate.test(name);
     }
 
     /**
      * Determines whether the provided name matches that of an index that backs a system data stream.
      */
     public boolean isSystemIndexBackingDataStream(String name) {
-        return systemDataStreamIndicesAutomaton.run(name);
+        return systemDataStreamIndicesRunAutomaton.run(name);
+    }
+
+    /**
+     * @return An {@link Automaton} that tests whether strings are names of system indices, aliases, or
+     * data streams.
+     */
+    public Automaton getSystemNameAutomaton() {
+        return systemNameAutomaton;
     }
 
     public boolean isNetNewSystemIndex(String indexName) {
@@ -293,11 +309,11 @@ public class SystemIndices {
         return featureDescriptors;
     }
 
-    private static CharacterRunAutomaton buildIndexCharacterRunAutomaton(Map<String, Feature> descriptors) {
+    private static Automaton buildIndexAutomaton(Map<String, Feature> descriptors) {
         Optional<Automaton> automaton = descriptors.values().stream()
             .map(SystemIndices::featureToIndexAutomaton)
             .reduce(Operations::union);
-        return new CharacterRunAutomaton(MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE));
+        return MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE);
     }
 
     private static CharacterRunAutomaton buildNetNewIndexCharacterRunAutomaton(Map<String, Feature> featureDescriptors) {
@@ -317,19 +333,26 @@ public class SystemIndices {
         return systemIndexAutomaton.orElse(EMPTY);
     }
 
-    private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> descriptors) {
-        Set<String> systemDataStreamNames = descriptors.values().stream()
+    private static Automaton buildDataStreamAutomaton(Map<String, Feature> descriptors) {
+        Optional<Automaton> automaton = descriptors.values().stream()
             .flatMap(feature -> feature.getDataStreamDescriptors().stream())
             .map(SystemDataStreamDescriptor::getDataStreamName)
-            .collect(Collectors.toUnmodifiableSet());
-        return systemDataStreamNames::contains;
+            .map(dsName -> SystemIndexDescriptor.buildAutomaton(dsName, null))
+            .reduce(Operations::union);
+
+        return automaton.isPresent() ? MinimizationOperations.minimize(automaton.get(), Integer.MAX_VALUE) : EMPTY;
+    }
+
+    private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> descriptors) {
+        CharacterRunAutomaton characterRunAutomaton = new CharacterRunAutomaton(buildDataStreamAutomaton(descriptors));
+        return characterRunAutomaton::run;
     }
 
-    private static CharacterRunAutomaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> descriptors) {
+    private static Automaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> descriptors) {
         Optional<Automaton> automaton = descriptors.values().stream()
             .map(SystemIndices::featureToDataStreamBackingIndicesAutomaton)
             .reduce(Operations::union);
-        return new CharacterRunAutomaton(automaton.orElse(EMPTY));
+        return MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE);
     }
 
     private static Automaton featureToDataStreamBackingIndicesAutomaton(Feature feature) {
@@ -343,7 +366,7 @@ public class SystemIndices {
     }
 
     public SystemDataStreamDescriptor validateDataStreamAccess(String dataStreamName, ThreadContext threadContext) {
-        if (systemDataStreamAutomaton.test(dataStreamName)) {
+        if (systemDataStreamPredicate.test(dataStreamName)) {
             SystemDataStreamDescriptor dataStreamDescriptor = featureDescriptors.values().stream()
                 .flatMap(feature -> feature.getDataStreamDescriptors().stream())
                 .filter(descriptor -> descriptor.getDataStreamName().equals(dataStreamName))
@@ -405,6 +428,7 @@ public class SystemIndices {
     /**
      * Determines what level of system index access should be allowed in the current context.
      *
+     * @param threadContext the current thread context that has headers associated with the current request
      * @return {@link SystemIndexAccessLevel#ALL} if unrestricted system index access should be allowed,
      * {@link SystemIndexAccessLevel#RESTRICTED} if a subset of system index access should be allowed, or
      * {@link SystemIndexAccessLevel#NONE} if no system index access should be allowed.

+ 1 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncTaskIndexService.java

@@ -142,7 +142,7 @@ public final class AsyncTaskIndexService<R extends AsyncResponse<R>> {
 
     public static SystemIndexDescriptor getSystemIndexDescriptor() {
         return SystemIndexDescriptor.builder()
-            .setIndexPattern(XPackPlugin.ASYNC_RESULTS_INDEX)
+            .setIndexPattern(XPackPlugin.ASYNC_RESULTS_INDEX + "*")
             .setDescription("Async search results")
             .setPrimaryIndex(XPackPlugin.ASYNC_RESULTS_INDEX)
             .setMappings(mappings())

+ 110 - 64
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/IndicesPermission.java

@@ -7,19 +7,19 @@
 package org.elasticsearch.xpack.core.security.authz.permission;
 
 import org.apache.lucene.util.automaton.Automaton;
+import org.apache.lucene.util.automaton.CharacterRunAutomaton;
 import org.apache.lucene.util.automaton.Operations;
 import org.elasticsearch.action.admin.indices.mapping.put.AutoPutMappingAction;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingAction;
 import org.elasticsearch.cluster.metadata.IndexAbstraction;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.core.Nullable;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.logging.DeprecationCategory;
 import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.regex.Regex;
+import org.elasticsearch.core.Nullable;
 import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
-import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
 import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.elasticsearch.xpack.core.security.support.StringMatcher;
 
@@ -35,6 +35,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
@@ -47,25 +48,65 @@ public final class IndicesPermission {
 
     private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(IndicesPermission.class);
 
-    public static final IndicesPermission NONE = new IndicesPermission();
+    public static final IndicesPermission NONE = new IndicesPermission(Automatons.EMPTY, new Group[0]);
 
     private static final Set<String> PRIVILEGE_NAME_SET_BWC_ALLOW_MAPPING_UPDATE = Set.of("create", "create_doc", "index", "write");
 
     private final Map<String, Predicate<IndexAbstraction>> allowedIndicesMatchersForAction = new ConcurrentHashMap<>();
 
+    private final Automaton restrictedNamesAutomaton;
     private final Group[] groups;
+    private final CharacterRunAutomaton characterRunAutomaton;
+
+    public static class Builder {
+
+        Automaton restrictedNamesAutomaton;
+        List<Group> groups = new ArrayList<>();
+
+        public Builder(Automaton restrictedNamesAutomaton) {
+            this.restrictedNamesAutomaton = restrictedNamesAutomaton;
+        }
+
+        public Builder addGroup(IndexPrivilege privilege,
+                      FieldPermissions fieldPermissions,
+                      @Nullable Set<BytesReference> query,
+                      boolean allowRestrictedIndices,
+                      String... indices) {
+            groups.add(new Group(privilege, fieldPermissions, query, allowRestrictedIndices, restrictedNamesAutomaton, indices));
+            return this;
+        }
+
+        public IndicesPermission build() {
+            return new IndicesPermission(restrictedNamesAutomaton, groups.toArray(new Group[0]));
+        }
+    }
 
-    public IndicesPermission(Group... groups) {
+    private IndicesPermission(Automaton restrictedNamesAutomaton, Group[] groups) {
+        this.restrictedNamesAutomaton = restrictedNamesAutomaton;
+        this.characterRunAutomaton = new CharacterRunAutomaton(restrictedNamesAutomaton);
         this.groups = groups;
     }
 
-    private static StringMatcher indexMatcher(Collection<String> ordinaryIndices, Collection<String> restrictedIndices) {
+    /**
+     * This function constructs an index matcher that can be used to find indices allowed by
+     * permissions groups.
+     *
+     * @param ordinaryIndices A list of ordinary indices. If this collection contains restricted indices,
+     *                        according to the restrictedNamesAutomaton, they will not be matched.
+     * @param restrictedIndices A list of restricted index names. All of these will be matched.
+     * @return A matcher that will match all non-restricted index names in the ordinaryIndices
+     * collection and all index names in the restrictedIndices collection.
+     */
+    private StringMatcher indexMatcher(Collection<String> ordinaryIndices, Collection<String> restrictedIndices) {
         StringMatcher matcher;
         if (ordinaryIndices.isEmpty()) {
             matcher = StringMatcher.of(restrictedIndices);
         } else {
-            matcher = StringMatcher.of(ordinaryIndices)
-                    .and("<not-restricted>", index -> false == RestrictedIndicesNames.isRestricted(index));
+            matcher = StringMatcher.of(ordinaryIndices);
+            if (restrictedNamesAutomaton != null) {
+                CharacterRunAutomaton characterRunAutomaton = new CharacterRunAutomaton(restrictedNamesAutomaton);
+                matcher = matcher.and("<not-restricted>", name -> characterRunAutomaton.run(name) == false);
+            }
             if (restrictedIndices.isEmpty() == false) {
                 matcher = StringMatcher.of(restrictedIndices).or(matcher);
             }
@@ -82,7 +123,39 @@ public final class IndicesPermission {
      * has the privilege for executing the given action on.
      */
     public Predicate<IndexAbstraction> allowedIndicesMatcher(String action) {
-        return allowedIndicesMatchersForAction.computeIfAbsent(action, a -> Group.buildIndexMatcherPredicateForAction(a, groups));
+        return allowedIndicesMatchersForAction.computeIfAbsent(action, this::buildIndexMatcherPredicateForAction);
+    }
+
+    private Predicate<IndexAbstraction> buildIndexMatcherPredicateForAction(String action) {
+        final Set<String> ordinaryIndices = new HashSet<>();
+        final Set<String> restrictedIndices = new HashSet<>();
+        final Set<String> grantMappingUpdatesOnIndices = new HashSet<>();
+        final Set<String> grantMappingUpdatesOnRestrictedIndices = new HashSet<>();
+        final boolean isMappingUpdateAction = isMappingUpdateAction(action);
+        for (final Group group : groups) {
+            if (group.actionMatcher.test(action)) {
+                if (group.allowRestrictedIndices) {
+                    restrictedIndices.addAll(Arrays.asList(group.indices()));
+                } else {
+                    ordinaryIndices.addAll(Arrays.asList(group.indices()));
+                }
+            } else if (isMappingUpdateAction && containsPrivilegeThatGrantsMappingUpdatesForBwc(group)) {
+                // special BWC case for certain privileges: allow put mapping on indices and aliases (but not on data streams), even if
+                // the privilege definition does not currently allow it
+                if (group.allowRestrictedIndices) {
+                    grantMappingUpdatesOnRestrictedIndices.addAll(Arrays.asList(group.indices()));
+                } else {
+                    grantMappingUpdatesOnIndices.addAll(Arrays.asList(group.indices()));
+                }
+            }
+        }
+        final StringMatcher nameMatcher = indexMatcher(ordinaryIndices, restrictedIndices);
+        final StringMatcher bwcSpecialCaseMatcher = indexMatcher(grantMappingUpdatesOnIndices, grantMappingUpdatesOnRestrictedIndices);
+        return indexAbstraction ->
+            nameMatcher.test(indexAbstraction.getName()) ||
+                (indexAbstraction.getType() != IndexAbstraction.Type.DATA_STREAM &&
+                    (indexAbstraction.getParentDataStream() == null) &&
+                    bwcSpecialCaseMatcher.test(indexAbstraction.getName()));
     }
 
     /**
@@ -118,13 +191,12 @@ public final class IndicesPermission {
         for (String forIndexPattern : checkForIndexPatterns) {
             Automaton checkIndexAutomaton = Automatons.patterns(forIndexPattern);
             if (false == allowRestrictedIndices && false == isConcreteRestrictedIndex(forIndexPattern)) {
-                checkIndexAutomaton = Automatons.minusAndMinimize(checkIndexAutomaton, RestrictedIndicesNames.NAMES_AUTOMATON);
+                checkIndexAutomaton = Automatons.minusAndMinimize(checkIndexAutomaton, restrictedNamesAutomaton);
             }
             if (false == Operations.isEmpty(checkIndexAutomaton)) {
                 Automaton allowedIndexPrivilegesAutomaton = null;
                 for (Group group : groups) {
-                    final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group,
-                            g -> IndicesPermission.Group.buildIndexMatcherAutomaton(g.allowRestrictedIndices(), g.indices()));
+                    final Automaton groupIndexAutomaton = predicateCache.computeIfAbsent(group, Group::getIndexMatcherAutomaton);
                     if (Operations.subsetOf(checkIndexAutomaton, groupIndexAutomaton)) {
                         if (allowedIndexPrivilegesAutomaton != null) {
                             allowedIndexPrivilegesAutomaton = Automatons
@@ -231,20 +303,20 @@ public final class IndicesPermission {
                                 // if more than one permission matches for a concrete index here and if
                                 // a single permission doesn't have a role query then DLS will not be
                                 // applied even when other permissions do have a role query
-                                permissions.setAllowAll(true);
+                                permissions.setAllowAll();
                             }
                         }
                         if (false == actionCheck) {
                             for (String privilegeName : group.privilege.name()) {
                                 if (PRIVILEGE_NAME_SET_BWC_ALLOW_MAPPING_UPDATE.contains(privilegeName)) {
-                                    bwcDeprecationLogActions.add(() -> {
+                                    bwcDeprecationLogActions.add(() ->
                                         deprecationLogger.deprecate(DeprecationCategory.SECURITY,
                                             "[" + indexOrAlias + "] mapping update for ingest privilege [" +
                                                 privilegeName + "]", "the index privilege [" + privilegeName + "] allowed the update " +
                                                 "mapping action [" + action + "] on index [" + indexOrAlias + "], this privilege " +
                                                 "will not permit mapping updates in the next major release - users who require access " +
-                                                "to update mappings must be granted explicit privileges");
-                                    });
+                                                "to update mappings must be granted explicit privileges")
+                                    );
                                 }
                             }
                         }
@@ -255,7 +327,7 @@ public final class IndicesPermission {
             if (false == granted && bwcGrantMappingUpdate) {
                 // the action is granted only due to the deprecated behaviour of certain privileges
                 granted = true;
-                bwcDeprecationLogActions.forEach(deprecationLogAction -> deprecationLogAction.run());
+                bwcDeprecationLogActions.forEach(Runnable::run);
             }
 
             if (concreteIndices.isEmpty()) {
@@ -297,7 +369,7 @@ public final class IndicesPermission {
         if (Regex.isSimpleMatchPattern(indexPattern) || Automatons.isLuceneRegex(indexPattern)) {
             return false;
         }
-        return RestrictedIndicesNames.isRestricted(indexPattern);
+        return characterRunAutomaton.run(indexPattern);
     }
 
     private static boolean isMappingUpdateAction(String action) {
@@ -309,10 +381,13 @@ public final class IndicesPermission {
     }
 
     public static class Group {
+        public static final Group[] EMPTY_ARRAY = new Group[0];
+
         private final IndexPrivilege privilege;
         private final Predicate<String> actionMatcher;
         private final String[] indices;
         private final Predicate<String> indexNameMatcher;
+        private final Supplier<Automaton> indexNameAutomaton;
         private final FieldPermissions fieldPermissions;
         private final Set<BytesReference> query;
         // by default certain restricted indices are exempted when granting privileges, as they should generally be hidden for ordinary
@@ -321,15 +396,25 @@ public final class IndicesPermission {
         private final boolean allowRestrictedIndices;
 
         public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable Set<BytesReference> query,
-                boolean allowRestrictedIndices, String... indices) {
+                     boolean allowRestrictedIndices, Automaton restrictedNamesAutomaton, String... indices) {
             assert indices.length != 0;
             this.privilege = privilege;
             this.actionMatcher = privilege.predicate();
             this.indices = indices;
-            this.indexNameMatcher = StringMatcher.of(Arrays.asList(indices));
+            this.allowRestrictedIndices = allowRestrictedIndices;
+            ConcurrentHashMap<String[], Automaton> indexNameAutomatonMemo = new ConcurrentHashMap<>(1);
+            if (allowRestrictedIndices) {
+                this.indexNameMatcher = StringMatcher.of(indices);
+                this.indexNameAutomaton = () -> indexNameAutomatonMemo.computeIfAbsent(indices, k -> Automatons.patterns(indices));
+            } else {
+                final CharacterRunAutomaton restrictedNamesRunAutomaton = new CharacterRunAutomaton(restrictedNamesAutomaton);
+                this.indexNameMatcher = StringMatcher.of(indices)
+                        .and(name -> restrictedNamesRunAutomaton.run(name) == false);
+                this.indexNameAutomaton = () -> indexNameAutomatonMemo.computeIfAbsent(indices,
+                        k -> Automatons.minusAndMinimize(Automatons.patterns(indices), restrictedNamesAutomaton));
+            }
             this.fieldPermissions = Objects.requireNonNull(fieldPermissions);
             this.query = query;
-            this.allowRestrictedIndices = allowRestrictedIndices;
         }
 
         public IndexPrivilege privilege() {
@@ -355,7 +440,7 @@ public final class IndicesPermission {
 
         private boolean checkIndex(String index) {
             assert index != null;
-            return indexNameMatcher.test(index) && (allowRestrictedIndices || (false == RestrictedIndicesNames.isRestricted(index)));
+            return indexNameMatcher.test(index);
         }
 
         boolean hasQuery() {
@@ -366,47 +451,8 @@ public final class IndicesPermission {
             return allowRestrictedIndices;
         }
 
-        public static Automaton buildIndexMatcherAutomaton(boolean allowRestrictedIndices, String... indices) {
-            final Automaton indicesAutomaton = Automatons.patterns(indices);
-            if (allowRestrictedIndices) {
-                return indicesAutomaton;
-            } else {
-                return Automatons.minusAndMinimize(indicesAutomaton, RestrictedIndicesNames.NAMES_AUTOMATON);
-            }
-        }
-
-        private static Predicate<IndexAbstraction> buildIndexMatcherPredicateForAction(String action, Group... groups) {
-            final Set<String> ordinaryIndices = new HashSet<>();
-            final Set<String> restrictedIndices = new HashSet<>();
-            final Set<String> grantMappingUpdatesOnIndices = new HashSet<>();
-            final Set<String> grantMappingUpdatesOnRestrictedIndices = new HashSet<>();
-            final boolean isMappingUpdateAction = isMappingUpdateAction(action);
-            for (final Group group : groups) {
-                if (group.actionMatcher.test(action)) {
-                    if (group.allowRestrictedIndices) {
-                        restrictedIndices.addAll(Arrays.asList(group.indices()));
-                    } else {
-                        ordinaryIndices.addAll(Arrays.asList(group.indices()));
-                    }
-                } else if (isMappingUpdateAction && containsPrivilegeThatGrantsMappingUpdatesForBwc(group)) {
-                    // special BWC case for certain privileges: allow put mapping on indices and aliases (but not on data streams), even if
-                    // the privilege definition does not currently allow it
-                    if (group.allowRestrictedIndices) {
-                        grantMappingUpdatesOnRestrictedIndices.addAll(Arrays.asList(group.indices()));
-                    } else {
-                        grantMappingUpdatesOnIndices.addAll(Arrays.asList(group.indices()));
-                    }
-                }
-            }
-            final StringMatcher nameMatcher = indexMatcher(ordinaryIndices, restrictedIndices);
-            final StringMatcher bwcSpecialCaseMatcher = indexMatcher(grantMappingUpdatesOnIndices,
-                    grantMappingUpdatesOnRestrictedIndices);
-            return indexAbstraction -> {
-                return nameMatcher.test(indexAbstraction.getName()) ||
-                        (indexAbstraction.getType() != IndexAbstraction.Type.DATA_STREAM &&
-                                (indexAbstraction.getParentDataStream() == null) &&
-                                bwcSpecialCaseMatcher.test(indexAbstraction.getName()));
-            };
+        public Automaton getIndexMatcherAutomaton() {
+            return indexNameAutomaton.get();
         }
     }
 
@@ -428,8 +474,8 @@ public final class IndicesPermission {
             return allowAll;
         }
 
-        private void setAllowAll(boolean allowAll) {
-            this.allowAll = allowAll;
+        private void setAllowAll() {
+            this.allowAll = true;
         }
     }
 }

+ 62 - 23
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java

@@ -8,10 +8,10 @@ package org.elasticsearch.xpack.core.security.authz.permission;
 
 import org.apache.lucene.util.automaton.Automaton;
 import org.elasticsearch.cluster.metadata.IndexAbstraction;
-import org.elasticsearch.core.Nullable;
 import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.core.Tuple;
 import org.elasticsearch.common.util.set.Sets;
+import org.elasticsearch.core.Nullable;
+import org.elasticsearch.core.Tuple;
 import org.elasticsearch.transport.TransportRequest;
 import org.elasticsearch.xpack.core.security.authc.Authentication;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
@@ -23,6 +23,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeRes
 import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
 import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,7 +37,7 @@ import java.util.function.Predicate;
 
 public class Role {
 
-    public static final Role EMPTY = Role.builder("__empty").build();
+    public static final Role EMPTY = Role.builder(Automatons.EMPTY, "__empty").build();
 
     private final String[] names;
     private final ClusterPermission cluster;
@@ -72,12 +73,19 @@ public class Role {
         return runAs;
     }
 
-    public static Builder builder(String... names) {
-        return new Builder(names);
+    /**
+     * @param restrictedIndices An automaton that can determine whether a string names
+     *                          a restricted index. For simple unit tests, this can be
+     *                          {@link Automatons#EMPTY}.
+     * @param names Names of roles.
+     * @return A builder for a role
+     */
+    public static Builder builder(Automaton restrictedIndices, String... names) {
+        return new Builder(restrictedIndices, names);
     }
 
-    public static Builder builder(RoleDescriptor rd, FieldPermissionsCache fieldPermissionsCache) {
-        return new Builder(rd, fieldPermissionsCache);
+    public static Builder builder(RoleDescriptor rd, FieldPermissionsCache fieldPermissionsCache, Automaton restrictedIndices) {
+        return new Builder(rd, fieldPermissionsCache, restrictedIndices);
     }
 
     /**
@@ -191,27 +199,30 @@ public class Role {
         private final String[] names;
         private ClusterPermission cluster = ClusterPermission.NONE;
         private RunAsPermission runAs = RunAsPermission.NONE;
-        private List<IndicesPermission.Group> groups = new ArrayList<>();
-        private List<Tuple<ApplicationPrivilege, Set<String>>> applicationPrivs = new ArrayList<>();
+        private final List<IndicesPermissionGroupDefinition> groups = new ArrayList<>();
+        private final List<Tuple<ApplicationPrivilege, Set<String>>> applicationPrivs = new ArrayList<>();
+        private final Automaton restrictedNamesAutomaton;
 
-        private Builder(String[] names) {
+        private Builder(Automaton restrictedNamesAutomaton, String[] names) {
+            this.restrictedNamesAutomaton = restrictedNamesAutomaton;
             this.names = names;
         }
 
-        private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissionsCache) {
+        private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissionsCache, Automaton restrictedNamesAutomaton) {
             this.names = new String[] { rd.getName() };
             cluster(Sets.newHashSet(rd.getClusterPrivileges()), Arrays.asList(rd.getConditionalClusterPrivileges()));
             groups.addAll(convertFromIndicesPrivileges(rd.getIndicesPrivileges(), fieldPermissionsCache));
 
             final RoleDescriptor.ApplicationResourcePrivileges[] applicationPrivileges = rd.getApplicationPrivileges();
-            for (int i = 0; i < applicationPrivileges.length; i++) {
-                applicationPrivs.add(convertApplicationPrivilege(rd.getName(), i, applicationPrivileges[i]));
+            for (RoleDescriptor.ApplicationResourcePrivileges applicationPrivilege : applicationPrivileges) {
+                applicationPrivs.add(convertApplicationPrivilege(applicationPrivilege));
             }
 
             String[] rdRunAs = rd.getRunAs();
             if (rdRunAs != null && rdRunAs.length > 0) {
                 this.runAs(new Privilege(Sets.newHashSet(rdRunAs), rdRunAs));
             }
+            this.restrictedNamesAutomaton = restrictedNamesAutomaton;
         }
 
         public Builder cluster(Set<String> privilegeNames, Iterable<ConfigurableClusterPrivilege> configurableClusterPrivileges) {
@@ -234,13 +245,13 @@ public class Role {
         }
 
         public Builder add(IndexPrivilege privilege, String... indices) {
-            groups.add(new IndicesPermission.Group(privilege, FieldPermissions.DEFAULT, null, false, indices));
+            groups.add(new IndicesPermissionGroupDefinition(privilege, FieldPermissions.DEFAULT, null, false, indices));
             return this;
         }
 
         public Builder add(FieldPermissions fieldPermissions, Set<BytesReference> query, IndexPrivilege privilege,
                 boolean allowRestrictedIndices, String... indices) {
-            groups.add(new IndicesPermission.Group(privilege, fieldPermissions, query, allowRestrictedIndices, indices));
+            groups.add(new IndicesPermissionGroupDefinition(privilege, fieldPermissions, query, allowRestrictedIndices, indices));
             return this;
         }
 
@@ -250,16 +261,25 @@ public class Role {
         }
 
         public Role build() {
-            IndicesPermission indices = groups.isEmpty() ? IndicesPermission.NONE :
-                new IndicesPermission(groups.toArray(new IndicesPermission.Group[groups.size()]));
+            final IndicesPermission indices;
+            if (groups.isEmpty()) {
+                indices = IndicesPermission.NONE;
+            } else {
+                IndicesPermission.Builder indicesBuilder = new IndicesPermission.Builder(restrictedNamesAutomaton);
+                for (IndicesPermissionGroupDefinition group : groups) {
+                    indicesBuilder.addGroup(group.privilege, group.fieldPermissions, group.query, group.allowRestrictedIndices,
+                            group.indices);
+                }
+                indices = indicesBuilder.build();
+            }
             final ApplicationPermission applicationPermission
                 = applicationPrivs.isEmpty() ? ApplicationPermission.NONE : new ApplicationPermission(applicationPrivs);
             return new Role(names, cluster, indices, applicationPermission, runAs);
         }
 
-        static List<IndicesPermission.Group> convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges,
+        static List<IndicesPermissionGroupDefinition> convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges,
                                                                           @Nullable FieldPermissionsCache fieldPermissionsCache) {
-            List<IndicesPermission.Group> list = new ArrayList<>(indicesPrivileges.length);
+            List<IndicesPermissionGroupDefinition> list = new ArrayList<>(indicesPrivileges.length);
             for (RoleDescriptor.IndicesPrivileges privilege : indicesPrivileges) {
                 final FieldPermissions fieldPermissions;
                 if (fieldPermissionsCache != null) {
@@ -269,19 +289,38 @@ public class Role {
                         new FieldPermissionsDefinition(privilege.getGrantedFields(), privilege.getDeniedFields()));
                 }
                 final Set<BytesReference> query = privilege.getQuery() == null ? null : Collections.singleton(privilege.getQuery());
-                list.add(new IndicesPermission.Group(IndexPrivilege.get(Sets.newHashSet(privilege.getPrivileges())), fieldPermissions,
-                        query, privilege.allowRestrictedIndices(), privilege.getIndices()));
+                list.add(new IndicesPermissionGroupDefinition(IndexPrivilege.get(Sets.newHashSet(privilege.getPrivileges())),
+                    fieldPermissions, query, privilege.allowRestrictedIndices(), privilege.getIndices()));
             }
             return list;
         }
 
-        static Tuple<ApplicationPrivilege, Set<String>> convertApplicationPrivilege(String role, int index,
-                                                                                    RoleDescriptor.ApplicationResourcePrivileges arp) {
+        static Tuple<ApplicationPrivilege, Set<String>> convertApplicationPrivilege(RoleDescriptor.ApplicationResourcePrivileges arp) {
             return new Tuple<>(new ApplicationPrivilege(arp.getApplication(),
                 Sets.newHashSet(arp.getPrivileges()),
                 arp.getPrivileges()
             ), Sets.newHashSet(arp.getResources()));
         }
+
+        private static class IndicesPermissionGroupDefinition {
+            private final IndexPrivilege privilege;
+            private final FieldPermissions fieldPermissions;
+            private final @Nullable Set<BytesReference> query;
+            private final boolean allowRestrictedIndices;
+            private final String[] indices;
+
+            private IndicesPermissionGroupDefinition(IndexPrivilege privilege,
+                                                     FieldPermissions fieldPermissions,
+                                                     @Nullable Set<BytesReference> query,
+                                                     boolean allowRestrictedIndices,
+                                                     String... indices) {
+                this.privilege = privilege;
+                this.fieldPermissions = fieldPermissions;
+                this.query = query;
+                this.allowRestrictedIndices = allowRestrictedIndices;
+                this.indices = indices;
+            }
+        }
     }
 
 }

+ 2 - 3
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java

@@ -17,7 +17,6 @@ import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
 import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
-import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
 import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges;
 import org.elasticsearch.xpack.core.security.support.MetadataUtils;
@@ -50,7 +49,6 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
             },
             null, new String[] { "*" },
             MetadataUtils.DEFAULT_RESERVED_METADATA, Collections.emptyMap());
-    public static final Role SUPERUSER_ROLE = Role.builder(SUPERUSER_ROLE_DESCRIPTOR, null).build();
     private static final Map<String, RoleDescriptor> RESERVED_ROLES = initializeReservedRoles();
 
     private static Map<String, RoleDescriptor> initializeReservedRoles() {
@@ -273,7 +271,8 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
                 .put("logstash_admin", new RoleDescriptor("logstash_admin", new String[] {"manage_logstash_pipelines"},
                     new RoleDescriptor.IndicesPrivileges[] {
                         RoleDescriptor.IndicesPrivileges.builder().indices(".logstash*")
-                                .privileges("create", "delete", "index", "manage", "read").build() },
+                                .privileges("create", "delete", "index", "manage", "read")
+                                .build() },
                         null, MetadataUtils.DEFAULT_RESERVED_METADATA))
                 .put("rollup_user", new RoleDescriptor("rollup_user", new String[] { "monitor_rollup" },
                         null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))

+ 0 - 14
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/index/RestrictedIndicesNames.java

@@ -7,11 +7,8 @@
 
 package org.elasticsearch.xpack.core.security.index;
 
-import org.apache.lucene.util.automaton.Automaton;
 import org.elasticsearch.common.util.set.Sets;
-import org.elasticsearch.xpack.core.security.support.Automatons;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Set;
 
@@ -23,21 +20,10 @@ public final class RestrictedIndicesNames {
     public static final String INTERNAL_SECURITY_TOKENS_INDEX_7 = ".security-tokens-7";
     public static final String SECURITY_TOKENS_ALIAS = ".security-tokens";
 
-    // public for tests
-    public static final String ASYNC_SEARCH_PREFIX = ".async-search";
-    private static final Automaton ASYNC_SEARCH_AUTOMATON = Automatons.patterns(ASYNC_SEARCH_PREFIX + "*");
-
     // public for tests
     public static final Set<String> 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 boolean isRestricted(String concreteIndexName) {
-        return RESTRICTED_NAMES.contains(concreteIndexName) || concreteIndexName.startsWith(ASYNC_SEARCH_PREFIX);
-    }
-
-    public static final Automaton NAMES_AUTOMATON = Automatons.unionAndMinimize(Arrays.asList(Automatons.patterns(RESTRICTED_NAMES),
-            ASYNC_SEARCH_AUTOMATON));
-
     private RestrictedIndicesNames() {
     }
 }

+ 4 - 5
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/AsyncSearchUser.java

@@ -6,9 +6,8 @@
  */
 package org.elasticsearch.xpack.core.security.user;
 
+import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
-import org.elasticsearch.xpack.core.security.authz.permission.Role;
-import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
 import org.elasticsearch.xpack.core.security.support.MetadataUtils;
 
 public class AsyncSearchUser extends User {
@@ -16,11 +15,11 @@ public class AsyncSearchUser extends User {
     public static final String NAME = UsernamesField.ASYNC_SEARCH_NAME;
     public static final AsyncSearchUser INSTANCE = new AsyncSearchUser();
     public static final String ROLE_NAME = UsernamesField.ASYNC_SEARCH_ROLE;
-    public static final Role ROLE = Role.builder(new RoleDescriptor(ROLE_NAME,
+    public static final RoleDescriptor ROLE_DESCRIPTOR = new RoleDescriptor(ROLE_NAME,
             new String[] { "cancel_task" },
             new RoleDescriptor.IndicesPrivileges[] {
                     RoleDescriptor.IndicesPrivileges.builder()
-                            .indices(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + "*")
+                            .indices(XPackPlugin.ASYNC_RESULTS_INDEX + "*")
                             .privileges("all")
                             .allowRestrictedIndices(true).build(),
             },
@@ -28,7 +27,7 @@ public class AsyncSearchUser extends User {
             null,
             null,
             MetadataUtils.DEFAULT_RESERVED_METADATA,
-            null), null).build();
+            null);
 
     private AsyncSearchUser() {
         super(NAME, ROLE_NAME);

+ 12 - 9
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/XPackUser.java

@@ -7,7 +7,6 @@
 package org.elasticsearch.xpack.core.security.user;
 
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
-import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.index.IndexAuditTrailField;
 import org.elasticsearch.xpack.core.security.support.MetadataUtils;
 
@@ -18,14 +17,18 @@ public class XPackUser extends User {
 
     public static final String NAME = UsernamesField.XPACK_NAME;
     public static final String ROLE_NAME = UsernamesField.XPACK_ROLE;
-    public static final Role ROLE = Role.builder(new RoleDescriptor(ROLE_NAME, new String[] { "all" },
-            new RoleDescriptor.IndicesPrivileges[] {
-                    RoleDescriptor.IndicesPrivileges.builder().indices("/@&~(\\.security.*)/").privileges("all").build(),
-                    RoleDescriptor.IndicesPrivileges.builder().indices(IndexAuditTrailField.INDEX_NAME_PREFIX + "-*")
-                            .privileges("read").build()
-            },
-            new String[] { "*" },
-            MetadataUtils.DEFAULT_RESERVED_METADATA), null).build();
+    public static final RoleDescriptor ROLE_DESCRIPTOR = new RoleDescriptor(ROLE_NAME, new String[] { "all" },
+        new RoleDescriptor.IndicesPrivileges[] {
+            RoleDescriptor.IndicesPrivileges.builder()
+                .indices("/@&~(\\.security.*)&~(\\.async-search.*)/")
+                .privileges("all")
+                .allowRestrictedIndices(true)
+                .build(),
+            RoleDescriptor.IndicesPrivileges.builder().indices(IndexAuditTrailField.INDEX_NAME_PREFIX + "-*")
+                .privileges("read").build()
+        },
+        new String[] { "*" },
+        MetadataUtils.DEFAULT_RESERVED_METADATA);
     public static final XPackUser INSTANCE = new XPackUser();
 
     private XPackUser() {

+ 27 - 26
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java

@@ -53,8 +53,8 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testRoleConstructorWithLimitedRole() {
-        Role fromRole = Role.builder("a-role").build();
-        Role limitedByRole = Role.builder("limited-role").build();
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").build();
+        Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").build();
         Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
         assertNotNull(role);
         assertThat(role.names(), is(limitedByRole.names()));
@@ -74,7 +74,7 @@ public class LimitedRoleTests extends ESTestCase {
                 .putAlias(AliasMetadata.builder("_alias1"));
         Metadata md = Metadata.builder().put(imbBuilder).put(imbBuilder1).build();
         FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
-        Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
                 .add(IndexPrivilege.ALL, "_index").add(IndexPrivilege.CREATE_INDEX, "_index1").build();
 
         IndicesAccessControl iac = fromRole.authorize(SearchAction.NAME, Sets.newHashSet("_index", "_alias1"), md.getIndicesLookup(),
@@ -91,7 +91,7 @@ public class LimitedRoleTests extends ESTestCase {
         assertThat(iac.getIndexPermissions("_index1").isGranted(), is(true));
 
         {
-            Role limitedByRole = Role.builder("limited-role")
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role")
                     .cluster(Collections.singleton("all"), Collections.emptyList()).add(IndexPrivilege.READ, "_index")
                     .add(IndexPrivilege.NONE, "_index1").build();
             iac = limitedByRole.authorize(SearchAction.NAME, Sets.newHashSet("_index", "_alias1"), md.getIndicesLookup(),
@@ -136,12 +136,12 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testCheckClusterAction() {
-        Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
             .build();
         Authentication authentication = mock(Authentication.class);
         assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true));
         {
-            Role limitedByRole = Role.builder("limited-role")
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role")
                 .cluster(Collections.singleton("all"), Collections.emptyList()).build();
             assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication),
                 is(true));
@@ -151,7 +151,7 @@ public class LimitedRoleTests extends ESTestCase {
             assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(false));
         }
         {
-            Role limitedByRole = Role.builder("limited-role")
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role")
                 .cluster(Collections.singleton("monitor"), Collections.emptyList()).build();
             assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(true));
             Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
@@ -161,12 +161,12 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testCheckIndicesAction() {
-        Role fromRole = Role.builder("a-role").add(IndexPrivilege.READ, "ind-1").build();
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").add(IndexPrivilege.READ, "ind-1").build();
         assertThat(fromRole.checkIndicesAction(SearchAction.NAME), is(true));
         assertThat(fromRole.checkIndicesAction(CreateIndexAction.NAME), is(false));
 
         {
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.ALL, "ind-1").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.ALL, "ind-1").build();
             assertThat(limitedByRole.checkIndicesAction(SearchAction.NAME), is(true));
             assertThat(limitedByRole.checkIndicesAction(CreateIndexAction.NAME), is(true));
             Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
@@ -174,7 +174,7 @@ public class LimitedRoleTests extends ESTestCase {
             assertThat(role.checkIndicesAction(CreateIndexAction.NAME), is(false));
         }
         {
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.NONE, "ind-1").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.NONE, "ind-1").build();
             assertThat(limitedByRole.checkIndicesAction(SearchAction.NAME), is(false));
             Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
             assertThat(role.checkIndicesAction(SearchAction.NAME), is(false));
@@ -183,13 +183,13 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testAllowedIndicesMatcher() {
-        Role fromRole = Role.builder("a-role").add(IndexPrivilege.READ, "ind-1*").build();
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").add(IndexPrivilege.READ, "ind-1*").build();
         assertThat(fromRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true));
         assertThat(fromRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-11")), is(true));
         assertThat(fromRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(false));
 
         {
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
             assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true));
             assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-11")), is(false));
             assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(true));
@@ -199,7 +199,7 @@ public class LimitedRoleTests extends ESTestCase {
             assertThat(role.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(false));
         }
         {
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.READ, "ind-*").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.READ, "ind-*").build();
             assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-1")), is(true));
             assertThat(limitedByRole.allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction("ind-2")), is(true));
             Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole);
@@ -209,7 +209,7 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testAllowedActionsMatcher() {
-        Role fromRole = Role.builder("fromRole")
+        Role fromRole = Role.builder(Automatons.EMPTY, "fromRole")
                 .add(IndexPrivilege.WRITE, "ind*")
                 .add(IndexPrivilege.READ, "ind*")
                 .add(IndexPrivilege.READ, "other*")
@@ -219,7 +219,7 @@ public class LimitedRoleTests extends ESTestCase {
         assertThat(fromRolePredicate.test(SearchAction.NAME), is(true));
         assertThat(fromRolePredicate.test(BulkAction.NAME), is(true));
 
-        Role limitedByRole = Role.builder("limitedRole")
+        Role limitedByRole = Role.builder(Automatons.EMPTY, "limitedRole")
                 .add(IndexPrivilege.READ, "index1", "index2")
                 .build();
         Automaton limitedByRoleAutomaton = limitedByRole.allowedActionsMatcher("index1");
@@ -245,13 +245,13 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testCheckClusterPrivilege() {
-        Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList())
                 .build();
         assertThat(fromRole.grants(ClusterPrivilegeResolver.ALL), is(false));
         assertThat(fromRole.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(true));
 
         {
-            Role limitedByRole = Role.builder("scoped-role")
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "scoped-role")
                     .cluster(Collections.singleton("all"), Collections.emptyList()).build();
             assertThat(limitedByRole.grants(ClusterPrivilegeResolver.ALL), is(true));
             assertThat(limitedByRole.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(true));
@@ -260,7 +260,7 @@ public class LimitedRoleTests extends ESTestCase {
             assertThat(role.grants(ClusterPrivilegeResolver.MANAGE_SECURITY), is(true));
         }
         {
-            Role limitedByRole = Role.builder("scoped-role")
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "scoped-role")
                     .cluster(Collections.singleton("monitor"), Collections.emptyList()).build();
             assertThat(limitedByRole.grants(ClusterPrivilegeResolver.ALL), is(false));
             assertThat(limitedByRole.grants(ClusterPrivilegeResolver.MONITOR), is(true));
@@ -272,7 +272,7 @@ public class LimitedRoleTests extends ESTestCase {
     }
 
     public void testGetPrivilegesForIndexPatterns() {
-        Role fromRole = Role.builder("a-role").add(IndexPrivilege.READ, "ind-1*").build();
+        Role fromRole = Role.builder(Automatons.EMPTY, "a-role").add(IndexPrivilege.READ, "ind-1*").build();
         ResourcePrivilegesMap resourcePrivileges = fromRole.checkIndicesPrivileges(Collections.singleton("ind-1-1-*"), true,
                 Sets.newHashSet("read", "write"));
         ResourcePrivilegesMap expectedAppPrivsByResource = new ResourcePrivilegesMap(false, Collections.singletonMap("ind-1-1-*",
@@ -285,7 +285,7 @@ public class LimitedRoleTests extends ESTestCase {
         verifyResourcesPrivileges(resourcePrivileges, expectedAppPrivsByResource);
 
         {
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
             resourcePrivileges = limitedByRole.checkIndicesPrivileges(Collections.singleton("ind-1"), true, Collections.singleton("read"));
             expectedAppPrivsByResource = new ResourcePrivilegesMap(true,
                     Collections.singletonMap("ind-1", ResourcePrivileges.builder("ind-1").addPrivilege("read", true).build()));
@@ -315,7 +315,7 @@ public class LimitedRoleTests extends ESTestCase {
             verifyResourcesPrivileges(resourcePrivileges, expectedAppPrivsByResource);
         }
         {
-            fromRole = Role.builder("a-role")
+            fromRole = Role.builder(Automatons.EMPTY, "a-role")
                     .add(FieldPermissions.DEFAULT, Collections.emptySet(), IndexPrivilege.READ, true, "ind-1*", ".security").build();
             resourcePrivileges = fromRole.checkIndicesPrivileges(Sets.newHashSet("ind-1", ".security"), true,
                     Collections.singleton("read"));
@@ -326,7 +326,7 @@ public class LimitedRoleTests extends ESTestCase {
                             .put(".security", ResourcePrivileges.builder(".security").addPrivilege("read", true).build()).map());
             verifyResourcesPrivileges(resourcePrivileges, expectedAppPrivsByResource);
 
-            Role limitedByRole = Role.builder("limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "limited-role").add(IndexPrivilege.READ, "ind-1", "ind-2").build();
             resourcePrivileges = limitedByRole.checkIndicesPrivileges(Sets.newHashSet("ind-1", "ind-2", ".security"), true,
                     Collections.singleton("read"));
 
@@ -354,7 +354,7 @@ public class LimitedRoleTests extends ESTestCase {
         final ApplicationPrivilege app2Read = defineApplicationPrivilege("app2", "read", "data:read/*");
         final ApplicationPrivilege app2Write = defineApplicationPrivilege("app2", "write", "data:write/*");
 
-        Role fromRole = Role.builder("test-role").addApplicationPrivilege(app1Read, Collections.singleton("foo/*"))
+        Role fromRole = Role.builder(Automatons.EMPTY, "test-role").addApplicationPrivilege(app1Read, Collections.singleton("foo/*"))
                 .addApplicationPrivilege(app1All, Collections.singleton("foo/bar/baz"))
                 .addApplicationPrivilege(app2Read, Collections.singleton("foo/bar/*"))
                 .addApplicationPrivilege(app2Write, Collections.singleton("*/bar/*")).build();
@@ -385,9 +385,10 @@ public class LimitedRoleTests extends ESTestCase {
         verifyResourcesPrivileges(appPrivsByResource, expectedAppPrivsByResource);
 
         {
-            Role limitedByRole = Role.builder("test-role-scoped").addApplicationPrivilege(app1Read, Collections.singleton("foo/scoped/*"))
-                    .addApplicationPrivilege(app2Read, Collections.singleton("foo/bar/*"))
-                    .addApplicationPrivilege(app2Write, Collections.singleton("moo/bar/*")).build();
+            Role limitedByRole = Role.builder(Automatons.EMPTY, "test-role-scoped")
+                .addApplicationPrivilege(app1Read, Collections.singleton("foo/scoped/*"))
+                .addApplicationPrivilege(app2Read, Collections.singleton("foo/bar/*"))
+                .addApplicationPrivilege(app2Write, Collections.singleton("moo/bar/*")).build();
             appPrivsByResource = limitedByRole.checkApplicationResourcePrivileges("app1", Collections.singleton("*"), forPrivilegeNames,
                     applicationPrivilegeDescriptors);
             expectedAppPrivsByResource = new ResourcePrivilegesMap(false, Collections.singletonMap("*", ResourcePrivileges.builder("*")

+ 56 - 54
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java

@@ -52,6 +52,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.transport.TransportRequest;
+import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.action.XPackInfoAction;
 import org.elasticsearch.xpack.core.ilm.action.DeleteLifecycleAction;
 import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction;
@@ -186,6 +187,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
 
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.not;
@@ -241,7 +243,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role snapshotUserRole = Role.builder(roleDescriptor, null).build();
+        Role snapshotUserRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request, authentication), is(true));
         assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request, authentication), is(true));
         assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request, authentication), is(true));
@@ -284,10 +286,10 @@ public class ReservedRolesStoreTests extends ESTestCase {
                     mockIndexAbstraction(index)), is(true));
         }
         assertThat(snapshotUserRole.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(
-                mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
 
         assertNoAccessAllowed(snapshotUserRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(snapshotUserRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(snapshotUserRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testIngestAdminRole() {
@@ -298,7 +300,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role ingestAdminRole = Role.builder(roleDescriptor, null).build();
+        Role ingestAdminRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true));
         assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true));
         assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(true));
@@ -317,7 +319,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
                 mockIndexAbstraction(randomAlphaOfLengthBetween(8, 24))), is(false));
 
         assertNoAccessAllowed(ingestAdminRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(ingestAdminRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(ingestAdminRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testKibanaSystemRole() {
@@ -328,7 +330,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role kibanaRole = Role.builder(roleDescriptor, null).build();
+        Role kibanaRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -595,7 +597,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(mockIndexAbstraction(index)), is(false));
 
         assertNoAccessAllowed(kibanaRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(kibanaRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(kibanaRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testKibanaAdminRole() {
@@ -607,7 +609,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
         assertThat(roleDescriptor.getMetadata(), not(hasEntry("_deprecated", true)));
 
-        Role kibanaAdminRole = Role.builder(roleDescriptor, null).build();
+        Role kibanaAdminRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(kibanaAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(kibanaAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
         assertThat(kibanaAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
@@ -654,7 +656,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
         assertThat(roleDescriptor.getMetadata(), hasEntry("_deprecated", true));
 
-        Role kibanaUserRole = Role.builder(roleDescriptor, null).build();
+        Role kibanaUserRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
         assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
@@ -683,7 +685,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
             "*"), is(false));
 
         assertNoAccessAllowed(kibanaUserRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(kibanaUserRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(kibanaUserRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testMonitoringUserRole() {
@@ -694,7 +696,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role monitoringUserRole = Role.builder(roleDescriptor, null).build();
+        Role monitoringUserRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request, authentication), is(true));
         assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request, authentication), is(true));
         assertThat(monitoringUserRole.cluster().check(RemoteInfoAction.NAME, request, authentication), is(true));
@@ -737,7 +739,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(monitoringUserRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(mockIndexAbstraction(index)), is(true));
 
         assertNoAccessAllowed(monitoringUserRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(monitoringUserRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(monitoringUserRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
 
         final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
         assertThat(monitoringUserRole.application().grants(
@@ -760,7 +762,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build();
+        Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -847,7 +849,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
                 .test(mockIndexAbstraction(metricbeatIndex)), is(false));
 
         assertNoAccessAllowed(remoteMonitoringAgentRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(remoteMonitoringAgentRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(remoteMonitoringAgentRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testRemoteMonitoringCollectorRole() {
@@ -858,7 +860,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role remoteMonitoringCollectorRole = Role.builder(roleDescriptor, null).build();
+        Role remoteMonitoringCollectorRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -923,45 +925,45 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(GetSettingsAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(GetSettingsAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesShardStoresAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesShardStoresAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(RecoveryAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(RecoveryAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesStatsAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesStatsAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesSegmentsAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(true));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndicesSegmentsAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(true));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(true));
 
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(SearchAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(SearchAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(false));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(GetAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(GetAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(false));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(DeleteAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(DeleteAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(false));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndexAction.NAME)
                 .test(mockIndexAbstraction(randomFrom(RestrictedIndicesNames.RESTRICTED_NAMES))), is(false));
         assertThat(remoteMonitoringCollectorRole.indices().allowedIndicesMatcher(IndexAction.NAME)
-                .test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))), is(false));
+                .test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))), is(false));
 
         assertMonitoringOnRestrictedIndices(remoteMonitoringCollectorRole);
 
         assertNoAccessAllowed(remoteMonitoringCollectorRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(remoteMonitoringCollectorRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(remoteMonitoringCollectorRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     private void assertMonitoringOnRestrictedIndices(Role role) {
@@ -980,7 +982,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         final List<String> indexMonitoringActionNamesList = Arrays.asList(IndicesStatsAction.NAME, IndicesSegmentsAction.NAME,
                 GetSettingsAction.NAME, IndicesShardStoresAction.NAME, RecoveryAction.NAME);
         for (final String indexMonitoringActionName : indexMonitoringActionNamesList) {
-            String asyncSearchIndex = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2);
+            String asyncSearchIndex = XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2);
             final Map<String, IndexAccessControl> authzMap = role.indices().authorize(indexMonitoringActionName,
                 Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS, asyncSearchIndex),
                 metadata.getIndicesLookup(), fieldPermissionsCache);
@@ -999,7 +1001,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
         assertThat(roleDescriptor.getMetadata(), hasEntry("_deprecated", true));
 
-        Role reportingUserRole = Role.builder(roleDescriptor, null).build();
+        Role reportingUserRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
         assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
@@ -1034,7 +1036,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(reportingUserRole.indices().allowedIndicesMatcher(BulkAction.NAME).test(mockIndexAbstraction(index)), is(false));
 
         assertNoAccessAllowed(reportingUserRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(reportingUserRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(reportingUserRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testSuperuserRole() {
@@ -1045,7 +1047,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role superuserRole = Role.builder(roleDescriptor, null).build();
+        Role superuserRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true));
         assertThat(superuserRole.cluster().check(PutUserAction.NAME, request, authentication), is(true));
@@ -1117,7 +1119,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role logstashSystemRole = Role.builder(roleDescriptor, null).build();
+        Role logstashSystemRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -1136,7 +1138,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
                 .test(mockIndexAbstraction(randomAlphaOfLengthBetween(8, 24))), is(false));
 
         assertNoAccessAllowed(logstashSystemRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(logstashSystemRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(logstashSystemRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testBeatsAdminRole() {
@@ -1148,7 +1150,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
 
-        final Role beatsAdminRole = Role.builder(roleDescriptor, null).build();
+        final Role beatsAdminRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
         assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false));
@@ -1177,7 +1179,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(beatsAdminRole.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true));
 
         assertNoAccessAllowed(beatsAdminRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(beatsAdminRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(beatsAdminRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testBeatsSystemRole() {
@@ -1188,7 +1190,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role beatsSystemRole = Role.builder(roleDescriptor, null).build();
+        Role beatsSystemRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -1213,7 +1215,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(beatsSystemRole.indices().allowedIndicesMatcher(BulkAction.NAME).test(mockIndexAbstraction(index)), is(true));
 
         assertNoAccessAllowed(beatsSystemRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(beatsSystemRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(beatsSystemRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testAPMSystemRole() {
@@ -1224,7 +1226,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role APMSystemRole = Role.builder(roleDescriptor, null).build();
+        Role APMSystemRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true));
         assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true));
         assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true));
@@ -1256,7 +1258,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
             "indices:data/write/index:op_type/" + randomAlphaOfLengthBetween(3,5)).test(mockIndexAbstraction(index)), is(false));
 
         assertNoAccessAllowed(APMSystemRole, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(APMSystemRole, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(APMSystemRole, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testAPMUserRole() {
@@ -1267,7 +1269,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
 
         assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
         assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
@@ -1313,7 +1315,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertRoleHasManageMl(role);
         assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false));
 
@@ -1328,7 +1330,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertReadWriteDocsButNotDeleteIndexAllowed(role, AnnotationIndex.INDEX_NAME);
 
         assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
 
         final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
         assertThat(role.application().grants(
@@ -1419,7 +1421,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(false));
         assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(false));
         assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(false));
@@ -1482,7 +1484,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertReadWriteDocsButNotDeleteIndexAllowed(role, AnnotationIndex.INDEX_NAME);
 
         assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
 
 
         final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
@@ -1516,7 +1518,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
                 assertThat(roleDescriptor.getMetadata(), not(hasEntry("_deprecated", true)));
             }
 
-            Role role = Role.builder(roleDescriptor, null).build();
+            Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
             assertThat(role.cluster().check(DeleteTransformAction.NAME, request, authentication), is(true));
             assertThat(role.cluster().check(GetTransformAction.NAME, request, authentication), is(true));
             assertThat(role.cluster().check(GetTransformStatsAction.NAME, request, authentication), is(true));
@@ -1535,7 +1537,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
             assertNoAccessAllowed(role, TransformInternalIndexConstants.LATEST_INDEX_NAME); // internal use only
 
             assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-            assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+            assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
 
             final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
             assertThat(role.application().grants(
@@ -1574,7 +1576,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
                 assertThat(roleDescriptor.getMetadata(), not(hasEntry("_deprecated", true)));
             }
 
-            Role role = Role.builder(roleDescriptor, null).build();
+            Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
             assertThat(role.cluster().check(DeleteTransformAction.NAME, request, authentication), is(false));
             assertThat(role.cluster().check(GetTransformAction.NAME, request, authentication), is(true));
             assertThat(role.cluster().check(GetTransformStatsAction.NAME, request, authentication), is(true));
@@ -1593,7 +1595,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
             assertNoAccessAllowed(role, TransformInternalIndexConstants.LATEST_INDEX_NAME);
 
             assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-            assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+            assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
 
             final String kibanaApplicationWithRandomIndex = "kibana-" + randomFrom(randomAlphaOfLengthBetween(8, 24), ".kibana");
             assertThat(role.application().grants(
@@ -1622,7 +1624,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(true));
         assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true));
         assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true));
@@ -1642,7 +1644,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         }
 
         assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testWatcherUserRole() {
@@ -1653,7 +1655,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(false));
         assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true));
         assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false));
@@ -1675,7 +1677,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         }
 
         assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     public void testPredefinedViewerRole() {
@@ -1686,7 +1688,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         // No cluster privileges
         assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(role.cluster().check(ClusterStateAction.NAME, request, authentication), is(false));
@@ -1735,7 +1737,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role role = Role.builder(roleDescriptor, null).build();
+        Role role = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
 
         // No cluster privileges
         assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
@@ -1817,7 +1819,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertThat(role.indices().allowedIndicesMatcher(BulkAction.NAME).test(mockIndexAbstraction(index)), is(false));
 
         assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES);
-        assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2));
+        assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2));
     }
 
     private void assertNoAccessAllowed(Role role, Collection<String> indices) {
@@ -1846,7 +1848,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
         assertNotNull(roleDescriptor);
         assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true));
 
-        Role logstashAdminRole = Role.builder(roleDescriptor, null).build();
+        Role logstashAdminRole = Role.builder(roleDescriptor, null, RESTRICTED_INDICES_AUTOMATON).build();
         assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false));
         assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false));
         assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false));

+ 107 - 0
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/test/TestRestrictedIndices.java

@@ -0,0 +1,107 @@
+
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+package org.elasticsearch.xpack.core.security.test;
+
+import org.apache.lucene.util.automaton.Automaton;
+import org.elasticsearch.Version;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.indices.ExecutorNames;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.indices.SystemIndices;
+import org.elasticsearch.indices.SystemIndices.Feature;
+import org.elasticsearch.indices.TestIndexNameExpressionResolver;
+import org.elasticsearch.xpack.core.XPackPlugin;
+import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
+import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME;
+import static org.elasticsearch.xpack.core.ClientHelper.ASYNC_SEARCH_ORIGIN;
+import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
+import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
+import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_TOKENS_ALIAS;
+
+public class TestRestrictedIndices {
+
+    public static final Automaton RESTRICTED_INDICES_AUTOMATON;
+    public static final IndexNameExpressionResolver RESOLVER;
+
+    static {
+        SystemIndices systemIndices = new SystemIndices(Map.of(
+            "security-mock",
+            new Feature("security-mock", "fake security for test restricted indices", List.of(
+                SystemIndexDescriptor.builder()
+                    // This can't just be `.security-*` because that would overlap with the tokens index pattern
+                    .setIndexPattern(".security-[0-9]+")
+                    .setPrimaryIndex(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7)
+                    .setDescription("Contains Security configuration")
+                    .setMappings(mockMappings())
+                    .setSettings(Settings.EMPTY)
+                    .setAliasName(SECURITY_MAIN_ALIAS)
+                    .setIndexFormat(7)
+                    .setVersionMetaKey("version")
+                    .setOrigin(SECURITY_ORIGIN)
+                    .setThreadPools(ExecutorNames.CRITICAL_SYSTEM_INDEX_THREAD_POOLS)
+                    .build(),
+                SystemIndexDescriptor.builder()
+                    .setIndexPattern(".security-tokens-[0-9]+")
+                    .setPrimaryIndex(RestrictedIndicesNames.INTERNAL_SECURITY_TOKENS_INDEX_7)
+                    .setDescription("Contains auth token data")
+                    .setMappings(mockMappings())
+                    .setSettings(Settings.EMPTY)
+                    .setAliasName(SECURITY_TOKENS_ALIAS)
+                    .setIndexFormat(7)
+                    .setVersionMetaKey("version")
+                    .setOrigin(SECURITY_ORIGIN)
+                    .setThreadPools(ExecutorNames.CRITICAL_SYSTEM_INDEX_THREAD_POOLS)
+                    .build()
+            )),
+            "async-search-mock",
+            new Feature("async search mock", "fake async search for restricted indices", List.of(
+                SystemIndexDescriptor.builder()
+                    .setIndexPattern(XPackPlugin.ASYNC_RESULTS_INDEX + "*")
+                    .setDescription("Async search results")
+                    .setPrimaryIndex(XPackPlugin.ASYNC_RESULTS_INDEX)
+                    .setMappings(mockMappings())
+                    .setSettings(Settings.EMPTY)
+                    .setVersionMetaKey("version")
+                    .setOrigin(ASYNC_SEARCH_ORIGIN)
+                    .build()
+            ))));
+        RESTRICTED_INDICES_AUTOMATON = systemIndices.getSystemNameAutomaton();
+        RESOLVER = TestIndexNameExpressionResolver.newInstance(systemIndices);
+    }
+
+    private TestRestrictedIndices() {}
+
+    private static XContentBuilder mockMappings() {
+        try {
+            XContentBuilder builder = jsonBuilder()
+                .startObject()
+                    .startObject(SINGLE_MAPPING_NAME)
+                        .startObject("_meta")
+                            .field("version", Version.CURRENT)
+                        .endObject()
+                        .field("dynamic", "strict")
+                        .startObject("properties")
+                        .endObject()
+                    .endObject()
+                .endObject();
+            return builder;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}

+ 7 - 2
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java

@@ -56,6 +56,7 @@ 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.index.RestrictedIndicesNames;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
 import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
 import org.elasticsearch.xpack.core.security.user.ElasticUser;
@@ -378,7 +379,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
             GetRolesResponse getRolesResponse = new GetRolesRequestBuilder(client()).names("test_role").get();
             assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
             assertTrue("any cluster permission should be authorized",
-                    Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request, authentication));
+                    Role.builder(getRolesResponse.roles()[0], null, Automatons.EMPTY).build()
+                        .cluster()
+                        .check("cluster:admin/foo", request, authentication));
 
             preparePutRole("test_role")
                     .cluster("none")
@@ -389,7 +392,9 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase {
             assertTrue("test_role does not exist!", getRolesResponse.hasRoles());
 
             assertFalse("no cluster permission should be authorized",
-                    Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request, authentication));
+                    Role.builder(getRolesResponse.roles()[0], null, Automatons.EMPTY).build()
+                        .cluster()
+                        .check("cluster:admin/bar", request, authentication));
         }
     }
 

+ 2 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java

@@ -563,7 +563,8 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin,
 
         final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore,
             privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache, apiKeyService,
-            serviceAccountService, dlsBitsetCache.get(), new DeprecationRoleDescriptorConsumer(clusterService, threadPool));
+            serviceAccountService, dlsBitsetCache.get(), expressionResolver,
+            new DeprecationRoleDescriptorConsumer(clusterService, threadPool));
         securityIndex.get().addStateListener(allRolesStore::onSecurityIndexStateChange);
 
         // to keep things simple, just invalidate all cached entries on license change. this happens so rarely that the impact should be

+ 72 - 37
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java

@@ -9,9 +9,11 @@ package org.elasticsearch.xpack.security.authz.store;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.message.ParameterizedMessage;
+import org.apache.lucene.util.automaton.Automaton;
 import org.elasticsearch.ElasticsearchException;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ContextPreservingActionListener;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.core.Nullable;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.bytes.BytesReference;
@@ -113,13 +115,17 @@ public class CompositeRolesStore {
     private final boolean isAnonymousEnabled;
     private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> builtInRoleProviders;
     private final List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> allRoleProviders;
+    private final Role superuserRole;
+    private final Role xpackUserRole;
+    private final Role asyncSearchUserRole;
+    private final Automaton restrictedIndicesAutomaton;
 
     public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore,
                                ReservedRolesStore reservedRolesStore, NativePrivilegeStore privilegeStore,
                                List<BiConsumer<Set<String>, ActionListener<RoleRetrievalResult>>> rolesProviders,
                                ThreadContext threadContext, XPackLicenseState licenseState, FieldPermissionsCache fieldPermissionsCache,
                                ApiKeyService apiKeyService, ServiceAccountService serviceAccountService,
-                               DocumentSubsetBitsetCache dlsBitsetCache,
+                               DocumentSubsetBitsetCache dlsBitsetCache, IndexNameExpressionResolver resolver,
                                Consumer<Collection<RoleDescriptor>> effectiveRoleDescriptorsConsumer) {
         this.fileRolesStore = Objects.requireNonNull(fileRolesStore);
         this.dlsBitsetCache = Objects.requireNonNull(dlsBitsetCache);
@@ -157,6 +163,14 @@ public class CompositeRolesStore {
         }
         this.anonymousUser = new AnonymousUser(settings);
         this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
+        this.restrictedIndicesAutomaton = resolver.getSystemNameAutomaton();
+        this.superuserRole = Role.builder(
+            ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR,
+            fieldPermissionsCache,
+            restrictedIndicesAutomaton
+        ).build();
+        xpackUserRole = Role.builder(XPackUser.ROLE_DESCRIPTOR, fieldPermissionsCache, restrictedIndicesAutomaton).build();
+        asyncSearchUserRole = Role.builder(AsyncSearchUser.ROLE_DESCRIPTOR, fieldPermissionsCache, restrictedIndicesAutomaton).build();
     }
 
     public void roles(Set<String> roleNames, ActionListener<Role> roleActionListener) {
@@ -207,6 +221,16 @@ public class CompositeRolesStore {
             });
     }
 
+    // for testing
+    Role getXpackUserRole() {
+        return xpackUserRole;
+    }
+
+    // for testing
+    Role getAsyncSearchUserRole() {
+        return asyncSearchUserRole;
+    }
+
     public void getRoles(User user, Authentication authentication, ActionListener<Role> roleActionListener) {
         // we need to special case the internal users in this method, if we apply the anonymous roles to every user including these system
         // user accounts then we run into the chance of a deadlock because then we need to get a role that we may be trying to get as the
@@ -218,15 +242,15 @@ public class CompositeRolesStore {
         }
         if (XPackUser.is(user)) {
             assert XPackUser.INSTANCE.roles().length == 1;
-            roleActionListener.onResponse(XPackUser.ROLE);
+            roleActionListener.onResponse(xpackUserRole);
             return;
         }
         if (XPackSecurityUser.is(user)) {
-            roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE);
+            roleActionListener.onResponse(superuserRole);
             return;
         }
         if (AsyncSearchUser.is(user)) {
-            roleActionListener.onResponse(AsyncSearchUser.ROLE);
+            roleActionListener.onResponse(asyncSearchUserRole);
             return;
         }
 
@@ -246,7 +270,7 @@ public class CompositeRolesStore {
             if (roleNames.isEmpty()) {
                 roleActionListener.onResponse(Role.EMPTY);
             } else if (roleNames.contains(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName())) {
-                roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE);
+                roleActionListener.onResponse(superuserRole);
             } else {
                 roles(roleNames, roleActionListener);
             }
@@ -320,27 +344,29 @@ public class CompositeRolesStore {
     private void buildThenMaybeCacheRole(RoleKey roleKey, Collection<RoleDescriptor> roleDescriptors, Set<String> missing,
                                          boolean tryCache, long invalidationCounter, ActionListener<Role> listener) {
         logger.trace("Building role from descriptors [{}] for names [{}] from source [{}]", roleDescriptors, roleKey.names, roleKey.source);
-        buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, privilegeStore, ActionListener.wrap(role -> {
-            if (role != null && tryCache) {
-                try (ReleasableLock ignored = roleCacheHelper.acquireUpdateLock()) {
-                    /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold
-                     * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching
-                     * stuff in an async fashion we need to make sure that if the cache got invalidated since we
-                     * started the request we don't put a potential stale result in the cache, hence the
-                     * numInvalidation.get() comparison to the number of invalidation when we started. we just try to
-                     * be on the safe side and don't cache potentially stale results
-                     */
-                    if (invalidationCounter == numInvalidation.get()) {
-                        roleCache.computeIfAbsent(roleKey, (s) -> role);
+        buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, privilegeStore, restrictedIndicesAutomaton,
+            ActionListener.wrap(role -> {
+                if (role != null && tryCache) {
+                    try (ReleasableLock ignored = roleCacheHelper.acquireUpdateLock()) {
+                        /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold
+                         * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching
+                         * stuff in an async fashion we need to make sure that if the cache got invalidated since we
+                         * started the request we don't put a potential stale result in the cache, hence the
+                         * numInvalidation.get() comparison to the number of invalidation when we started. we just try to
+                         * be on the safe side and don't cache potentially stale results
+                         */
+                        if (invalidationCounter == numInvalidation.get()) {
+                            roleCache.computeIfAbsent(roleKey, (s) -> role);
+                        }
                     }
-                }
 
-                for (String missingRole : missing) {
-                    negativeLookupCache.computeIfAbsent(missingRole, s -> Boolean.TRUE);
+                    for (String missingRole : missing) {
+                        negativeLookupCache.computeIfAbsent(missingRole, s -> Boolean.TRUE);
+                    }
                 }
-            }
-            listener.onResponse(role);
-        }, listener::onFailure));
+                listener.onResponse(role);
+            }, listener::onFailure)
+        );
     }
 
     private void buildAndCacheRoleForApiKey(Authentication authentication, boolean limitedBy, ActionListener<Role> roleActionListener) {
@@ -420,7 +446,8 @@ public class CompositeRolesStore {
     }
 
     public static void buildRoleFromDescriptors(Collection<RoleDescriptor> roleDescriptors, FieldPermissionsCache fieldPermissionsCache,
-                                                NativePrivilegeStore privilegeStore, ActionListener<Role> listener) {
+                                                NativePrivilegeStore privilegeStore, Automaton restrictedIndicesAutomaton,
+                                                ActionListener<Role> listener) {
         if (roleDescriptors.isEmpty()) {
             listener.onResponse(Role.EMPTY);
             return;
@@ -463,19 +490,27 @@ public class CompositeRolesStore {
         }
 
         final Privilege runAsPrivilege = runAs.isEmpty() ? Privilege.NONE : new Privilege(runAs, runAs.toArray(Strings.EMPTY_ARRAY));
-        final Role.Builder builder = Role.builder(roleNames.toArray(new String[roleNames.size()]))
+        final Role.Builder builder = Role.builder(restrictedIndicesAutomaton, roleNames.toArray(Strings.EMPTY_ARRAY))
                 .cluster(clusterPrivileges, configurableClusterPrivileges)
                 .runAs(runAsPrivilege);
-        indicesPrivilegesMap.entrySet().forEach((entry) -> {
-            MergeableIndicesPrivilege privilege = entry.getValue();
-            builder.add(fieldPermissionsCache.getFieldPermissions(privilege.fieldPermissionsDefinition), privilege.query,
-                    IndexPrivilege.get(privilege.privileges), false, privilege.indices.toArray(Strings.EMPTY_ARRAY));
-        });
-        restrictedIndicesPrivilegesMap.entrySet().forEach((entry) -> {
-            MergeableIndicesPrivilege privilege = entry.getValue();
-            builder.add(fieldPermissionsCache.getFieldPermissions(privilege.fieldPermissionsDefinition), privilege.query,
-                    IndexPrivilege.get(privilege.privileges), true, privilege.indices.toArray(Strings.EMPTY_ARRAY));
-        });
+        indicesPrivilegesMap.forEach((key, privilege) ->
+            builder.add(
+                fieldPermissionsCache.getFieldPermissions(privilege.fieldPermissionsDefinition),
+                privilege.query,
+                IndexPrivilege.get(privilege.privileges),
+                false,
+                privilege.indices.toArray(Strings.EMPTY_ARRAY)
+            )
+        );
+        restrictedIndicesPrivilegesMap.forEach((key, privilege) ->
+            builder.add(
+                fieldPermissionsCache.getFieldPermissions(privilege.fieldPermissionsDefinition),
+                privilege.query,
+                IndexPrivilege.get(privilege.privileges),
+                true,
+                privilege.indices.toArray(Strings.EMPTY_ARRAY)
+            )
+        );
 
         if (applicationPrivilegesMap.isEmpty()) {
             listener.onResponse(builder.build());
@@ -544,8 +579,8 @@ public class CompositeRolesStore {
      * A mutable class that can be used to represent the combination of one or more {@link IndicesPrivileges}
      */
     private static class MergeableIndicesPrivilege {
-        private Set<String> indices;
-        private Set<String> privileges;
+        private final Set<String> indices;
+        private final Set<String> privileges;
         private FieldPermissionsDefinition fieldPermissionsDefinition;
         private Set<BytesReference> query = null;
 

+ 3 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java

@@ -99,6 +99,7 @@ import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.elasticsearch.xpack.security.authc.service.ElasticServiceAccounts.ElasticServiceAccount;
 
 import java.util.Collection;
@@ -127,7 +128,8 @@ public class ElasticServiceAccountsTests extends ESTestCase {
     }
 
     public void testElasticFleetPrivileges() {
-        final Role role = Role.builder(ElasticServiceAccounts.ACCOUNTS.get("elastic/fleet-server").roleDescriptor(), null).build();
+        final Role role =
+            Role.builder(ElasticServiceAccounts.ACCOUNTS.get("elastic/fleet-server").roleDescriptor(), null, Automatons.EMPTY).build();
         final Authentication authentication = mock(Authentication.class);
         assertThat(role.cluster().check(CreateApiKeyAction.NAME,
             new CreateApiKeyRequest(randomAlphaOfLengthBetween(3, 8), null, null), authentication), is(true));

+ 2 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java

@@ -198,6 +198,7 @@ import static org.elasticsearch.xpack.core.security.authz.AuthorizationServiceFi
 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.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
 import static org.hamcrest.Matchers.arrayWithSize;
 import static org.hamcrest.Matchers.containsString;
@@ -280,7 +281,7 @@ public class AuthorizationServiceTests extends ESTestCase {
                 callback.onResponse(Role.EMPTY);
             } else {
                 CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, privilegesStore,
-                    ActionListener.wrap(r -> callback.onResponse(r), callback::onFailure)
+                    RESTRICTED_INDICES_AUTOMATON, ActionListener.wrap(r -> callback.onResponse(r), callback::onFailure)
                 );
             }
             return Void.TYPE;

+ 13 - 7
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java

@@ -34,6 +34,7 @@ import java.util.List;
 import java.util.Set;
 
 import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.createTimestampField;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.not;
@@ -75,7 +76,8 @@ public class AuthorizedIndicesTests extends ESTestCase {
                 .build();
         final PlainActionFuture<Role> future = new PlainActionFuture<>();
         final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
-        CompositeRolesStore.buildRoleFromDescriptors(descriptors, new FieldPermissionsCache(Settings.EMPTY), null, future);
+        CompositeRolesStore.buildRoleFromDescriptors(
+            descriptors, new FieldPermissionsCache(Settings.EMPTY), null, RESTRICTED_INDICES_AUTOMATON, future);
         Role roles = future.actionGet();
         Set<String> list =
             RBACEngine.resolveAuthorizedIndicesFromRole(roles, getRequestInfo(SearchAction.NAME), metadata.getIndicesLookup());
@@ -87,21 +89,23 @@ public class AuthorizedIndicesTests extends ESTestCase {
     }
 
     public void testAuthorizedIndicesUserWithSomeRolesEmptyMetadata() {
-        Role role = Role.builder("role").add(IndexPrivilege.ALL, "*").build();
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role").add(IndexPrivilege.ALL, "*").build();
         Set<String> authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(role, getRequestInfo(SearchAction.NAME),
             Metadata.EMPTY_METADATA.getIndicesLookup());
         assertTrue(authorizedIndices.isEmpty());
     }
 
     public void testSecurityIndicesAreRemovedFromRegularUser() {
-        Role role = Role.builder("user_role").add(IndexPrivilege.ALL, "*").cluster(Set.of("all"), Set.of()).build();
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "user_role")
+            .add(IndexPrivilege.ALL, "*").cluster(Set.of("all"), Set.of()).build();
         Set<String> authorizedIndices = RBACEngine.resolveAuthorizedIndicesFromRole(role, getRequestInfo(SearchAction.NAME),
             Metadata.EMPTY_METADATA.getIndicesLookup());
         assertTrue(authorizedIndices.isEmpty());
     }
 
     public void testSecurityIndicesAreRestrictedForDefaultRole() {
-        Role role = Role.builder(randomFrom("user_role", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()))
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON,
+                    randomFrom("user_role", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()))
                 .add(IndexPrivilege.ALL, "*")
                 .cluster(Set.of("all"), Set.of())
                 .build();
@@ -128,7 +132,7 @@ public class AuthorizedIndicesTests extends ESTestCase {
     }
 
     public void testSecurityIndicesAreNotRemovedFromUnrestrictedRole() {
-        Role role = Role.builder(randomAlphaOfLength(8))
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, randomAlphaOfLength(8))
                 .add(FieldPermissions.DEFAULT, null, IndexPrivilege.ALL, true, "*")
                 .cluster(Set.of("all"), Set.of())
                 .build();
@@ -190,7 +194,8 @@ public class AuthorizedIndicesTests extends ESTestCase {
             .build();
         final PlainActionFuture<Role> future = new PlainActionFuture<>();
         final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
-        CompositeRolesStore.buildRoleFromDescriptors(descriptors, new FieldPermissionsCache(Settings.EMPTY), null, future);
+        CompositeRolesStore.buildRoleFromDescriptors(
+            descriptors, new FieldPermissionsCache(Settings.EMPTY), null, RESTRICTED_INDICES_AUTOMATON, future);
         Role roles = future.actionGet();
         Set<String> list =
             RBACEngine.resolveAuthorizedIndicesFromRole(roles, getRequestInfo(SearchAction.NAME), metadata.getIndicesLookup());
@@ -235,7 +240,8 @@ public class AuthorizedIndicesTests extends ESTestCase {
             .build();
         final PlainActionFuture<Role> future = new PlainActionFuture<>();
         final Set<RoleDescriptor> descriptors = Sets.newHashSet(aStarRole, bRole);
-        CompositeRolesStore.buildRoleFromDescriptors(descriptors, new FieldPermissionsCache(Settings.EMPTY), null, future);
+        CompositeRolesStore.buildRoleFromDescriptors(
+            descriptors, new FieldPermissionsCache(Settings.EMPTY), null, RESTRICTED_INDICES_AUTOMATON, future);
         Role roles = future.actionGet();
         TransportRequest request = new ResolveIndexAction.Request(new String[]{"a*"});
         AuthorizationEngine.RequestInfo requestInfo = new AuthorizationEngine.RequestInfo(null, request, SearchAction.NAME);

+ 28 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java

@@ -64,6 +64,7 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.IndicesPrivile
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache;
 import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
+import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.XPackSecurityUser;
 import org.elasticsearch.xpack.core.security.user.XPackUser;
@@ -90,6 +91,7 @@ import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
 import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
 import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
 import static org.elasticsearch.xpack.security.authz.AuthorizedIndicesTests.getRequestInfo;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.arrayContaining;
@@ -106,7 +108,6 @@ import static org.hamcrest.Matchers.oneOf;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anySetOf;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -280,12 +281,36 @@ public class IndicesAndAliasesResolverTests extends ESTestCase {
                 callback.onResponse(Role.EMPTY);
             } else {
                 CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, null,
-                        ActionListener.wrap(r -> callback.onResponse(r), callback::onFailure)
+                    RESTRICTED_INDICES_AUTOMATON, ActionListener.wrap(r -> callback.onResponse(r), callback::onFailure)
                 );
             }
             return Void.TYPE;
         }).when(rolesStore).roles(anySetOf(String.class), anyActionListener());
-        doCallRealMethod().when(rolesStore).getRoles(any(User.class), any(Authentication.class), anyActionListener());
+
+        doAnswer(i -> {
+            User user = (User) i.getArguments()[0];
+            @SuppressWarnings("unchecked")
+            ActionListener<Role> listener = (ActionListener<Role>) i.getArguments()[2];
+            if (XPackUser.is(user)) {
+                listener.onResponse(Role.builder(XPackUser.ROLE_DESCRIPTOR, fieldPermissionsCache, RESTRICTED_INDICES_AUTOMATON).build());
+                return Void.TYPE;
+            }
+            if (XPackSecurityUser.is(user)) {
+                listener.onResponse(Role.builder(
+                    ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR,
+                    fieldPermissionsCache,
+                    RESTRICTED_INDICES_AUTOMATON
+                ).build());
+                return Void.TYPE;
+            }
+            if (AsyncSearchUser.is(user)) {
+                listener.onResponse(
+                    Role.builder(AsyncSearchUser.ROLE_DESCRIPTOR, fieldPermissionsCache, RESTRICTED_INDICES_AUTOMATON).build());
+                return Void.TYPE;
+            }
+            i.callRealMethod();
+            return Void.TYPE;
+        }).when(rolesStore).getRoles(any(User.class), any(Authentication.class), anyActionListener());
 
         ClusterService clusterService = mock(ClusterService.class);
         when(clusterService.getClusterSettings()).thenReturn(new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));

+ 23 - 21
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java

@@ -18,8 +18,8 @@ import org.elasticsearch.action.search.SearchAction;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.client.Client;
-import org.elasticsearch.cluster.metadata.DataStreamTestHelper;
 import org.elasticsearch.cluster.metadata.DataStream;
+import org.elasticsearch.cluster.metadata.DataStreamTestHelper;
 import org.elasticsearch.cluster.metadata.IndexAbstraction;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.common.Strings;
@@ -31,6 +31,7 @@ import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.license.GetLicenseAction;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.transport.TransportRequest;
+import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
@@ -85,6 +86,7 @@ import java.util.stream.Collectors;
 import static java.util.Collections.emptyMap;
 import static org.elasticsearch.common.util.set.Sets.newHashSet;
 import static org.elasticsearch.xpack.security.authz.AuthorizedIndicesTests.getRequestInfo;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.emptyIterable;
 import static org.hamcrest.Matchers.equalTo;
@@ -354,7 +356,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test1")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test1")
             .cluster(Collections.singleton("all"), Collections.emptyList())
             .add(IndexPrivilege.WRITE, "academy")
             .build();
@@ -396,7 +398,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test2")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test2")
             .cluster(Set.of("monitor"), Set.of())
             .add(IndexPrivilege.INDEX, "academy")
             .add(IndexPrivilege.WRITE, "initiative")
@@ -455,7 +457,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test3")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test3")
             .cluster(Set.of("monitor"), Set.of())
             .build();
         RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null);
@@ -494,7 +496,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test3")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test3")
             .add(IndexPrivilege.ALL, "logstash-*", "foo?")
             .add(IndexPrivilege.READ, "abc*")
             .add(IndexPrivilege.WRITE, "*xyz")
@@ -585,7 +587,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test-write")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test-write")
             .add(IndexPrivilege.INDEX, "apache-*")
             .add(IndexPrivilege.DELETE, "apache-2016-*")
             .build();
@@ -611,9 +613,9 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        final String patternPrefix = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX.substring(0,
-                randomIntBetween(2, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX.length() - 2));
-        Role role = Role.builder("role")
+        final String patternPrefix = XPackPlugin.ASYNC_RESULTS_INDEX.substring(0,
+                randomIntBetween(2, XPackPlugin.ASYNC_RESULTS_INDEX.length() - 2));
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role")
                 .add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, false, patternPrefix + "*")
                 .build();
         RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null);
@@ -631,7 +633,7 @@ public class RBACEngineTests extends ESTestCase {
                         .addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
                                 .put("index", false).map()).build()));
 
-        String matchesPatternPrefix = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX.substring(0, patternPrefix.length() + 1);
+        String matchesPatternPrefix = XPackPlugin.ASYNC_RESULTS_INDEX.substring(0, patternPrefix.length() + 1);
         response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
                 .indices(matchesPatternPrefix + "*")
                 .allowRestrictedIndices(false)
@@ -666,7 +668,7 @@ public class RBACEngineTests extends ESTestCase {
                         .addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
                                 .put("index", true).map()).build()));
 
-        final String restrictedIndexMatchingWildcard = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2);
+        final String restrictedIndexMatchingWildcard = XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2);
         response = hasPrivileges(RoleDescriptor.IndicesPrivileges.builder()
                 .indices(restrictedIndexMatchingWildcard + "*")
                 .allowRestrictedIndices(true)
@@ -701,7 +703,7 @@ public class RBACEngineTests extends ESTestCase {
                         .addPrivileges(MapBuilder.newMapBuilder(new LinkedHashMap<String, Boolean>())
                                 .put("index", false).map()).build()));
 
-        role = Role.builder("role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role")
                 .add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, true, patternPrefix + "*")
                 .build();
         authzInfo = new RBACAuthorizationInfo(role, null);
@@ -724,7 +726,7 @@ public class RBACEngineTests extends ESTestCase {
         when(authentication.getUser()).thenReturn(user);
         final boolean restrictedIndexPermission = randomBoolean();
         final boolean restrictedMonitorPermission = randomBoolean();
-        Role role = Role.builder("role")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role")
             .add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, restrictedIndexPermission, ".sec*")
             .add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, restrictedMonitorPermission, ".security*")
             .build();
@@ -767,7 +769,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("role")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role")
             .add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, false, ".sec*")
             .add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, true, ".security*")
             .build();
@@ -804,7 +806,7 @@ public class RBACEngineTests extends ESTestCase {
                     .put("index", false).put("monitor", true).map()).build()
         ));
 
-        role = Role.builder("role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "role")
                 .add(FieldPermissions.DEFAULT, null, IndexPrivilege.INDEX, true, ".sec*")
                 .add(FieldPermissions.DEFAULT, null, IndexPrivilege.MONITOR, false, ".security*")
                 .build();
@@ -854,7 +856,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test-role")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test-role")
             .addApplicationPrivilege(app1Read, Collections.singleton("foo/*"))
             .addApplicationPrivilege(app1All, Collections.singleton("foo/bar/baz"))
             .addApplicationPrivilege(app2Read, Collections.singleton("foo/bar/*"))
@@ -917,7 +919,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test-write")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test-write")
             .addApplicationPrivilege(priv1, Collections.singleton("user/*/name"))
             .build();
         RBACAuthorizationInfo authzInfo = new RBACAuthorizationInfo(role, null);
@@ -952,7 +954,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test-write")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test-write")
             .cluster(Set.of("monitor"), Set.of())
             .add(IndexPrivilege.READ, "read-*")
             .add(IndexPrivilege.ALL, "all-*")
@@ -1004,7 +1006,7 @@ public class RBACEngineTests extends ESTestCase {
     public void testBuildUserPrivilegeResponse() {
         final ManageApplicationPrivileges manageApplicationPrivileges = new ManageApplicationPrivileges(Sets.newHashSet("app01", "app02"));
         final BytesArray query = new BytesArray("{\"term\":{\"public\":true}}");
-        final Role role = Role.builder("test", "role")
+        final Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test", "role")
             .cluster(Sets.newHashSet("monitor", "manage_watcher"), Collections.singleton(manageApplicationPrivileges))
             .add(IndexPrivilege.get(Sets.newHashSet("read", "write")), "index-1")
             .add(IndexPrivilege.ALL, "index-2", "index-3")
@@ -1051,7 +1053,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test1")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test1")
             .cluster(Collections.singleton("all"), Collections.emptyList())
             .add(IndexPrivilege.READ, dataStreamName)
             .build();
@@ -1083,7 +1085,7 @@ public class RBACEngineTests extends ESTestCase {
         User user = new User(randomAlphaOfLengthBetween(4, 12));
         Authentication authentication = mock(Authentication.class);
         when(authentication.getUser()).thenReturn(user);
-        Role role = Role.builder("test1")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "test1")
                 .cluster(Collections.emptySet(), Collections.emptyList())
                 .add(IndexPrivilege.CREATE, "my_*")
                 .add(IndexPrivilege.WRITE, "my_data*")

+ 78 - 44
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java

@@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.set.Sets;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
@@ -44,6 +45,7 @@ import java.util.SortedMap;
 import java.util.stream.Collectors;
 
 import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.createTimestampField;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
@@ -67,7 +69,7 @@ public class IndicesPermissionTests extends ESTestCase {
         // basics:
         Set<BytesReference> query = Collections.singleton(new BytesArray("{}"));
         String[] fields = new String[]{"_field"};
-        Role role = Role.builder("_role")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
             .add(new FieldPermissions(fieldPermissionDef(fields, null)), query, IndexPrivilege.ALL, randomBoolean(), "_index")
             .build();
         IndicesAccessControl permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), lookup, fieldPermissionsCache);
@@ -79,7 +81,7 @@ public class IndicesPermissionTests extends ESTestCase {
         assertThat(permissions.getIndexPermissions("_index").getDocumentPermissions().getQueries(), equalTo(query));
 
         // no document level security:
-        role = Role.builder("_role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
             .add(new FieldPermissions(fieldPermissionDef(fields, null)), null, IndexPrivilege.ALL, randomBoolean(), "_index")
             .build();
         permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), lookup, fieldPermissionsCache);
@@ -90,7 +92,9 @@ public class IndicesPermissionTests extends ESTestCase {
         assertThat(permissions.getIndexPermissions("_index").getDocumentPermissions().getQueries(), nullValue());
 
         // no field level security:
-        role = Role.builder("_role").add(new FieldPermissions(), query, IndexPrivilege.ALL, randomBoolean(), "_index").build();
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
+            .add(new FieldPermissions(), query, IndexPrivilege.ALL, randomBoolean(), "_index")
+            .build();
         permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), lookup, fieldPermissionsCache);
         assertThat(permissions.getIndexPermissions("_index"), notNullValue());
         assertFalse(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity());
@@ -99,7 +103,7 @@ public class IndicesPermissionTests extends ESTestCase {
         assertThat(permissions.getIndexPermissions("_index").getDocumentPermissions().getQueries(), equalTo(query));
 
         // index group associated with an alias:
-        role = Role.builder("_role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
                 .add(new FieldPermissions(fieldPermissionDef(fields, null)), query, IndexPrivilege.ALL, randomBoolean(), "_alias")
                 .build();
         permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), lookup, fieldPermissionsCache);
@@ -120,7 +124,7 @@ public class IndicesPermissionTests extends ESTestCase {
         // match all fields
         String[] allFields = randomFrom(new String[]{"*"}, new String[]{"foo", "*"},
         new String[]{randomAlphaOfLengthBetween(1, 10), "*"});
-        role = Role.builder("_role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
             .add(new FieldPermissions(fieldPermissionDef(allFields, null)), query, IndexPrivilege.ALL, randomBoolean(), "_alias")
             .build();
         permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), lookup, fieldPermissionsCache);
@@ -150,7 +154,7 @@ public class IndicesPermissionTests extends ESTestCase {
         Set<BytesReference> fooQuery = Collections.singleton(new BytesArray("{foo}"));
         allFields = randomFrom(new String[]{"*"}, new String[]{"foo", "*"},
                 new String[]{randomAlphaOfLengthBetween(1, 10), "*"});
-        role = Role.builder("_role")
+        role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
             .add(new FieldPermissions(fieldPermissionDef(allFields, null)), fooQuery, IndexPrivilege.ALL, randomBoolean(), "_alias")
             .add(new FieldPermissions(fieldPermissionDef(allFields, null)), query, IndexPrivilege.ALL, randomBoolean(), "_alias")
             .build();
@@ -190,7 +194,7 @@ public class IndicesPermissionTests extends ESTestCase {
 
         Set<BytesReference> query = Collections.singleton(new BytesArray("{}"));
         String[] fields = new String[]{"_field"};
-        Role role = Role.builder("_role")
+        Role role = Role.builder(RESTRICTED_INDICES_AUTOMATON, "_role")
                 .add(new FieldPermissions(fieldPermissionDef(fields, null)), query, IndexPrivilege.ALL, randomBoolean(), "_index")
                 .add(new FieldPermissions(fieldPermissionDef(null, null)), null, IndexPrivilege.ALL, randomBoolean(), "*")
                 .build();
@@ -244,11 +248,15 @@ public class IndicesPermissionTests extends ESTestCase {
         SortedMap<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
 
         FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
-        IndicesPermission.Group group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, randomBoolean(),
-                "a1");
-        IndicesPermission.Group group2 = new IndicesPermission.Group(IndexPrivilege.READ,
-                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})), null, randomBoolean(), "a1");
-        IndicesPermission core = new IndicesPermission(group1, group2);
+        IndicesPermission core = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, randomBoolean(), "a1")
+            .addGroup(
+                IndexPrivilege.READ,
+                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})),
+                null,
+                randomBoolean(),
+                "a1")
+            .build();
         Map<String, IndicesAccessControl.IndexAccessControl> authzMap =
                 core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), lookup, fieldPermissionsCache);
         assertTrue(authzMap.get("a1").getFieldPermissions().grantsAccessTo("denied_field"));
@@ -262,16 +270,27 @@ public class IndicesPermissionTests extends ESTestCase {
         assertFalse(core.check("unknown"));
 
         // test with two indices
-        group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, randomBoolean(), "a1");
-        group2 = new IndicesPermission.Group(IndexPrivilege.ALL,
-                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})), null, randomBoolean(), "a1");
-        IndicesPermission.Group group3 = new IndicesPermission.Group(IndexPrivilege.ALL,
-                new FieldPermissions(fieldPermissionDef(new String[] { "*_field" }, new String[] { "denied_field" })), null,
-                randomBoolean(), "a2");
-        IndicesPermission.Group group4 = new IndicesPermission.Group(IndexPrivilege.ALL,
-                new FieldPermissions(fieldPermissionDef(new String[] { "*_field2" }, new String[] { "denied_field2" })), null,
-                randomBoolean(), "a2");
-        core = new IndicesPermission(group1, group2, group3, group4);
+        core = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, randomBoolean(), "a1")
+            .addGroup(
+                IndexPrivilege.ALL,
+                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})),
+                null,
+                randomBoolean(),
+                "a1")
+            .addGroup(
+                IndexPrivilege.ALL,
+                new FieldPermissions(fieldPermissionDef(new String[] { "*_field" }, new String[] { "denied_field" })),
+                null,
+                randomBoolean(),
+                "a2")
+            .addGroup(
+                IndexPrivilege.ALL,
+                new FieldPermissions(fieldPermissionDef(new String[] { "*_field2" }, new String[] { "denied_field2" })),
+                null,
+                randomBoolean(),
+                "a2")
+            .build();
         authzMap = core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "a2"), lookup, fieldPermissionsCache);
         assertFalse(authzMap.get("a1").getFieldPermissions().hasFieldLevelSecurity());
         assertFalse(authzMap.get("a2").getFieldPermissions().grantsAccessTo("denied_field2"));
@@ -295,7 +314,7 @@ public class IndicesPermissionTests extends ESTestCase {
         }
         final ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class,
                 () -> new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, randomBoolean(),
-                        indices.toArray(Strings.EMPTY_ARRAY)));
+                    RESTRICTED_INDICES_AUTOMATON, indices.toArray(Strings.EMPTY_ARRAY)));
         assertThat(e.getMessage(), containsString(indices.get(0)));
         assertThat(e.getMessage(), containsString("too complex to evaluate"));
     }
@@ -316,16 +335,20 @@ public class IndicesPermissionTests extends ESTestCase {
         SortedMap<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
 
         // allow_restricted_indices: false
-        IndicesPermission.Group group = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, false, "*");
-        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = new IndicesPermission(group).authorize(SearchAction.NAME,
-                Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup,
-                fieldPermissionsCache);
+        IndicesPermission indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, false, "*")
+            .build();
+        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = indicesPermission.authorize(SearchAction.NAME,
+            Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup,
+            fieldPermissionsCache);
         assertThat(authzMap.get(internalSecurityIndex).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,
+        indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, true, "*")
+            .build();
+        authzMap = indicesPermission.authorize(SearchAction.NAME,
                 Sets.newHashSet(internalSecurityIndex, RestrictedIndicesNames.SECURITY_MAIN_ALIAS), lookup,
                 fieldPermissionsCache);
         assertThat(authzMap.get(internalSecurityIndex).isGranted(), is(true));
@@ -334,7 +357,7 @@ public class IndicesPermissionTests extends ESTestCase {
 
     public void testAsyncSearchIndicesPermissions() {
         final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build();
-        final String asyncSearchIndex = RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2);
+        final String asyncSearchIndex = XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2);
         final Metadata metadata = new Metadata.Builder()
                 .put(new IndexMetadata.Builder(asyncSearchIndex)
                         .settings(indexSettings)
@@ -346,14 +369,18 @@ public class IndicesPermissionTests extends ESTestCase {
         SortedMap<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
 
         // allow_restricted_indices: false
-        IndicesPermission.Group group = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, false, "*");
-        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = new IndicesPermission(group).authorize(SearchAction.NAME,
+        IndicesPermission indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, false, "*")
+            .build();
+        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = indicesPermission.authorize(SearchAction.NAME,
                 Sets.newHashSet(asyncSearchIndex), lookup, fieldPermissionsCache);
         assertThat(authzMap.get(asyncSearchIndex).isGranted(), is(false));
 
         // allow_restricted_indices: true
-        group = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, true, "*");
-        authzMap = new IndicesPermission(group).authorize(SearchAction.NAME,
+        indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.ALL, new FieldPermissions(), null, true, "*")
+            .build();
+        authzMap = indicesPermission.authorize(SearchAction.NAME,
                 Sets.newHashSet(asyncSearchIndex), lookup, fieldPermissionsCache);
         assertThat(authzMap.get(asyncSearchIndex).isGranted(), is(true));
     }
@@ -376,9 +403,10 @@ public class IndicesPermissionTests extends ESTestCase {
 
         FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
         SortedMap<String, IndexAbstraction> lookup = metadata.getIndicesLookup();
-        IndicesPermission.Group group = new IndicesPermission.Group(IndexPrivilege.READ, new FieldPermissions(), null, false,
-                dataStreamName);
-        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = new IndicesPermission(group).authorize(
+        IndicesPermission indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.READ, new FieldPermissions(), null, false, dataStreamName)
+            .build();
+        Map<String, IndicesAccessControl.IndexAccessControl> authzMap = indicesPermission.authorize(
                 SearchAction.NAME,
                 Sets.newHashSet(backingIndices.stream().map(im -> im.getIndex().getName()).collect(Collectors.toList())),
                 lookup,
@@ -388,8 +416,10 @@ public class IndicesPermissionTests extends ESTestCase {
             assertThat(authzMap.get(im.getIndex().getName()).isGranted(), is(true));
         }
 
-        group = new IndicesPermission.Group(IndexPrivilege.CREATE_DOC, new FieldPermissions(), null, false, dataStreamName);
-        authzMap = new IndicesPermission(group).authorize(
+        indicesPermission = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.CREATE_DOC, new FieldPermissions(), null, false, dataStreamName)
+            .build();
+        authzMap = indicesPermission.authorize(
                 randomFrom(PutMappingAction.NAME, AutoPutMappingAction.NAME),
                 Sets.newHashSet(backingIndices.stream().map(im -> im.getIndex().getName()).collect(Collectors.toList())),
                 lookup,
@@ -421,11 +451,15 @@ public class IndicesPermissionTests extends ESTestCase {
         SortedMap<String, IndexAbstraction> lookup = metadata.build().getIndicesLookup();
 
         FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY);
-        IndicesPermission.Group group1 = new IndicesPermission.Group(IndexPrivilege.INDEX, new FieldPermissions(), null, randomBoolean(),
-                "test*");
-        IndicesPermission.Group group2 = new IndicesPermission.Group(IndexPrivilege.WRITE,
-                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})), null, randomBoolean(), "test_write*");
-        IndicesPermission core = new IndicesPermission(group1, group2);
+        IndicesPermission core = new IndicesPermission.Builder(RESTRICTED_INDICES_AUTOMATON)
+            .addGroup(IndexPrivilege.INDEX, new FieldPermissions(), null, randomBoolean(), "test*")
+            .addGroup(
+                IndexPrivilege.WRITE,
+                new FieldPermissions(fieldPermissionDef(null, new String[]{"denied_field"})),
+                null,
+                randomBoolean(),
+                "test_write*")
+            .build();
         Map<String, IndicesAccessControl.IndexAccessControl> authzMap =
                 core.authorize(PutMappingAction.NAME, Sets.newHashSet("test1", "test_write1"), lookup, fieldPermissionsCache);
         assertThat(authzMap.get("test1").isGranted(), is(true));

+ 2 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptorTests.java

@@ -33,6 +33,7 @@ import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
 import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
 import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.security.audit.AuditTrailService;
 
@@ -113,7 +114,7 @@ public class ResizeRequestInterceptorTests extends ESTestCase {
         when(threadPool.getThreadContext()).thenReturn(threadContext);
         AuditTrailService auditTrailService = new AuditTrailService(Collections.emptyList(), licenseState);
         final Authentication authentication = new Authentication(new User("john", "role"), new RealmRef(null, null, null), null);
-        Role role = Role.builder()
+        Role role = Role.builder(Automatons.EMPTY)
                 .add(IndexPrivilege.ALL, "target")
                 .add(IndexPrivilege.READ, "source")
                 .build();

+ 4 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java

@@ -13,6 +13,7 @@ import org.elasticsearch.cluster.metadata.IndexAbstraction;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 import org.junit.Before;
 
 import java.util.List;
@@ -31,7 +32,7 @@ public class PermissionTests extends ESTestCase {
 
     @Before
     public void init() {
-        Role.Builder builder = Role.builder("test");
+        Role.Builder builder = Role.builder(Automatons.EMPTY, "test");
         builder.add(MONITOR, "test_*", "/foo.*/");
         builder.add(READ, "baz_*foo", "/fool.*bar/");
         builder.add(MONITOR, "/bar.*/");
@@ -69,7 +70,7 @@ public class PermissionTests extends ESTestCase {
     }
 
     public void testBuildEmptyRole() {
-        Role.Builder permission = Role.builder(new String[] { "some_role" });
+        Role.Builder permission = Role.builder(Automatons.EMPTY, "some_role");
         Role role = permission.build();
         assertThat(role, notNullValue());
         assertThat(role.cluster(), notNullValue());
@@ -78,7 +79,7 @@ public class PermissionTests extends ESTestCase {
     }
 
     public void testRunAs() {
-        Role permission = Role.builder("some_role")
+        Role permission = Role.builder(Automatons.EMPTY, "some_role")
                 .runAs(new Privilege("name", "user1", "run*"))
                 .build();
         assertThat(permission.runAs().check("user1"), is(true));

+ 147 - 18
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java

@@ -6,17 +6,24 @@
  */
 package org.elasticsearch.xpack.security.authz.store;
 
+import org.apache.lucene.util.automaton.CharacterRunAutomaton;
 import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsAction;
 import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction;
 import org.elasticsearch.action.admin.cluster.state.ClusterStateAction;
+import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction;
+import org.elasticsearch.action.delete.DeleteAction;
 import org.elasticsearch.action.get.GetAction;
 import org.elasticsearch.action.index.IndexAction;
+import org.elasticsearch.action.search.SearchAction;
 import org.elasticsearch.action.support.PlainActionFuture;
+import org.elasticsearch.action.update.UpdateAction;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.cluster.metadata.IndexAbstraction;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.metadata.Metadata;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.bytes.BytesArray;
@@ -39,6 +46,7 @@ import org.elasticsearch.test.VersionUtils;
 import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportRequest;
 import org.elasticsearch.transport.TransportRequest.Empty;
+import org.elasticsearch.xpack.core.XPackPlugin;
 import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction;
 import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
@@ -60,6 +68,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableCluster
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
 import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;
+import org.elasticsearch.xpack.core.security.index.IndexAuditTrailField;
 import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
 import org.elasticsearch.xpack.core.security.support.MetadataUtils;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
@@ -67,11 +76,16 @@ import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.XPackUser;
+import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchAction;
 import org.elasticsearch.xpack.security.audit.AuditUtil;
+import org.elasticsearch.xpack.security.audit.index.IndexNameResolver;
 import org.elasticsearch.xpack.security.authc.ApiKeyService;
 import org.elasticsearch.xpack.security.authc.service.ServiceAccountService;
 import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry;
 import org.elasticsearch.xpack.security.support.SecurityIndexManager;
+import org.elasticsearch.xpack.core.security.test.TestRestrictedIndices;
+import org.hamcrest.Matchers;
+import org.joda.time.DateTime;
 
 import java.io.IOException;
 import java.time.Clock;
@@ -101,6 +115,7 @@ import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.AP
 import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY;
 import static org.elasticsearch.xpack.security.authc.ApiKeyService.API_KEY_ID_KEY;
 import static org.elasticsearch.xpack.security.authc.ApiKeyServiceTests.Utils.createApiKeyAuthentication;
+import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON;
 import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
@@ -129,6 +144,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             .put(XPackSettings.SECURITY_ENABLED.getKey(), true)
             .build();
 
+    private final IndexNameExpressionResolver resolver = TestRestrictedIndices.RESOLVER;
     private final FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY);
     private final String concreteSecurityIndexName = randomFrom(
         RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_7);
@@ -363,7 +379,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore,
             reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(settings),
             new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-            mock(ServiceAccountService.class), documentSubsetBitsetCache,
+            mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
             rds -> effectiveRoleDescriptors.set(rds));
         verify(fileRolesStore).addListener(anyConsumer()); // adds a listener in ctor
 
@@ -404,7 +420,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> effectiveRoleDescriptors.set(rds));
         verify(fileRolesStore).addListener(anyConsumer()); // adds a listener in ctor
 
@@ -493,7 +509,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
                 new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                                 mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, inMemoryProvider2),
                                 new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(() -> 0),
-                                cache, mock(ApiKeyService.class), mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                                cache, mock(ApiKeyService.class), mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                                 rds -> effectiveRoleDescriptors.set(rds));
 
         final Set<String> roleNames = Sets.newHashSet("roleA", "roleB", "unknown");
@@ -555,7 +571,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }, null);
         FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY);
         PlainActionFuture<Role> future = new PlainActionFuture<>();
-        CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(flsRole, addsL1Fields), cache, null, future);
+        CompositeRolesStore.buildRoleFromDescriptors(
+            Sets.newHashSet(flsRole, addsL1Fields), cache, null, RESTRICTED_INDICES_AUTOMATON, future);
         Role role = future.actionGet();
 
         Metadata metadata = Metadata.builder()
@@ -652,7 +669,8 @@ public class CompositeRolesStoreTests extends ESTestCase {
             listener.onResponse(set);
             return null;
         }).when(privilegeStore).getPrivileges(anyCollectionOf(String.class), anyCollectionOf(String.class), anyActionListener());
-        CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore, future);
+        CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore,
+            RESTRICTED_INDICES_AUTOMATON, future);
         Role role = future.actionGet();
 
         assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3), authentication), equalTo(true));
@@ -725,7 +743,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
                 mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, failingProvider),
                 new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(() -> 0),
                 cache, mock(ApiKeyService.class), mock(ServiceAccountService.class),
-                documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
+                documentSubsetBitsetCache, resolver, rds -> effectiveRoleDescriptors.set(rds));
 
         final Set<String> roleNames = Sets.newHashSet("roleA", "roleB", "unknown");
         PlainActionFuture<Role> future = new PlainActionFuture<>();
@@ -775,7 +793,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
             Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache,
             mock(ApiKeyService.class), mock(ServiceAccountService.class),
-            documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
+            documentSubsetBitsetCache, resolver, rds -> effectiveRoleDescriptors.set(rds));
 
         Set<String> roleNames = Sets.newHashSet("roleA");
         PlainActionFuture<Role> future = new PlainActionFuture<>();
@@ -791,7 +809,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
             Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache,
             mock(ApiKeyService.class), mock(ServiceAccountService.class),
-            documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
+            documentSubsetBitsetCache, resolver, rds -> effectiveRoleDescriptors.set(rds));
         // these licenses allow custom role providers
         xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), true,
             Long.MAX_VALUE);
@@ -810,7 +828,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class),
             Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache,
             mock(ApiKeyService.class), mock(ServiceAccountService.class),
-            documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds));
+            documentSubsetBitsetCache, resolver, rds -> effectiveRoleDescriptors.set(rds));
         xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), false,
             Long.MAX_VALUE);
         roleNames = Sets.newHashSet("roleA");
@@ -845,7 +863,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
                 Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY),
                 new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> {}) {
             @Override
             public void invalidateAll() {
@@ -900,7 +918,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
                 fileRolesStore, nativeRolesStore, reservedRolesStore,
                 mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-            mock(ServiceAccountService.class), documentSubsetBitsetCache, rds -> {}) {
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver, rds -> {}) {
             @Override
             public void invalidateAll() {
                 numInvalidation.incrementAndGet();
@@ -1001,7 +1019,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> effectiveRoleDescriptors.set(rds));
         verify(fileRolesStore).addListener(anyConsumer()); // adds a listener in ctor
 
@@ -1010,7 +1028,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         Authentication auth = new Authentication(XPackUser.INSTANCE, new RealmRef("name", "type", "node"), null);
         compositeRolesStore.getRoles(XPackUser.INSTANCE, auth, rolesFuture);
         Role roles = rolesFuture.actionGet();
-        assertThat(roles, equalTo(XPackUser.ROLE));
+        assertThat(roles, equalTo(compositeRolesStore.getXpackUserRole()));
         assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
         verifyNoMoreInteractions(fileRolesStore, nativeRolesStore, reservedRolesStore);
 
@@ -1019,7 +1037,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         auth = new Authentication(AsyncSearchUser.INSTANCE, new RealmRef("name", "type", "node"), null);
         compositeRolesStore.getRoles(AsyncSearchUser.INSTANCE, auth, rolesFuture);
         roles = rolesFuture.actionGet();
-        assertThat(roles, equalTo(AsyncSearchUser.ROLE));
+        assertThat(roles, equalTo(compositeRolesStore.getAsyncSearchUserRole()));
         assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
         verifyNoMoreInteractions(fileRolesStore, nativeRolesStore, reservedRolesStore);
     }
@@ -1044,7 +1062,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, mock(ApiKeyService.class),
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> effectiveRoleDescriptors.set(rds));
         verify(fileRolesStore).addListener(anyConsumer()); // adds a listener in ctor
         IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
@@ -1085,7 +1103,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 nativePrivStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, apiKeyService,
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> effectiveRoleDescriptors.set(rds));
         AuditUtil.getOrGenerateRequestId(threadContext);
         final Version version = randomFrom(Version.CURRENT, VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_8_1));
@@ -1141,7 +1159,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore,
                 nativePrivStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS),
                 new XPackLicenseState(() -> 0), cache, apiKeyService,
-                mock(ServiceAccountService.class), documentSubsetBitsetCache,
+                mock(ServiceAccountService.class), documentSubsetBitsetCache, resolver,
                 rds -> effectiveRoleDescriptors.set(rds));
         AuditUtil.getOrGenerateRequestId(threadContext);
         final Version version = randomFrom(Version.CURRENT, VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_8_1));
@@ -1283,6 +1301,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
             apiKeyService,
             mock(ServiceAccountService.class),
             documentSubsetBitsetCache,
+            resolver,
             rds -> effectiveRoleDescriptors.set(rds));
         AuditUtil.getOrGenerateRequestId(threadContext);
         final BytesArray roleBytes = new BytesArray("{\"a role\": {\"cluster\": [\"all\"]}}");
@@ -1368,6 +1387,110 @@ public class CompositeRolesStoreTests extends ESTestCase {
                 AuthenticationType.ANONYMOUS), Collections.emptyMap());
     }
 
+    public void testXPackUserCanAccessNonRestrictedIndices() {
+        CharacterRunAutomaton restrictedAutomaton = new CharacterRunAutomaton(RESTRICTED_INDICES_AUTOMATON);
+        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
+            Predicate<IndexAbstraction> predicate = getXPackUserRole().indices().allowedIndicesMatcher(action);
+            IndexAbstraction index = mockIndexAbstraction(randomAlphaOfLengthBetween(3, 12));
+            if (false == restrictedAutomaton.run(index.getName())) {
+                assertThat(predicate.test(index), Matchers.is(true));
+            }
+            index = mockIndexAbstraction("." + randomAlphaOfLengthBetween(3, 12));
+            if (false == restrictedAutomaton.run(index.getName())) {
+                assertThat(predicate.test(index), Matchers.is(true));
+            }
+        }
+    }
+
+    public void testXPackUserCannotAccessSecurityOrAsyncSearch() {
+        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
+            Predicate<IndexAbstraction> predicate = getXPackUserRole().indices().allowedIndicesMatcher(action);
+            for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
+                assertThat(predicate.test(mockIndexAbstraction(index)), Matchers.is(false));
+            }
+            assertThat(predicate.test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2))),
+                Matchers.is(false));
+        }
+    }
+
+    public void testXPackUserCanReadAuditTrail() {
+        final String action = randomFrom(GetAction.NAME, SearchAction.NAME);
+        final Predicate<IndexAbstraction> predicate = getXPackUserRole().indices().allowedIndicesMatcher(action);
+        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(true));
+    }
+
+    public void testXPackUserCannotWriteToAuditTrail() {
+        final String action = randomFrom(IndexAction.NAME, UpdateAction.NAME);
+        final Predicate<IndexAbstraction> predicate = getXPackUserRole().indices().allowedIndicesMatcher(action);
+        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(false));
+    }
+
+    public void testAsyncSearchUserCannotAccessNonRestrictedIndices() {
+        CharacterRunAutomaton restrictedAutomaton = new CharacterRunAutomaton(RESTRICTED_INDICES_AUTOMATON);
+        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
+            Predicate<IndexAbstraction> predicate = getAsyncSearchUserRole().indices().allowedIndicesMatcher(action);
+            IndexAbstraction index = mockIndexAbstraction(randomAlphaOfLengthBetween(3, 12));
+            if (false == restrictedAutomaton.run(index.getName())) {
+                assertThat(predicate.test(index), Matchers.is(false));
+            }
+            index = mockIndexAbstraction("." + randomAlphaOfLengthBetween(3, 12));
+            if (false == restrictedAutomaton.run(index.getName())) {
+                assertThat(predicate.test(index), Matchers.is(false));
+            }
+        }
+    }
+
+    public void testAsyncSearchUserCanAccessOnlyAsyncSearchRestrictedIndices() {
+        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
+            final Predicate<IndexAbstraction> predicate = getAsyncSearchUserRole().indices().allowedIndicesMatcher(action);
+            for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
+                assertThat(predicate.test(mockIndexAbstraction(index)), Matchers.is(false));
+            }
+            assertThat(predicate.test(mockIndexAbstraction(XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 3))),
+                Matchers.is(true));
+        }
+    }
+
+    public void testAsyncSearchUserHasNoClusterPrivileges() {
+        for (String action : Arrays.asList(ClusterStateAction.NAME, GetWatchAction.NAME, ClusterStatsAction.NAME, NodesStatsAction.NAME)) {
+            assertThat(getAsyncSearchUserRole().cluster().check(action, mock(TransportRequest.class), mock(Authentication.class)),
+                Matchers.is(false));
+        }
+    }
+
+    // async search can't read/write audit trail
+    public void testAsyncSearchUserCannotReadAuditTrail() {
+        final String action = randomFrom(GetAction.NAME, SearchAction.NAME);
+        final Predicate<IndexAbstraction> predicate = getAsyncSearchUserRole().indices().allowedIndicesMatcher(action);
+        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(false));
+    }
+
+    public void testAsyncSearchUserCannotWriteToAuditTrail() {
+        final String action = randomFrom(IndexAction.NAME, UpdateAction.NAME);
+        final Predicate<IndexAbstraction> predicate = getAsyncSearchUserRole().indices().allowedIndicesMatcher(action);
+        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(false));
+    }
+
+
+    public void testXpackUserHasClusterPrivileges() {
+        for (String action : Arrays.asList(ClusterStateAction.NAME, GetWatchAction.NAME, ClusterStatsAction.NAME, NodesStatsAction.NAME)) {
+            assertThat(getXPackUserRole().cluster().check(action, mock(TransportRequest.class), mock(Authentication.class)),
+                Matchers.is(true));
+        }
+    }
+
+    private Role getXPackUserRole() {
+        CompositeRolesStore compositeRolesStore =
+            buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, null, null, null, null, null, null, null, null, null);
+        return compositeRolesStore.getXpackUserRole();
+    }
+
+    private Role getAsyncSearchUserRole() {
+        CompositeRolesStore compositeRolesStore =
+            buildCompositeRolesStore(SECURITY_ENABLED_SETTINGS, null, null, null, null, null, null, null, null, null);
+        return compositeRolesStore.getAsyncSearchUserRole();
+    }
+
     private CompositeRolesStore buildCompositeRolesStore(Settings settings,
                                                          @Nullable FileRolesStore fileRolesStore,
                                                          @Nullable NativeRolesStore nativeRolesStore,
@@ -1424,7 +1547,7 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }
         return new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, privilegeStore,
             Collections.emptyList(), new ThreadContext(settings), licenseState, cache, apiKeyService,
-            serviceAccountService, documentSubsetBitsetCache, roleConsumer);
+            serviceAccountService, documentSubsetBitsetCache, resolver, roleConsumer);
     }
 
     private DocumentSubsetBitsetCache buildBitsetCache() {
@@ -1464,6 +1587,12 @@ public class CompositeRolesStoreTests extends ESTestCase {
         }
     }
 
+    private String getAuditLogName() {
+        final DateTime date = new DateTime().plusDays(randomIntBetween(1, 360));
+        final IndexNameResolver.Rollover rollover = randomFrom(IndexNameResolver.Rollover.values());
+        return IndexNameResolver.resolve(IndexAuditTrailField.INDEX_NAME_PREFIX, date, rollover);
+    }
+
     private IndexAbstraction mockIndexAbstraction(String name) {
         IndexAbstraction mock = mock(IndexAbstraction.class);
         when(mock.getName()).thenReturn(name);

+ 15 - 11
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java

@@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.store;
 
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.Logger;
+import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.MinimizationOperations;
 import org.apache.lucene.util.automaton.Operations;
 import org.elasticsearch.common.xcontent.ParseField;
@@ -35,6 +36,7 @@ import org.elasticsearch.xpack.core.security.authz.permission.Role;
 import org.elasticsearch.xpack.core.security.authz.permission.RunAsPermission;
 import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
+import org.elasticsearch.xpack.core.security.support.Automatons;
 
 import java.io.BufferedWriter;
 import java.io.IOException;
@@ -69,6 +71,8 @@ import static org.mockito.Mockito.when;
 
 public class FileRolesStoreTests extends ESTestCase {
 
+    public Automaton reservedNamesAutomaton = Automatons.EMPTY;
+
     @Override
     protected NamedXContentRegistry xContentRegistry() {
         return new NamedXContentRegistry(singletonList(new NamedXContentRegistry.Entry(QueryBuilder.class,
@@ -85,7 +89,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         RoleDescriptor descriptor = roles.get("role1");
         assertNotNull(descriptor);
-        Role role = Role.builder(descriptor, null).build();
+        Role role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role1" }));
         assertThat(role.cluster(), notNullValue());
@@ -113,7 +117,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role1.ab");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role1.ab" }));
         assertThat(role.cluster(), notNullValue());
@@ -125,7 +129,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role2");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role2" }));
         assertThat(role.cluster(), notNullValue());
@@ -136,7 +140,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role3");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role3" }));
         assertThat(role.cluster(), notNullValue());
@@ -160,7 +164,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role_run_as");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role_run_as" }));
         assertThat(role.cluster(), notNullValue());
@@ -173,7 +177,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role_run_as1");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role_run_as1" }));
         assertThat(role.cluster(), notNullValue());
@@ -186,7 +190,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role_fields");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role_fields" }));
         assertThat(role.cluster(), notNullValue());
@@ -208,7 +212,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role_query");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role_query" }));
         assertThat(role.cluster(), notNullValue());
@@ -229,7 +233,7 @@ public class FileRolesStoreTests extends ESTestCase {
 
         descriptor = roles.get("role_query_fields");
         assertNotNull(descriptor);
-        role = Role.builder(descriptor, null).build();
+        role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "role_query_fields" }));
         assertThat(role.cluster(), notNullValue());
@@ -388,7 +392,7 @@ public class FileRolesStoreTests extends ESTestCase {
             descriptors = store.roleDescriptors(Collections.singleton("role5"));
             assertThat(descriptors, notNullValue());
             assertEquals(1, descriptors.size());
-            Role role = Role.builder(descriptors.iterator().next(), null).build();
+            Role role = Role.builder(descriptors.iterator().next(), null, reservedNamesAutomaton).build();
             assertThat(role, notNullValue());
             assertThat(role.names(), equalTo(new String[] { "role5" }));
             assertThat(role.cluster().check("cluster:monitor/foo/bar", request, authentication), is(true));
@@ -475,7 +479,7 @@ public class FileRolesStoreTests extends ESTestCase {
         assertThat(roles, hasKey("valid_role"));
         RoleDescriptor descriptor = roles.get("valid_role");
         assertNotNull(descriptor);
-        Role role = Role.builder(descriptor, null).build();
+        Role role = Role.builder(descriptor, null, reservedNamesAutomaton).build();
         assertThat(role, notNullValue());
         assertThat(role.names(), equalTo(new String[] { "valid_role" }));
 

+ 0 - 72
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/AsyncSearchUserTests.java

@@ -1,72 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-package org.elasticsearch.xpack.security.user;
-
-import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsAction;
-import org.elasticsearch.action.admin.cluster.state.ClusterStateAction;
-import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction;
-import org.elasticsearch.action.delete.DeleteAction;
-import org.elasticsearch.action.get.GetAction;
-import org.elasticsearch.action.index.IndexAction;
-import org.elasticsearch.action.search.SearchAction;
-import org.elasticsearch.cluster.metadata.IndexAbstraction;
-import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.transport.TransportRequest;
-import org.elasticsearch.xpack.core.security.authc.Authentication;
-import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
-import org.elasticsearch.xpack.core.security.user.AsyncSearchUser;
-import org.elasticsearch.xpack.core.watcher.transport.actions.get.GetWatchAction;
-import org.hamcrest.Matchers;
-
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class AsyncSearchUserTests extends ESTestCase {
-
-    public void testAsyncSearchUserCannotAccessNonRestrictedIndices() {
-        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
-            Predicate<IndexAbstraction> predicate = AsyncSearchUser.ROLE.indices().allowedIndicesMatcher(action);
-            IndexAbstraction index = mockIndexAbstraction(randomAlphaOfLengthBetween(3, 12));
-            if (false == RestrictedIndicesNames.isRestricted(index.getName())) {
-                assertThat(predicate.test(index), Matchers.is(false));
-            }
-            index = mockIndexAbstraction("." + randomAlphaOfLengthBetween(3, 12));
-            if (false == RestrictedIndicesNames.isRestricted(index.getName())) {
-                assertThat(predicate.test(index), Matchers.is(false));
-            }
-        }
-    }
-
-    public void testAsyncSearchUserCanAccessOnlyAsyncSearchRestrictedIndices() {
-        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
-            final Predicate<IndexAbstraction> predicate = AsyncSearchUser.ROLE.indices().allowedIndicesMatcher(action);
-            for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
-                assertThat(predicate.test(mockIndexAbstraction(index)), Matchers.is(false));
-            }
-            assertThat(predicate.test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 3))),
-                    Matchers.is(true));
-        }
-    }
-
-    public void testAsyncSearchUserHasNoClusterPrivileges() {
-        for (String action : Arrays.asList(ClusterStateAction.NAME, GetWatchAction.NAME, ClusterStatsAction.NAME, NodesStatsAction.NAME)) {
-            assertThat(AsyncSearchUser.ROLE.cluster().check(action, mock(TransportRequest.class), mock(Authentication.class)),
-                    Matchers.is(false));
-        }
-    }
-
-    private IndexAbstraction mockIndexAbstraction(String name) {
-        IndexAbstraction mock = mock(IndexAbstraction.class);
-        when(mock.getName()).thenReturn(name);
-        when(mock.getType()).thenReturn(randomFrom(IndexAbstraction.Type.CONCRETE_INDEX,
-                IndexAbstraction.Type.ALIAS, IndexAbstraction.Type.DATA_STREAM));
-        return mock;
-    }
-}

+ 0 - 81
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/user/XPackUserTests.java

@@ -1,81 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-package org.elasticsearch.xpack.security.user;
-
-import org.elasticsearch.action.delete.DeleteAction;
-import org.elasticsearch.action.get.GetAction;
-import org.elasticsearch.action.index.IndexAction;
-import org.elasticsearch.action.search.SearchAction;
-import org.elasticsearch.action.update.UpdateAction;
-import org.elasticsearch.cluster.metadata.IndexAbstraction;
-import org.elasticsearch.test.ESTestCase;
-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.hamcrest.Matchers;
-import org.joda.time.DateTime;
-
-import java.util.Arrays;
-import java.util.function.Predicate;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class XPackUserTests extends ESTestCase {
-
-    public void testXPackUserCanAccessNonSecurityIndices() {
-        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
-            Predicate<IndexAbstraction> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
-            IndexAbstraction index = mockIndexAbstraction(randomAlphaOfLengthBetween(3, 12));
-            if (false == RestrictedIndicesNames.isRestricted(index.getName())) {
-                assertThat(predicate.test(index), Matchers.is(true));
-            }
-            index = mockIndexAbstraction("." + randomAlphaOfLengthBetween(3, 12));
-            if (false == RestrictedIndicesNames.isRestricted(index.getName())) {
-                assertThat(predicate.test(index), Matchers.is(true));
-            }
-        }
-    }
-
-    public void testXPackUserCannotAccessRestrictedIndices() {
-        for (String action : Arrays.asList(GetAction.NAME, DeleteAction.NAME, SearchAction.NAME, IndexAction.NAME)) {
-            Predicate<IndexAbstraction> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
-            for (String index : RestrictedIndicesNames.RESTRICTED_NAMES) {
-                assertThat(predicate.test(mockIndexAbstraction(index)), Matchers.is(false));
-            }
-            assertThat(predicate.test(mockIndexAbstraction(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2))),
-                    Matchers.is(false));
-        }
-    }
-
-    public void testXPackUserCanReadAuditTrail() {
-        final String action = randomFrom(GetAction.NAME, SearchAction.NAME);
-        final Predicate<IndexAbstraction> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
-        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(true));
-    }
-
-    public void testXPackUserCannotWriteToAuditTrail() {
-        final String action = randomFrom(IndexAction.NAME, UpdateAction.NAME);
-        final Predicate<IndexAbstraction> predicate = XPackUser.ROLE.indices().allowedIndicesMatcher(action);
-        assertThat(predicate.test(mockIndexAbstraction(getAuditLogName())), Matchers.is(false));
-    }
-
-    private String getAuditLogName() {
-        final DateTime date = new DateTime().plusDays(randomIntBetween(1, 360));
-        final IndexNameResolver.Rollover rollover = randomFrom(IndexNameResolver.Rollover.values());
-        return IndexNameResolver.resolve(IndexAuditTrailField.INDEX_NAME_PREFIX, date, rollover);
-    }
-
-    private IndexAbstraction mockIndexAbstraction(String name) {
-        IndexAbstraction mock = mock(IndexAbstraction.class);
-        when(mock.getName()).thenReturn(name);
-        when(mock.getType()).thenReturn(randomFrom(IndexAbstraction.Type.CONCRETE_INDEX,
-                IndexAbstraction.Type.ALIAS, IndexAbstraction.Type.DATA_STREAM));
-        return mock;
-    }
-}

+ 2 - 0
x-pack/plugin/sql/qa/server/security/build.gradle

@@ -2,6 +2,7 @@ apply plugin: 'elasticsearch.internal-test-artifact'
 
 dependencies {
   testImplementation project(':x-pack:plugin:core')
+  testImplementation(testArtifact(project(xpackModule('core'))))
 }
 
 Project mainProject = project
@@ -15,6 +16,7 @@ subprojects {
 
   dependencies {
     testImplementation project(":x-pack:plugin:core")
+    testImplementation(testArtifact(project(xpackModule('core'))))
     testArtifacts testArtifact(project(mainProject.path))
   }
 

+ 6 - 2
x-pack/plugin/sql/qa/server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security/SqlSecurityTestCase.java

@@ -7,6 +7,7 @@
 package org.elasticsearch.xpack.sql.qa.security;
 
 import org.apache.lucene.util.SuppressForbidden;
+import org.apache.lucene.util.automaton.CharacterRunAutomaton;
 import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.SpecialPermission;
 import org.elasticsearch.action.admin.indices.get.GetIndexAction;
@@ -20,7 +21,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.test.rest.ESRestTestCase;
-import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
+import org.elasticsearch.xpack.core.security.test.TestRestrictedIndices;
 import org.hamcrest.Matcher;
 import org.hamcrest.Matchers;
 import org.junit.AfterClass;
@@ -641,13 +642,16 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
                                     List<String> castIndices = (ArrayList<String>) log.get("indices");
                                     indices = castIndices;
                                     if ("test_admin".equals(log.get("user.name"))) {
+                                        CharacterRunAutomaton restrictedAutomaton = new CharacterRunAutomaton(
+                                            TestRestrictedIndices.RESTRICTED_INDICES_AUTOMATON
+                                        );
                                         /*
                                          * Sometimes we accidentally sneak access to the security tables. This is fine,
                                          * SQL drops them from the interface. So we might have access to them, but we
                                          * don't show them.
                                          */
                                         indices = indices.stream()
-                                            .filter(idx -> false == RestrictedIndicesNames.isRestricted(idx))
+                                            .filter(idx -> false == restrictedAutomaton.run(idx))
                                             .collect(Collectors.toList());
                                     }
                                 }