Browse Source

remote_cluster role documentation and expose to built in privs API (#108840)

This commit introduces the documentation for remote_clusters which is used to help
 express the monitor_enrich privilege needed to use the ENRICH keyword across clusters 
when using the API key based CCS security model.

This commit also adds "remote_clusters" to the built in privs API to for easier consumption
 in Kibana.
Jake Landis 1 year ago
parent
commit
7504fed0b3

+ 7 - 1
docs/reference/rest-api/security/get-builtin-privileges.asciidoc

@@ -45,6 +45,9 @@ version of {es}.
 <<privileges-list-indices,index privileges>> that are understood by this version
 of {es}.
 
+`remote_cluster`:: (array of string) The list of
+<<roles-remote-cluster-priv, remote_cluster>> privileges that are understood by this version
+of {es}.
 
 [[security-api-get-builtin-privileges-example]]
 ==== {api-examples-title}
@@ -56,7 +59,7 @@ The following example retrieves the names of all builtin privileges:
 GET /_security/privilege/_builtin
 --------------------------------------------------
 
-A successful call returns an object with "cluster" and "index" fields.
+A successful call returns an object with "cluster", "index", and "remote_cluster" fields.
 
 [source,console-result]
 --------------------------------------------------
@@ -145,6 +148,9 @@ A successful call returns an object with "cluster" and "index" fields.
     "read_cross_cluster",
     "view_index_metadata",
     "write"
+  ],
+  "remote_cluster" : [
+    "monitor_enrich"
   ]
 }
 --------------------------------------------------

+ 36 - 1
docs/reference/security/authorization/managing-roles.asciidoc

@@ -12,7 +12,8 @@ A role is defined by the following JSON structure:
   "global": { ... }, <3>
   "indices": [ ... ], <4>
   "applications": [ ... ], <5>
-  "remote_indices": [ ... ] <6>
+  "remote_indices": [ ... ], <6>
+  "remote_cluster": [ ... ] <7>
 }
 -----
 // NOTCONSOLE
@@ -35,6 +36,10 @@ A role is defined by the following JSON structure:
     <<remote-clusters-api-key,remote clusters configured with the API key based model>>.
     This field is optional (missing `remote_indices` privileges effectively mean
     no index level permissions for any API key based remote clusters).
+<7> A list of cluster permissions entries for
+    <<remote-clusters-api-key,remote clusters configured with the API key based model>>.
+    This field is optional (missing `remote_cluster` privileges effectively means
+    no additional cluster permissions for any API key based remote clusters).
 
 [[valid-role-name]]
 NOTE: Role names must be at least 1 and no more than 507 characters. They can
@@ -209,6 +214,36 @@ The following describes the structure of a remote indices permissions entry:
     restricted indices, you must set this field to `true` (default is `false`), and then the
     `names` field will cover the restricted indices as well.
 
+[[roles-remote-cluster-priv]]
+==== Remote cluster privileges
+
+For <<remote-clusters-api-key,remote clusters configured with the API key based model>>, remote cluster privileges
+can be used to specify additional cluster privileges for matching remote clusters.
+
+NOTE: Remote cluster privileges are only effective for remote clusters configured with the API key based model.
+They have no effect on remote clusters configured with the certificate based model.
+
+The following describes the structure of a remote cluster permissions entry:
+
+[source,js]
+-------
+{
+  "clusters": [ ... ], <1>
+  "privileges": [ ... ] <2>
+}
+-------
+// NOTCONSOLE
+<1> A list of remote cluster aliases. It supports literal strings as well as
+<<api-multi-index,wildcards>> and <<regexp-syntax,regular expressions>>.
+This field is required.
+<2> The cluster level privileges for the remote cluster. The allowed values here are a subset of the
+<<privileges-list-cluster,cluster privileges>>. This field is required.
+
+The `monitor_enrich` privilege for remote clusters was introduced in version
+8.15.0. Currently, this is the only privilege available for remote clusters and
+is required to enable users to use the `ENRICH` keyword in ES|QL queries across
+clusters.
+
 ==== Example
 
 The following snippet shows an example definition of a `clicks_admin` role:

