Browse Source

Merge pull request ESQL-1251 from elastic/main

🤖 ESQL: Merge upstream
elasticsearchmachine 2 years ago
parent
commit
e133577bf5
29 changed files with 656 additions and 194 deletions
  1. 2 1
      build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JarApiComparisonTask.java
  2. 6 0
      docs/changelog/96606.yaml
  3. 12 4
      modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java
  4. 48 0
      modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportActionTests.java
  5. 0 3
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestDeleteIndexTemplateAction.java
  6. 0 3
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndexTemplateAction.java
  7. 0 3
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutIndexTemplateAction.java
  8. 29 12
      test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java
  9. 6 0
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalSpecBuilder.java
  10. 5 0
      test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalSpecBuilder.java
  11. 2 0
      x-pack/docs/build.gradle
  12. 4 4
      x-pack/docs/en/rest-api/security.asciidoc
  13. 233 2
      x-pack/docs/en/rest-api/security/create-cross-cluster-api-key.asciidoc
  14. 246 1
      x-pack/docs/en/rest-api/security/update-cross-cluster-api-key.asciidoc
  15. 2 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CrossClusterAccessSubjectInfo.java
  16. 0 74
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/CrossClusterAccessUser.java
  17. 0 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java
  18. 43 1
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java
  19. 0 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/UsernamesField.java
  20. 5 6
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/AuthenticationTestHelper.java
  21. 2 2
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java
  22. 0 4
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/user/InternalUsersTests.java
  23. 3 3
      x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityFcActionAuthorizationIT.java
  24. 2 2
      x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/crossclusteraccess/CrossClusterAccessHeadersForCcsRestIT.java
  25. 2 4
      x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java
  26. 1 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java
  27. 0 52
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java
  28. 1 4
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/crossclusteraccess/CrossClusterAccessAuthenticationServiceIntegTests.java
  29. 2 2
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java

+ 2 - 1
build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JarApiComparisonTask.java

@@ -14,6 +14,7 @@ import org.gradle.api.provider.Property;
 import org.gradle.api.tasks.CacheableTask;
 import org.gradle.api.tasks.CompileClasspath;
 import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.jvm.Jvm;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -127,7 +128,7 @@ public abstract class JarApiComparisonTask extends PrecommitTask {
         static List<String> disassemble(String location, String modulePath, String classpath) {
             ProcessBuilder pb = new ProcessBuilder();
             List<String> command = new ArrayList<>();
-            command.add("javap");
+            command.add(Jvm.current().getExecutable("javap").getPath());
             if (modulePath != null) {
                 command.add("--module-path");
                 command.add(modulePath);

+ 6 - 0
docs/changelog/96606.yaml

@@ -0,0 +1,6 @@
+pr: 96606
+summary: The get data stream api incorrectly prints warning log for upgraded tsdb
+  data streams
+area: TSDB
+type: bug
+issues: []

+ 12 - 4
modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java

@@ -29,7 +29,6 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.core.Tuple;
 import org.elasticsearch.index.Index;
 import org.elasticsearch.index.IndexMode;
-import org.elasticsearch.index.IndexSettings;
 import org.elasticsearch.indices.SystemDataStreamDescriptor;
 import org.elasticsearch.indices.SystemIndices;
 import org.elasticsearch.tasks.Task;
@@ -129,10 +128,14 @@ public class GetDataStreamsTransportAction extends TransportMasterNodeReadAction
             if (dataStream.getIndexMode() == IndexMode.TIME_SERIES) {
                 List<Tuple<Instant, Instant>> ranges = new ArrayList<>();
                 Tuple<Instant, Instant> current = null;
+                String previousIndexName = null;
                 for (Index index : dataStream.getIndices()) {
                     IndexMetadata metadata = state.getMetadata().index(index);
-                    Instant start = IndexSettings.TIME_SERIES_START_TIME.get(metadata.getSettings());
-                    Instant end = IndexSettings.TIME_SERIES_END_TIME.get(metadata.getSettings());
+                    if (metadata.getIndexMode() != IndexMode.TIME_SERIES) {
+                        continue;
+                    }
+                    Instant start = metadata.getTimeSeriesStart();
+                    Instant end = metadata.getTimeSeriesEnd();
                     if (current == null) {
                         current = new Tuple<>(start, end);
                     } else if (current.v2().compareTo(start) == 0) {
@@ -142,10 +145,14 @@ public class GetDataStreamsTransportAction extends TransportMasterNodeReadAction
                         current = new Tuple<>(start, end);
                     } else {
                         String message = "previous backing index ["
+                            + previousIndexName
+                            + "] range ["
                             + current.v1()
                             + "/"
                             + current.v2()
-                            + "] range is colliding with current backing index range ["
+                            + "] range is colliding with current backing ["
+                            + index.getName()
+                            + "] index range ["
                             + start
                             + "/"
                             + end
@@ -153,6 +160,7 @@ public class GetDataStreamsTransportAction extends TransportMasterNodeReadAction
                         assert current.v2().compareTo(start) < 0 : message;
                         LOGGER.warn(message);
                     }
+                    previousIndexName = index.getName();
                 }
                 if (current != null) {
                     ranges.add(current);

+ 48 - 0
modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportActionTests.java

@@ -15,7 +15,9 @@ import org.elasticsearch.cluster.metadata.DataStreamTestHelper;
 import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
 import org.elasticsearch.cluster.metadata.Metadata;
 import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.core.Tuple;
+import org.elasticsearch.index.Index;
 import org.elasticsearch.index.IndexNotFoundException;
 import org.elasticsearch.indices.SystemIndices;
 import org.elasticsearch.indices.TestIndexNameExpressionResolver;
@@ -196,4 +198,50 @@ public class GetDataStreamsTransportActionTests extends ESTestCase {
             )
         );
     }
+
+    @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/96672")
+    public void testGetTimeSeriesMixedDataStream() {
+        Instant now = Instant.now().truncatedTo(ChronoUnit.SECONDS);
+        String dataStream1 = "ds-1";
+        Instant twoHoursAgo = now.minus(2, ChronoUnit.HOURS);
+        Instant twoHoursAhead = now.plus(2, ChronoUnit.HOURS);
+
+        ClusterState state;
+        {
+            var mBuilder = new Metadata.Builder();
+            DataStreamTestHelper.getClusterStateWithDataStreams(
+                mBuilder,
+                List.of(Tuple.tuple(dataStream1, 2)),
+                List.of(),
+                now.toEpochMilli(),
+                Settings.EMPTY,
+                0,
+                false
+            );
+            DataStreamTestHelper.getClusterStateWithDataStream(mBuilder, dataStream1, List.of(new Tuple<>(twoHoursAgo, twoHoursAhead)));
+            state = ClusterState.builder(new ClusterName("_name")).metadata(mBuilder).build();
+        }
+
+        var req = new GetDataStreamAction.Request(new String[] {});
+        var response = GetDataStreamsTransportAction.innerOperation(
+            state,
+            req,
+            resolver,
+            systemIndices,
+            ClusterSettings.createBuiltInClusterSettings()
+        );
+        assertThat(
+            response.getDataStreams(),
+            contains(
+                allOf(
+                    transformedMatch(d -> d.getDataStream().getName(), equalTo(dataStream1)),
+                    transformedMatch(
+                        d -> d.getDataStream().getIndices().stream().map(Index::getName).toList(),
+                        contains(".ds-ds-1-2023.06.06-000001", ".ds-ds-1-2023.06.06-000002", ".ds-ds-1-2023.06.06-000003")
+                    ),
+                    transformedMatch(d -> d.getTimeSeries().temporalRanges(), contains(new Tuple<>(twoHoursAgo, twoHoursAhead)))
+                )
+            )
+        );
+    }
 }

+ 0 - 3
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestDeleteIndexTemplateAction.java

@@ -11,8 +11,6 @@ import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplat
 import org.elasticsearch.client.internal.node.NodeClient;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.Scope;
-import org.elasticsearch.rest.ServerlessScope;
 import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
@@ -20,7 +18,6 @@ import java.util.List;
 
 import static org.elasticsearch.rest.RestRequest.Method.DELETE;
 
-@ServerlessScope(Scope.PUBLIC)
 public class RestDeleteIndexTemplateAction extends BaseRestHandler {
 
     @Override

+ 0 - 3
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestGetIndexTemplateAction.java

@@ -18,8 +18,6 @@ import org.elasticsearch.core.RestApiVersion;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
 import org.elasticsearch.rest.RestStatus;
-import org.elasticsearch.rest.Scope;
-import org.elasticsearch.rest.ServerlessScope;
 import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
@@ -35,7 +33,6 @@ import static org.elasticsearch.rest.RestStatus.OK;
 /**
  * The REST handler for get template and head template APIs.
  */
-@ServerlessScope(Scope.PUBLIC)
 public class RestGetIndexTemplateAction extends BaseRestHandler {
 
     private static final Set<String> COMPATIBLE_RESPONSE_PARAMS = addToCopy(Settings.FORMAT_PARAMS, INCLUDE_TYPE_NAME_PARAMETER);

+ 0 - 3
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestPutIndexTemplateAction.java

@@ -17,8 +17,6 @@ import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.core.RestApiVersion;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.Scope;
-import org.elasticsearch.rest.ServerlessScope;
 import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
@@ -29,7 +27,6 @@ import static java.util.Arrays.asList;
 import static org.elasticsearch.rest.RestRequest.Method.POST;
 import static org.elasticsearch.rest.RestRequest.Method.PUT;
 
-@ServerlessScope(Scope.PUBLIC)
 public class RestPutIndexTemplateAction extends BaseRestHandler {
 
     private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(RestPutIndexTemplateAction.class);

+ 29 - 12
test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java

@@ -313,6 +313,19 @@ public final class DataStreamTestHelper {
         boolean replicated
     ) {
         Metadata.Builder builder = Metadata.builder();
+        getClusterStateWithDataStreams(builder, dataStreams, indexNames, currentTime, settings, replicas, replicated);
+        return ClusterState.builder(new ClusterName("_name")).metadata(builder).build();
+    }
+
+    public static void getClusterStateWithDataStreams(
+        Metadata.Builder builder,
+        List<Tuple<String, Integer>> dataStreams,
+        List<String> indexNames,
+        long currentTime,
+        Settings settings,
+        int replicas,
+        boolean replicated
+    ) {
         builder.put(
             "template_1",
             new ComposableIndexTemplate(List.of("*"), null, null, null, null, null, new ComposableIndexTemplate.DataStreamTemplate())
@@ -345,8 +358,6 @@ public final class DataStreamTestHelper {
         for (IndexMetadata index : allIndices) {
             builder.put(index, false);
         }
-
-        return ClusterState.builder(new ClusterName("_name")).metadata(builder).build();
     }
 
     public static ClusterState getClusterStateWithDataStream(String dataStream, List<Tuple<Instant, Instant>> timeSlices) {
@@ -357,11 +368,17 @@ public final class DataStreamTestHelper {
 
     public static void getClusterStateWithDataStream(
         Metadata.Builder builder,
-        String dataStream,
+        String dataStreamName,
         List<Tuple<Instant, Instant>> timeSlices
     ) {
         List<IndexMetadata> backingIndices = new ArrayList<>();
-        int generation = 1;
+        DataStream existing = builder.dataStream(dataStreamName);
+        if (existing != null) {
+            for (Index index : existing.getIndices()) {
+                backingIndices.add(builder.getSafe(index));
+            }
+        }
+        long generation = existing != null ? existing.getGeneration() + 1 : 1L;
         for (Tuple<Instant, Instant> tuple : timeSlices) {
             Instant start = tuple.v1();
             Instant end = tuple.v2();
@@ -371,20 +388,20 @@ public final class DataStreamTestHelper {
                 .put(IndexSettings.TIME_SERIES_START_TIME.getKey(), DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.format(start))
                 .put(IndexSettings.TIME_SERIES_END_TIME.getKey(), DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.format(end))
                 .build();
-            var im = createIndexMetadata(getDefaultBackingIndexName(dataStream, generation, start.toEpochMilli()), true, settings, 0);
+            var im = createIndexMetadata(getDefaultBackingIndexName(dataStreamName, generation, start.toEpochMilli()), true, settings, 0);
             builder.put(im, true);
             backingIndices.add(im);
             generation++;
         }
         DataStream ds = new DataStream(
-            dataStream,
+            dataStreamName,
             backingIndices.stream().map(IndexMetadata::getIndex).collect(Collectors.toList()),
-            backingIndices.size(),
-            null,
-            false,
-            false,
-            false,
-            false,
+            generation,
+            existing != null ? existing.getMetadata() : null,
+            existing != null && existing.isHidden(),
+            existing != null && existing.isReplicated(),
+            existing != null && existing.isSystem(),
+            existing != null && existing.isAllowCustomRouting(),
             IndexMode.TIME_SERIES
         );
         builder.put(ds);

+ 6 - 0
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/AbstractLocalSpecBuilder.java

@@ -178,6 +178,12 @@ public abstract class AbstractLocalSpecBuilder<T extends LocalSpecBuilder<?>> im
         return cast(this);
     }
 
+    @Override
+    public T keystore(SettingsProvider settingsProvider) {
+        this.keystoreProviders.add(settingsProvider);
+        return cast(this);
+    }
+
     public List<SettingsProvider> getKeystoreProviders() {
         return inherit(() -> parent.getKeystoreProviders(), keystoreProviders);
     }

+ 5 - 0
test/test-clusters/src/main/java/org/elasticsearch/test/cluster/local/LocalSpecBuilder.java

@@ -96,6 +96,11 @@ interface LocalSpecBuilder<T extends LocalSpecBuilder<?>> {
      */
     T keystore(String key, Supplier<String> supplier, Predicate<LocalNodeSpec> predicate);
 
+    /**
+     * Register a {@link SettingsProvider} for keystore settings.
+     */
+    T keystore(SettingsProvider settingsProvider);
+
     /**
      * Sets the security setting keystore password.
      */

+ 2 - 0
x-pack/docs/build.gradle

@@ -37,6 +37,8 @@ restResources {
 tasks.named("yamlRestTest").configure {
   if (BuildParams.isSnapshotBuild()) {
     systemProperty 'tests.rest.blacklist', '*/get-builtin-privileges/*'
+  } else {
+    systemProperty 'tests.rest.blacklist', ['*/create-cross-cluster-api-key/*', '*/update-cross-cluster-api-key/*']
   }
 }
 

+ 4 - 4
x-pack/docs/en/rest-api/security.asciidoc

@@ -79,10 +79,10 @@ ifeval::["{release-state}"!="released"]
 Use the following APIs to create and update API keys for access via the REST interface
 without requiring basic authentication:
 
-* <<security-api-create-api-key,Create API key>>
-* <<security-api-grant-api-key,Grant API key>>
-* <<security-api-update-api-key,Update API key>>
-* <<security-api-bulk-update-api-keys,Bulk update API keys>>
+* <<security-api-create-api-key,Create REST API key>>
+* <<security-api-grant-api-key,Grant REST API key>>
+* <<security-api-update-api-key,Update REST API key>>
+* <<security-api-bulk-update-api-keys,Bulk update REST API keys>>
 
 Use the following APIs to create and update cross-cluster API keys for
 API key based remote cluster access:

+ 233 - 2
x-pack/docs/en/rest-api/security/create-cross-cluster-api-key.asciidoc

@@ -3,7 +3,238 @@
 === Create Cross-Cluster API key API
 
 ++++
-<titleabbrev>Create Cross-Cluster API key</titleabbrev>
+<titleabbrev>Create Cross-Cluster API key API</titleabbrev>
 ++++
 
-TODO: Placeholder
+Creates an API key of the `cross_cluster` type for the API key based remote cluster access.
+A `cross_cluster` API key cannot be used to authenticate through the REST interface.
+On the contrary, a <<security-api-create-api-key,REST API key>> is meant to be used through the REST interface
+and cannot be used for the API key based remote cluster access.
+
+[[security-api-create-cross-cluster-api-key-request]]
+==== {api-request-title}
+
+`POST /_security/cross_cluster/api_key`
+
+[[security-api-create-cross-cluster-api-key-prereqs]]
+==== {api-prereq-title}
+
+* To use this API, you must have at least the `manage_security` cluster privilege.
+
+IMPORTANT: To authenticate this request you must use a credential that is *not* an API key. Even if you use an API key that has the required privilege, the API returns an error.
+
+[[security-api-create-cross-cluster-api-key-desc]]
+==== {api-description-title}
+
+Cross-cluster API keys are created by the {es} API key service, which is automatically enabled.
+For instructions on disabling the API key service, refer to <<api-key-service-settings>>.
+
+A successful request returns a JSON structure that contains the
+API key, its unique ID, and its name. If applicable, it also returns expiration
+information for the API key in milliseconds.
+
+NOTE: By default, API keys never expire. You can specify expiration information
+when you create the API keys.
+
+Refer to <<api-key-service-settings>> for configuration settings related to API key
+service.
+
+Cross-cluster API keys can only be updated with the
+<<security-api-update-cross-cluster-api-key,Update Cross-Cluster API key API>>.
+Attempting to update them with the <<security-api-update-api-key,Update REST API key API>>
+or the <<security-api-bulk-update-api-keys,Bulk Update REST API Keys API>> will result
+into an error. They can be retrieved and invalidated using
+<<security-api-get-api-key,Get API keys API>>, <<security-api-query-api-key,Query API keys API>>
+and <<security-api-invalidate-api-key,Invalidate API keys API>>.
+
+
+[[security-api-create-cross-cluster-api-key-request-body]]
+==== {api-request-body-title}
+
+The following parameters can be specified in the body of a POST request:
+
+`name`::
+(Required, string) Specifies the name for this API key.
+
+[[cross-cluster-api-key-access]]
+`access`::
+(required, object) The access to be granted to this API key. The access is
+composed of permissions for cross-cluster search and cross-cluster replication.
+At least one of them must be specified.
+`search`::: (optional, list) A list of indices permission entries for cross-cluster search.
+`names`:::: (required, list) A list of indices or name patterns to which the
+permissions in this entry apply.
+`field_security`:::: (optional, object) The document fields that the owners of the role have
+read access to. For more information, check <<field-and-document-access-control>>.
+`query`:::: (optional) A search query that defines the documents the owners of the role have
+read access to. A document within the specified indices must match this query to be accessible by the owners of the role. For more information, check
+<<field-and-document-access-control>>.
+`allow_restricted_indices`:::: (optional, boolean) This needs to be set to `true` (default
+is `false`) if the patterns in the `names` field should cover <<system-indices,system indices>>.
+`replication`::: (optional, list) A list of indices permission entries for cross-cluster replication.
+`names`:::: (required, list) A list of indices or name patterns to which the
+permissions in this entry apply.
+
+NOTE: No explicit <<security-privileges,privileges>> should be specified for either search
+or replication access. The creation process automatically converts the `access` specification
+to a role descriptor which has relevant privileges assigned accordingly.
+
+NOTE: Unlike <<api-key-role-descriptors,REST API keys>>, a cross-cluster API key
+does not capture permissions of the authenticated user. The API key's effective
+permission is exactly as specified with the `access` parameter.
+
+`expiration`::
+(optional, string) Expiration time for the API key. By default, API keys never
+expire.
+
+`metadata`::
+(optional, object) Arbitrary metadata that you want to associate with the API key.
+It supports nested data structure.
+Within the `metadata` object, keys beginning with `_` are reserved for
+system usage.
+
+[[security-api-create-cross-cluster-api-key-example]]
+==== {api-examples-title}
+
+The following example creates a cross-cluster API key:
+
+[source,console]
+----
+POST /_security/cross_cluster/api_key
+{
+  "name": "my-cross-cluster-api-key",
+  "expiration": "1d",   <1>
+  "access": {
+    "search": [  <2>
+      {
+        "names": ["logs*"]
+      }
+    ],
+    "replication": [  <3>
+      {
+        "names": ["archive*"]
+      }
+    ]
+  },
+  "metadata": {
+    "description": "phase one",
+    "environment": {
+       "level": 1,
+       "trusted": true,
+       "tags": ["dev", "staging"]
+    }
+  }
+}
+----
+<1> Optional expiration for the API key being generated. If expiration is not
+provided then the API key does not expire.
+<2> Cross-cluster search access to be granted to the API key.
+<3> Cross-cluster replication access to be granted to the API key.
+
+A successful call returns a JSON structure that provides API key information.
+
+[source,console-result]
+----
+{
+  "id": "VuaCfGcBCdbkQm-e5aOx",        <1>
+  "name": "my-cross-cluster-api-key",
+  "expiration": 1544068612110,         <2>
+  "api_key": "ui2lp2axTNmsyakw9tvNnw", <3>
+  "encoded": "VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=="  <4>
+}
+----
+// TESTRESPONSE[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TESTRESPONSE[s/1544068612110/$body.expiration/]
+// TESTRESPONSE[s/ui2lp2axTNmsyakw9tvNnw/$body.api_key/]
+// TESTRESPONSE[s/VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw==/$body.encoded/]
+<1> Unique `id` for this API key
+<2> Optional expiration in milliseconds for this API key
+<3> Generated API key secret
+<4> API key credentials which is the Base64-encoding of the UTF-8
+representation of the `id` and `api_key` joined by a colon (`:`)
+
+The API key information can be retrieved with the <<security-api-get-api-key,Get API key API>>.
+
+[source,console]
+--------------------------------------------------
+GET /_security/api_key?id=VuaCfGcBCdbkQm-e5aOx
+--------------------------------------------------
+// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TEST[continued]
+
+A successful call returns a JSON structure that contains the information of the API key:
+
+[source,js]
+--------------------------------------------------
+{
+  "api_keys": [
+    {
+      "id": "VuaCfGcBCdbkQm-e5aOx", <1>
+      "name": "my-cross-cluster-api-key", <2>
+      "type": "cross_cluster", <3>
+      "creation": 1548550550158,
+      "expiration": 1548551550158,
+      "invalidated": false,
+      "username": "myuser",
+      "realm": "native1",
+      "metadata": {
+        "description": "phase one",
+          "environment": {
+             "level": 1,
+             "trusted": true,
+             "tags": ["dev", "staging"]
+          }
+      },
+      "role_descriptors": {  <4>
+        "cross_cluster": {
+          "cluster": [  <5>
+              "cross_cluster_search", "cross_cluster_replication"
+          ],
+          "indices": [
+            {  <6>
+              "names": [
+                "logs*"
+              ],
+              "privileges": [
+                "read", "read_cross_cluster", "view_index_metadata"
+              ],
+              "allow_restricted_indices": false
+            },
+            {  <7>
+              "names": [
+                "archive*"
+              ],
+              "privileges": [
+                "cross_cluster_replication", "cross_cluster_replication_internal"
+              ],
+              "allow_restricted_indices": false
+            }
+          ],
+          "applications": [ ],
+          "run_as": [ ],
+          "metadata": { },
+          "transient_metadata": {
+            "enabled": true
+          }
+        }
+      }
+    }
+  ]
+}
+--------------------------------------------------
+// NOTCONSOLE
+<1> ID for the API key
+<2> Name of the API key
+<3> Type of the API key
+<4> The role descriptors generated for the cross-cluster API key. It always
+contains exactly one role descriptor named `cross_cluster`.
+A cross-cluster API key has no limited-by role descriptors.
+<5> The cluster privileges necessary for the required cross-cluster access.
+The value is `cross_cluster_search` if only cross-cluster search is required.
+It is `cross_cluster_replication` if only cross-cluster replication is required.
+Or both, if search and replication are required.
+<6> The indices privileges corresponding to the required cross-cluster search access.
+<7> The indices privileges corresponding to the required cross-cluster replication access.
+
+
+To use the generated API key, configure it as the cluster credential as part of an API key based remote cluster configuration.

+ 246 - 1
x-pack/docs/en/rest-api/security/update-cross-cluster-api-key.asciidoc

@@ -6,4 +6,249 @@
 <titleabbrev>Update Cross-Cluster API key</titleabbrev>
 ++++
 
-TODO: Placeholder
+Update an existing cross-cluster API Key.
+
+
+[[security-api-update-cross-cluster-api-key-request]]
+==== {api-request-title}
+
+`PUT /_security/cross_cluster/api_key/<id>`
+
+[[security-api-update-cross-cluster-api-key-prereqs]]
+==== {api-prereq-title}
+
+* To use this API, you must have at least the `manage_security` cluster privilege.
+Users can only update API keys that they created.
+To update another user's API key, use the <<run-as-privilege,`run_as` feature>>
+to submit a request on behalf of another user.
+
+IMPORTANT: It's not possible to use an API key as the authentication credential for this API.
+To update an API key, the owner user's credentials are required.
+
+[[security-api-update-cross-cluster-api-key-desc]]
+==== {api-description-title}
+
+Use this API to update cross-cluster API keys created by the <<security-api-create-cross-cluster-api-key,Create Cross-Cluster API key API>>.
+It's not possible to update expired API keys, or API keys that have been invalidated by
+<<security-api-invalidate-api-key,invalidate API Key>>.
+
+This API supports updates to an API key's access scope and metadata.
+The owner user's information, e.g. `username`, `realm`, is also updated automatically on every call.
+
+NOTE: This API cannot update <<security-api-create-api-key,REST API keys>>, which should be updated by
+either <<security-api-update-api-key>> or <<security-api-bulk-update-api-keys>> API.
+
+[[security-api-update-cross-cluster-api-key-path-params]]
+==== {api-path-parms-title}
+
+`id`::
+(Required, string) The ID of the API key to update.
+
+[[security-api-update-cross-cluster-api-key-request-body]]
+==== {api-request-body-title}
+
+You can specify the following parameters in the request body. The parameters are optional. But they cannot all be absent.
+
+[[security-api-update-cross-cluster-api-key-api-key-role-descriptors]]
+`access`::
+(Optional, object) The access to be granted to this API key. The access is
+composed of permissions for cross cluster search and cross cluster replication.
+At least one of them must be specified.
+When specified, the new access assignment fully replaces the previously assigned access.
+Refer to the <<cross-cluster-api-key-access,parameter of the same of the Create Cross-Cluster API key API>>
+for the field's structure.
+
+`metadata`::
+(Optional, object) Arbitrary metadata that you want to associate with the API key.
+It supports nested data structure.
+Within the `metadata` object, top-level keys beginning with `_` are reserved for system usage.
+When specified, this fully replaces metadata previously associated with the API key.
+
+[[security-api-update-cross-cluster-api-key-response-body]]
+==== {api-response-body-title}
+
+`updated`::
+(boolean) If `true`, the API key was updated.
+If `false`, the API key didn't change because no change was detected.
+
+[[security-api-update-cross-cluster-api-key-example]]
+==== {api-examples-title}
+
+If you create a cross-cluster API key as follows:
+
+[source,console]
+------------------------------------------------------------
+POST /_security/cross_cluster/api_key
+{
+  "name": "my-cross-cluster-api-key",
+  "access": {
+    "search": [
+      {
+        "names": ["logs*"]
+      }
+    ]
+  },
+  "metadata": {
+    "application": "search"
+  }
+}
+------------------------------------------------------------
+
+A successful call returns a JSON structure that provides API key information.
+For example:
+
+[source,console-result]
+--------------------------------------------------
+{
+  "id": "VuaCfGcBCdbkQm-e5aOx",
+  "name": "my-cross-cluster-api-key",
+  "api_key": "ui2lp2axTNmsyakw9tvNnw",
+  "encoded": "VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw=="
+}
+--------------------------------------------------
+// TESTRESPONSE[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TESTRESPONSE[s/ui2lp2axTNmsyakw9tvNnw/$body.api_key/]
+// TESTRESPONSE[s/VnVhQ2ZHY0JDZGJrUW0tZTVhT3g6dWkybHAyYXhUTm1zeWFrdzl0dk5udw==/$body.encoded/]
+
+Information of the API key, including its exact role descriptor can be inspected with
+the <<security-api-get-api-key,Get API key API>>
+
+[source,console]
+--------------------------------------------------
+GET /_security/api_key?id=VuaCfGcBCdbkQm-e5aOx
+--------------------------------------------------
+// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TEST[continued]
+
+A successful call returns a JSON structure that contains the information of the API key:
+
+[source,js]
+--------------------------------------------------
+{
+  "api_keys": [
+    {
+      "id": "VuaCfGcBCdbkQm-e5aOx",
+      "name": "my-cross-cluster-api-key",
+      "type": "cross_cluster",
+      "creation": 1548550550158,
+      "expiration": null,
+      "invalidated": false,
+      "username": "myuser",
+      "realm": "native1",
+      "metadata": {
+        "application": "search"
+      },
+      "role_descriptors": {
+        "cross_cluster": {  <1>
+          "cluster": [
+              "cross_cluster_search"
+          ],
+          "indices": [
+            {
+              "names": [
+                "logs*"
+              ],
+              "privileges": [
+                "read", "read_cross_cluster", "view_index_metadata"
+              ],
+              "allow_restricted_indices": false
+            }
+          ],
+          "applications": [ ],
+          "run_as": [ ],
+          "metadata": { },
+          "transient_metadata": {
+            "enabled": true
+          }
+        }
+      }
+    }
+  ]
+}
+--------------------------------------------------
+// NOTCONSOLE
+<1> Role descriptor corresponding to the specified `access` scope at creation time.
+In this example, it grants cross cluster search permission for the `logs*` index pattern.
+
+
+The following example updates the API key created above, assigning it new access scope and metadata:
+
+[source,console]
+----
+PUT /_security/cross_cluster/api_key/VuaCfGcBCdbkQm-e5aOx
+{
+  "access": {
+    "replication": [
+      {
+        "names": ["archive"]
+      }
+    ]
+  },
+  "metadata": {
+    "application": "replication"
+  }
+}
+----
+// TEST[s/VuaCfGcBCdbkQm-e5aOx/\${body.api_keys.0.id}/]
+// TEST[continued]
+
+A successful call returns a JSON structure indicating that the API key was updated:
+
+[source,console-result]
+----
+{
+  "updated": true
+}
+----
+
+The API key's permissions after the update can be inspected again with the <<security-api-get-api-key,Get API key API>>
+and it will be:
+
+[source,js]
+--------------------------------------------------
+{
+  "api_keys": [
+    {
+      "id": "VuaCfGcBCdbkQm-e5aOx",
+      "name": "my-cross-cluster-api-key",
+      "type": "cross_cluster",
+      "creation": 1548550550158,
+      "expiration": null,
+      "invalidated": false,
+      "username": "myuser",
+      "realm": "native1",
+      "metadata": {
+        "application": "replication"
+      },
+      "role_descriptors": {
+        "cross_cluster": {  <1>
+          "cluster": [
+              "cross_cluster_replication"
+          ],
+          "indices": [
+            {
+              "names": [
+                "archive*"
+              ],
+              "privileges": [
+                "cross_cluster_replication", "cross_cluster_replication_internal"
+              ],
+              "allow_restricted_indices": false
+            }
+          ],
+          "applications": [ ],
+          "run_as": [ ],
+          "metadata": { },
+          "transient_metadata": {
+            "enabled": true
+          }
+        }
+      }
+    }
+  ]
+}
+--------------------------------------------------
+// NOTCONSOLE
+<1> Role descriptor is updated to be the `access` scope specified at update time.
+In this example, it is updated to grant the cross cluster replication permission
+for the `archive*` index pattern.

+ 2 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/CrossClusterAccessSubjectInfo.java

@@ -205,10 +205,10 @@ public final class CrossClusterAccessSubjectInfo {
         assert false == authentication.isCrossClusterAccess();
         authentication.checkConsistency();
         final User user = authentication.getEffectiveSubject().getUser();
-        if (user == InternalUsers.CROSS_CLUSTER_ACCESS_USER) {
+        if (user == InternalUsers.SYSTEM_USER) {
             if (false == getRoleDescriptorsBytesList().isEmpty()) {
                 logger.warn(
-                    "Received non-empty role descriptors bytes list for internal cross cluster access user. "
+                    "Received non-empty remote access role descriptors bytes list for _system user. "
                         + "These will be ignored during authorization."
                 );
                 assert false : "role descriptors bytes list for internal cross cluster access user must be empty";

+ 0 - 74
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/CrossClusterAccessUser.java

@@ -1,74 +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.core.security.user;
-
-import org.elasticsearch.TransportVersion;
-import org.elasticsearch.xpack.core.security.authc.Authentication;
-import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo;
-import org.elasticsearch.xpack.core.security.authc.Subject;
-import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
-import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.util.Optional;
-
-public class CrossClusterAccessUser extends InternalUser {
-
-    private static final RoleDescriptor REMOTE_ACCESS_ROLE_DESCRIPTOR = new RoleDescriptor(
-        UsernamesField.CROSS_CLUSTER_ACCESS_ROLE,
-        new String[] { "cross_cluster_search", "cross_cluster_replication" },
-        // Needed for CCR background jobs (with system user)
-        new RoleDescriptor.IndicesPrivileges[] {
-            RoleDescriptor.IndicesPrivileges.builder()
-                .indices("*")
-                .privileges("cross_cluster_replication", "cross_cluster_replication_internal")
-                .allowRestrictedIndices(true)
-                .build() },
-        null,
-        null,
-        null,
-        null,
-        null,
-        null,
-        null
-    );
-
-    /**
-     * Package protected to enforce a singleton (private constructor) - use {@link InternalUsers#CROSS_CLUSTER_ACCESS_USER} instead
-     */
-    static final InternalUser INSTANCE = new CrossClusterAccessUser();
-
-    private CrossClusterAccessUser() {
-        super(
-            UsernamesField.CROSS_CLUSTER_ACCESS_NAME,
-            /**
-             *  this user is not permitted to execute actions that originate on the local cluster,
-             *  its only purpose is to execute actions from a remote cluster
-             */
-            Optional.empty(),
-            Optional.of(REMOTE_ACCESS_ROLE_DESCRIPTOR)
-        );
-    }
-
-    /**
-     * The role descriptor intersection in the returned subject info is always empty. Because the privileges of the cross cluster access
-     * internal user are static, we set them during role reference resolution instead of needlessly deserializing the role descriptor
-     * intersection (see flow starting at {@link Subject#getRoleReferenceIntersection(AnonymousUser)})
-     */
-    public static CrossClusterAccessSubjectInfo subjectInfo(TransportVersion transportVersion, String nodeName) {
-        try {
-            return new CrossClusterAccessSubjectInfo(
-                Authentication.newInternalAuthentication(InternalUsers.CROSS_CLUSTER_ACCESS_USER, transportVersion, nodeName),
-                RoleDescriptorsIntersection.EMPTY
-            );
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-}

+ 0 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java

@@ -186,7 +186,6 @@ public class InternalUsers {
     );
 
     public static final SystemUser SYSTEM_USER = SystemUser.INSTANCE;
-    public static final InternalUser CROSS_CLUSTER_ACCESS_USER = CrossClusterAccessUser.INSTANCE;
 
     private static final Map<String, InternalUser> INTERNAL_USERS;
 
@@ -197,7 +196,6 @@ public class InternalUsers {
             XPACK_SECURITY_USER,
             SECURITY_PROFILE_USER,
             ASYNC_SEARCH_USER,
-            CROSS_CLUSTER_ACCESS_USER,
             STORAGE_USER,
             DLM_USER,
             SYNONYMS_USER

+ 43 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/SystemUser.java

@@ -6,9 +6,16 @@
  */
 package org.elasticsearch.xpack.core.security.user;
 
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.xpack.core.security.authc.Authentication;
+import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo;
+import org.elasticsearch.xpack.core.security.authc.Subject;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
+import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
 import org.elasticsearch.xpack.core.security.authz.privilege.SystemPrivilege;
 
+import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.util.Optional;
 import java.util.function.Predicate;
 
@@ -22,6 +29,25 @@ public class SystemUser extends InternalUser {
     @Deprecated
     public static final String ROLE_NAME = UsernamesField.SYSTEM_ROLE;
 
+    private static final RoleDescriptor REMOTE_ACCESS_ROLE_DESCRIPTOR = new RoleDescriptor(
+        ROLE_NAME + "_cross_cluster_access",
+        new String[] { "cross_cluster_search", "cross_cluster_replication" },
+        // Needed for CCR background jobs (with system user)
+        new RoleDescriptor.IndicesPrivileges[] {
+            RoleDescriptor.IndicesPrivileges.builder()
+                .indices("*")
+                .privileges("cross_cluster_replication", "cross_cluster_replication_internal")
+                .allowRestrictedIndices(true)
+                .build() },
+        null,
+        null,
+        null,
+        null,
+        null,
+        null,
+        null
+    );
+
     /**
      * Package protected to enforce a singleton (private constructor) - use {@link InternalUsers#SYSTEM_USER} instead
      */
@@ -30,7 +56,7 @@ public class SystemUser extends InternalUser {
     private static final Predicate<String> PREDICATE = SystemPrivilege.INSTANCE.predicate();
 
     private SystemUser() {
-        super(NAME, Optional.empty(), Optional.empty());
+        super(NAME, Optional.empty(), Optional.of(REMOTE_ACCESS_ROLE_DESCRIPTOR));
     }
 
     /**
@@ -50,4 +76,20 @@ public class SystemUser extends InternalUser {
     public static boolean isAuthorized(String action) {
         return PREDICATE.test(action);
     }
+
+    /**
+     * The role descriptor intersection in the returned subject info is always empty. Because the privileges of the cross cluster access
+     * internal user are static, we set them during role reference resolution instead of needlessly deserializing the role descriptor
+     * intersection (see flow starting at {@link Subject#getRoleReferenceIntersection(AnonymousUser)})
+     */
+    public static CrossClusterAccessSubjectInfo crossClusterAccessSubjectInfo(TransportVersion transportVersion, String nodeName) {
+        try {
+            return new CrossClusterAccessSubjectInfo(
+                Authentication.newInternalAuthentication(INSTANCE, transportVersion, nodeName),
+                RoleDescriptorsIntersection.EMPTY
+            );
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
 }

+ 0 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/UsernamesField.java

@@ -18,8 +18,6 @@ public final class UsernamesField {
     public static final String XPACK_SECURITY_ROLE = "_xpack_security";
     public static final String DLM_NAME = "_dlm";
     public static final String DLM_ROLE = "_dlm";
-    public static final String CROSS_CLUSTER_ACCESS_NAME = "_cross_cluster_access";
-    public static final String CROSS_CLUSTER_ACCESS_ROLE = "_cross_cluster_access";
     public static final String SECURITY_PROFILE_NAME = "_security_profile";
     public static final String SECURITY_PROFILE_ROLE = "_security_profile";
     public static final String XPACK_NAME = "_xpack";

+ 5 - 6
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/AuthenticationTestHelper.java

@@ -30,9 +30,9 @@ import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountSetting
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
 import org.elasticsearch.xpack.core.security.user.AnonymousUser;
-import org.elasticsearch.xpack.core.security.user.CrossClusterAccessUser;
 import org.elasticsearch.xpack.core.security.user.InternalUser;
 import org.elasticsearch.xpack.core.security.user.InternalUsers;
+import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.UsernamesField;
 
@@ -240,7 +240,6 @@ public class AuthenticationTestHelper {
             UsernamesField.ASYNC_SEARCH_ROLE,
             UsernamesField.XPACK_SECURITY_ROLE,
             UsernamesField.SECURITY_PROFILE_ROLE,
-            UsernamesField.CROSS_CLUSTER_ACCESS_ROLE,
             UsernamesField.DLM_ROLE
         );
     }
@@ -257,8 +256,8 @@ public class AuthenticationTestHelper {
     }
 
     public static CrossClusterAccessSubjectInfo crossClusterAccessSubjectInfoForInternalUser() {
-        final Authentication authentication = AuthenticationTestHelper.builder().internal(InternalUsers.CROSS_CLUSTER_ACCESS_USER).build();
-        return CrossClusterAccessUser.subjectInfo(
+        final Authentication authentication = AuthenticationTestHelper.builder().internal(InternalUsers.SYSTEM_USER).build();
+        return SystemUser.crossClusterAccessSubjectInfo(
             authentication.getEffectiveSubject().getTransportVersion(),
             authentication.getEffectiveSubject().getRealm().getNodeName()
         );
@@ -273,7 +272,7 @@ public class AuthenticationTestHelper {
         return switch (type) {
             case "realm" -> AuthenticationTestHelper.builder().realm().build();
             case "apikey" -> AuthenticationTestHelper.builder().apiKey().build();
-            case "internal" -> AuthenticationTestHelper.builder().internal(InternalUsers.CROSS_CLUSTER_ACCESS_USER).build();
+            case "internal" -> AuthenticationTestHelper.builder().internal(InternalUsers.SYSTEM_USER).build();
             case "service_account" -> AuthenticationTestHelper.builder().serviceAccount().build();
             default -> throw new UnsupportedOperationException("unknown type " + type);
         };
@@ -289,7 +288,7 @@ public class AuthenticationTestHelper {
     }
 
     public static CrossClusterAccessSubjectInfo randomCrossClusterAccessSubjectInfo(final Authentication authentication) {
-        if (InternalUsers.CROSS_CLUSTER_ACCESS_USER == authentication.getEffectiveSubject().getUser()) {
+        if (InternalUsers.SYSTEM_USER == authentication.getEffectiveSubject().getUser()) {
             return crossClusterAccessSubjectInfoForInternalUser();
         }
         final int numberOfRoleDescriptors;

+ 2 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/SubjectTests.java

@@ -218,7 +218,7 @@ public class SubjectTests extends ESTestCase {
         );
 
         final Authentication authentication = crossClusterAccessSubjectInfo.getAuthentication();
-        final boolean isInternalUser = authentication.getEffectiveSubject().getUser() == InternalUsers.CROSS_CLUSTER_ACCESS_USER;
+        final boolean isInternalUser = authentication.getEffectiveSubject().getUser() == InternalUsers.SYSTEM_USER;
         final RoleReferenceIntersection roleReferenceIntersection = subject.getRoleReferenceIntersection(getAnonymousUser());
         // Number of role references depends on the authentication and its number of roles.
         // Test setup can randomly authentication with 0, 1 or 2 (in case of API key) role descriptors,
@@ -276,7 +276,7 @@ public class SubjectTests extends ESTestCase {
     private static void expectFixedReferenceAtIndex(int index, List<RoleReference> roleReferences) {
         final FixedRoleReference fixedRoleReference = (FixedRoleReference) roleReferences.get(index);
         final RoleKey expectedKey = new RoleKey(
-            Set.of(InternalUsers.CROSS_CLUSTER_ACCESS_USER.getRemoteAccessRoleDescriptor().get().getName()),
+            Set.of(InternalUsers.SYSTEM_USER.getRemoteAccessRoleDescriptor().get().getName()),
             "cross_cluster_access_internal"
         );
         assertThat(fixedRoleReference.id(), equalTo(expectedKey));

+ 0 - 4
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/user/InternalUsersTests.java

@@ -193,10 +193,6 @@ public class InternalUsersTests extends ESTestCase {
         checkIndexAccess(role, randomFrom(sampleAllowedActions), randomAlphaOfLengthBetween(3, 12), false);
     }
 
-    public void testCrossClusterAccessUser() {
-        assertThat(InternalUsers.getUser("_cross_cluster_access"), is(InternalUsers.CROSS_CLUSTER_ACCESS_USER));
-    }
-
     public void testStorageUser() {
         assertThat(InternalUsers.getUser("_storage"), is(InternalUsers.STORAGE_USER));
 

+ 3 - 3
x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityFcActionAuthorizationIT.java

@@ -42,7 +42,7 @@ import org.elasticsearch.xpack.core.security.authc.Authentication;
 import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
-import org.elasticsearch.xpack.core.security.user.CrossClusterAccessUser;
+import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.security.authc.CrossClusterAccessHeaders;
 import org.junit.ClassRule;
@@ -155,7 +155,7 @@ public class RemoteClusterSecurityFcActionAuthorizationIT extends ESRestTestCase
                 e.getMessage(),
                 containsString(
                     "action [indices:internal/admin/ccr/restore/session/put] towards remote cluster is unauthorized "
-                        + "for user [_cross_cluster_access] with assigned roles [] authenticated by API key id ["
+                        + "for user [_system] with assigned roles [] authenticated by API key id ["
                         + crossClusterApiKeyMap.get("id")
                         + "] of user [test_user] on indices [private-index], this action is granted by the index privileges "
                         + "[cross_cluster_replication_internal,all]"
@@ -449,7 +449,7 @@ public class RemoteClusterSecurityFcActionAuthorizationIT extends ESRestTestCase
                 try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
                     new CrossClusterAccessHeaders(
                         "ApiKey " + encodedApiKey,
-                        subjectInfoLookup.getOrDefault(action, CrossClusterAccessUser.subjectInfo(TransportVersion.CURRENT, nodeName))
+                        subjectInfoLookup.getOrDefault(action, SystemUser.crossClusterAccessSubjectInfo(TransportVersion.CURRENT, nodeName))
                     ).writeToContext(threadContext);
                     connection.sendRequest(requestId, action, request, options);
                 }

+ 2 - 2
x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/crossclusteraccess/CrossClusterAccessHeadersForCcsRestIT.java

@@ -53,7 +53,7 @@ import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
 import org.elasticsearch.xpack.core.security.authz.permission.Role;
-import org.elasticsearch.xpack.core.security.user.CrossClusterAccessUser;
+import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.security.SecurityOnTrialLicenseRestTestCase;
 import org.elasticsearch.xpack.security.audit.AuditUtil;
@@ -1063,7 +1063,7 @@ public class CrossClusterAccessHeadersForCcsRestIT extends SecurityOnTrialLicens
                     final var actualCrossClusterAccessSubjectInfo = CrossClusterAccessSubjectInfo.decode(
                         actual.headers().get(CrossClusterAccessSubjectInfo.CROSS_CLUSTER_ACCESS_SUBJECT_INFO_HEADER_KEY)
                     );
-                    final var expectedCrossClusterAccessSubjectInfo = CrossClusterAccessUser.subjectInfo(
+                    final var expectedCrossClusterAccessSubjectInfo = SystemUser.crossClusterAccessSubjectInfo(
                         TransportVersion.CURRENT,
                         // Since we are running on a multi-node cluster the actual node name may be different between runs
                         // so just copy the one from the actual result

+ 2 - 4
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptor.java

@@ -40,7 +40,6 @@ import org.elasticsearch.xpack.core.security.SecurityContext;
 import org.elasticsearch.xpack.core.security.authc.Authentication;
 import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo;
 import org.elasticsearch.xpack.core.security.transport.ProfileConfigurations;
-import org.elasticsearch.xpack.core.security.user.CrossClusterAccessUser;
 import org.elasticsearch.xpack.core.security.user.InternalUser;
 import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
@@ -353,8 +352,7 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
                         // Use system user for cluster state requests (CCR has many calls of cluster state with end-user context)
                         logger.trace(
                             () -> format(
-                                "Switching to internal cross cluster access user for cluster state action towards [{}]. "
-                                    + "Original user is [%s]",
+                                "Switching to the system user for cluster state action towards [{}]. Original user is [%s]",
                                 remoteClusterAlias,
                                 user
                             )
@@ -362,7 +360,7 @@ public class SecurityServerTransportInterceptor implements TransportInterceptor
                     }
                     final var crossClusterAccessHeaders = new CrossClusterAccessHeaders(
                         remoteClusterCredentials.credentials(),
-                        CrossClusterAccessUser.subjectInfo(
+                        SystemUser.crossClusterAccessSubjectInfo(
                             authentication.getEffectiveSubject().getTransportVersion(),
                             authentication.getEffectiveSubject().getRealm().getNodeName()
                         )

+ 1 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java

@@ -2676,7 +2676,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
         final String requestId = randomRequestId();
         final Authentication remoteAuthentication = randomFrom(
             AuthenticationTestHelper.builder().realm(false),
-            AuthenticationTestHelper.builder().internal(InternalUsers.CROSS_CLUSTER_ACCESS_USER),
+            AuthenticationTestHelper.builder().internal(InternalUsers.SYSTEM_USER),
             AuthenticationTestHelper.builder().serviceAccount(),
             AuthenticationTestHelper.builder().apiKey().metadata(Map.of(AuthenticationField.API_KEY_NAME_KEY, randomAlphaOfLength(42)))
         ).build(false);

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

@@ -1794,48 +1794,6 @@ public class CompositeRolesStoreTests extends ESTestCase {
         assertEquals("the internal user [_system] should never have its roles resolved", iae.getMessage());
     }
 
-    public void testGetRolesForCrossClusterAccessUserThrowsException() {
-        final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
-        doCallRealMethod().when(fileRolesStore).accept(anySet(), anyActionListener());
-        final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class);
-        doCallRealMethod().when(nativeRolesStore).accept(anySet(), anyActionListener());
-        when(fileRolesStore.roleDescriptors(anySet())).thenReturn(Collections.emptySet());
-        doAnswer((invocationOnMock) -> {
-            @SuppressWarnings("unchecked")
-            ActionListener<RoleRetrievalResult> callback = (ActionListener<RoleRetrievalResult>) invocationOnMock.getArguments()[1];
-            callback.onResponse(RoleRetrievalResult.failure(new RuntimeException("intentionally failed!")));
-            return null;
-        }).when(nativeRolesStore).getRoleDescriptors(isASet(), anyActionListener());
-        final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore());
-
-        final AtomicReference<Collection<RoleDescriptor>> effectiveRoleDescriptors = new AtomicReference<Collection<RoleDescriptor>>();
-        final CompositeRolesStore compositeRolesStore = buildCompositeRolesStore(
-            SECURITY_ENABLED_SETTINGS,
-            fileRolesStore,
-            nativeRolesStore,
-            reservedRolesStore,
-            null,
-            null,
-            null,
-            null,
-            null,
-            effectiveRoleDescriptors::set
-        );
-        verify(fileRolesStore).addListener(anyConsumer()); // adds a listener in ctor
-        IllegalArgumentException iae = expectThrows(
-            IllegalArgumentException.class,
-            () -> compositeRolesStore.getRole(
-                new Subject(
-                    InternalUsers.CROSS_CLUSTER_ACCESS_USER,
-                    new RealmRef("__attach", "__attach", randomAlphaOfLengthBetween(3, 8))
-                ),
-                null
-            )
-        );
-        assertThat(effectiveRoleDescriptors.get(), is(nullValue()));
-        assertEquals("the internal user [_cross_cluster_access] should never have its roles resolved", iae.getMessage());
-    }
-
     public void testApiKeyAuthUsesApiKeyService() throws Exception {
         final FileRolesStore fileRolesStore = mock(FileRolesStore.class);
         doCallRealMethod().when(fileRolesStore).accept(anySet(), anyActionListener());
@@ -2511,16 +2469,6 @@ public class CompositeRolesStoreTests extends ESTestCase {
         );
         assertThat(e1.getMessage(), equalTo("should never try to get the roles for internal user [" + SystemUser.NAME + "]"));
 
-        when(subject.getUser()).thenReturn(InternalUsers.CROSS_CLUSTER_ACCESS_USER);
-        final IllegalArgumentException e2 = expectThrows(
-            IllegalArgumentException.class,
-            () -> compositeRolesStore.getRoleDescriptorsList(subject, new PlainActionFuture<>())
-        );
-        assertThat(
-            e2.getMessage(),
-            equalTo("should never try to get the roles for internal user [" + InternalUsers.CROSS_CLUSTER_ACCESS_USER.principal() + "]")
-        );
-
         for (var internalUser : AuthenticationTestHelper.internalUsersWithLocalRoleDescriptor()) {
             when(subject.getUser()).thenReturn(internalUser);
             final PlainActionFuture<Collection<Set<RoleDescriptor>>> future = new PlainActionFuture<>();

+ 1 - 4
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/crossclusteraccess/CrossClusterAccessAuthenticationServiceIntegTests.java

@@ -99,10 +99,7 @@ public class CrossClusterAccessAuthenticationServiceIntegTests extends SecurityI
         }
 
         try (var ignored = threadContext.stashContext()) {
-            final var internalUser = randomValueOtherThan(
-                InternalUsers.CROSS_CLUSTER_ACCESS_USER,
-                AuthenticationTestHelper::randomInternalUser
-            );
+            final var internalUser = randomValueOtherThan(InternalUsers.SYSTEM_USER, AuthenticationTestHelper::randomInternalUser);
             new CrossClusterAccessHeaders(
                 encodedCrossClusterAccessApiKey,
                 new CrossClusterAccessSubjectInfo(

+ 2 - 2
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/SecurityServerTransportInterceptorTests.java

@@ -54,9 +54,9 @@ import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo
 import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
 import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection;
 import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
-import org.elasticsearch.xpack.core.security.user.CrossClusterAccessUser;
 import org.elasticsearch.xpack.core.security.user.InternalUser;
 import org.elasticsearch.xpack.core.security.user.InternalUsers;
+import org.elasticsearch.xpack.core.security.user.SystemUser;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.ssl.SSLService;
 import org.elasticsearch.xpack.security.Security;
@@ -813,7 +813,7 @@ public class SecurityServerTransportInterceptorTests extends ESTestCase {
             assertThat(
                 sentCrossClusterAccessSubjectInfo.get(),
                 equalTo(
-                    CrossClusterAccessUser.subjectInfo(
+                    SystemUser.crossClusterAccessSubjectInfo(
                         authentication.getEffectiveSubject().getTransportVersion(),
                         authentication.getEffectiveSubject().getRealm().getNodeName()
                     )