+ 20 - 6
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/privilege/GetBuiltinPrivilegesResponse.java

@@ -23,17 +23,27 @@ public final class GetBuiltinPrivilegesResponse extends ActionResponse {
 
     private final String[] clusterPrivileges;
     private final String[] indexPrivileges;
+    private final String[] remoteClusterPrivileges;
 
+    // used by serverless
     public GetBuiltinPrivilegesResponse(Collection<String> clusterPrivileges, Collection<String> indexPrivileges) {
-        this.clusterPrivileges = Objects.requireNonNull(
-            clusterPrivileges.toArray(Strings.EMPTY_ARRAY),
-            "Cluster privileges cannot be null"
-        );
-        this.indexPrivileges = Objects.requireNonNull(indexPrivileges.toArray(Strings.EMPTY_ARRAY), "Index privileges cannot be null");
+        this(clusterPrivileges, indexPrivileges, Collections.emptySet());
+    }
+
+    public GetBuiltinPrivilegesResponse(
+        Collection<String> clusterPrivileges,
+        Collection<String> indexPrivileges,
+        Collection<String> remoteClusterPrivileges
+    ) {
+        this.clusterPrivileges = Objects.requireNonNull(clusterPrivileges, "Cluster privileges cannot be null")
+            .toArray(Strings.EMPTY_ARRAY);
+        this.indexPrivileges = Objects.requireNonNull(indexPrivileges, "Index privileges cannot be null").toArray(Strings.EMPTY_ARRAY);
+        this.remoteClusterPrivileges = Objects.requireNonNull(remoteClusterPrivileges, "Remote cluster privileges cannot be null")
+            .toArray(Strings.EMPTY_ARRAY);
     }
 
     public GetBuiltinPrivilegesResponse() {
-        this(Collections.emptySet(), Collections.emptySet());
+        this(Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
     }
 
     public String[] getClusterPrivileges() {
@@ -44,6 +54,10 @@ public final class GetBuiltinPrivilegesResponse extends ActionResponse {
         return indexPrivileges;
     }
 
+    public String[] getRemoteClusterPrivileges() {
+        return remoteClusterPrivileges;
+    }
+
     @Override
     public void writeTo(StreamOutput out) throws IOException {
         TransportAction.localOnly();

+ 3 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/privilege/TransportGetBuiltinPrivilegesAction.java

@@ -15,6 +15,7 @@ import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction;
 import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesRequest;
 import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesResponse;
+import org.elasticsearch.xpack.core.security.authz.permission.RemoteClusterPermissions;
 import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver;
 import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
 
@@ -34,7 +35,8 @@ public class TransportGetBuiltinPrivilegesAction extends TransportAction<GetBuil
     protected void doExecute(Task task, GetBuiltinPrivilegesRequest request, ActionListener<GetBuiltinPrivilegesResponse> listener) {
         final TreeSet<String> cluster = new TreeSet<>(ClusterPrivilegeResolver.names());
         final TreeSet<String> index = new TreeSet<>(IndexPrivilege.names());
-        listener.onResponse(new GetBuiltinPrivilegesResponse(cluster, index));
+        final TreeSet<String> remoteCluster = new TreeSet<>(RemoteClusterPermissions.getSupportedRemoteClusterPermissions());
+        listener.onResponse(new GetBuiltinPrivilegesResponse(cluster, index, remoteCluster));
     }
 
 }

+ 4 - 0
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/privilege/RestGetBuiltinPrivilegesAction.java

@@ -73,6 +73,10 @@ public class RestGetBuiltinPrivilegesAction extends SecurityBaseRestHandler {
                     builder.startObject();
                     builder.array("cluster", translatedResponse.getClusterPrivileges());
                     builder.array("index", translatedResponse.getIndexPrivileges());
+                    String[] remoteClusterPrivileges = translatedResponse.getRemoteClusterPrivileges();
+                    if (remoteClusterPrivileges.length > 0) { // remote clusters are not supported in stateless mode, so hide entirely
+                        builder.array("remote_cluster", remoteClusterPrivileges);
+                    }
                     builder.endObject();
                     return new RestResponse(RestStatus.OK, builder);
                 }