Sfoglia il codice sorgente

REST spec and YAML tests for cross-cluster API key APIs (#96339)

This PR adds REST spec files and YAML tests for the new create and
update cross-cluster API key APIs.

Relates: #95714, #96085
Yang Wang 2 anni fa
parent
commit
e5e2a0400f

+ 34 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/security.create_cross_cluster_api_key.json

@@ -0,0 +1,34 @@
+{
+  "security.create_cross_cluster_api_key": {
+    "documentation": {
+      "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-cross-cluster-api-key.html",
+      "description": "Creates a cross-cluster API key for API key based remote cluster access."
+    },
+    "stability": "experimental",
+    "visibility": "feature_flag",
+    "feature_flag": "es.untrusted_remote_cluster_feature_flag_registered",
+    "headers": {
+      "accept": [
+        "application/json"
+      ],
+      "content_type": [
+        "application/json"
+      ]
+    },
+    "url": {
+      "paths": [
+        {
+          "path": "/_security/cross_cluster/api_key",
+          "methods": [
+            "POST"
+          ]
+        }
+      ]
+    },
+    "params": {},
+    "body": {
+      "description": "The request to create a cross-cluster API key",
+      "required": true
+    }
+  }
+}

+ 39 - 0
rest-api-spec/src/main/resources/rest-api-spec/api/security.update_cross_cluster_api_key.json

@@ -0,0 +1,39 @@
+{
+  "security.update_cross_cluster_api_key": {
+    "documentation": {
+      "url": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-update-cross-cluster-api-key.html",
+      "description": "Updates attributes of an existing cross-cluster API key."
+    },
+    "stability": "experimental",
+    "visibility": "feature_flag",
+    "feature_flag": "es.untrusted_remote_cluster_feature_flag_registered",
+    "headers": {
+      "accept": [
+        "application/json"
+      ],
+      "content_type": [
+        "application/json"
+      ]
+    },
+    "url": {
+      "paths": [
+        {
+          "path": "/_security/cross_cluster/api_key/{id}",
+          "methods": [
+            "PUT"
+          ],
+          "parts": {
+            "id": {
+              "type": "string",
+              "description": "The ID of the cross-cluster API key to update"
+            }
+          }
+        }
+      ]
+    },
+    "body": {
+      "description": "The request to update attributes of a cross-cluster API key.",
+      "required": true
+    }
+  }
+}

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

@@ -61,6 +61,7 @@ without requiring basic authentication:
 [[security-api-keys]]
 === API Keys
 
+ifeval::["{release-state}"=="released"]
 Use the following APIs to create, retrieve and invalidate API keys for access
 without requiring basic authentication:
 
@@ -72,6 +73,30 @@ without requiring basic authentication:
 * <<security-api-query-api-key,Query API key>>
 * <<security-api-update-api-key,Update API key>>
 * <<security-api-bulk-update-api-keys,Bulk update API keys>>
+endif::[]
+
+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>>
+
+Use the following APIs to create and update cross-cluster API keys for
+API key based remote cluster access:
+
+* <<security-api-create-cross-cluster-api-key,Create Cross-Cluster API key>>
+* <<security-api-update-cross-cluster-api-key,Update Cross-Cluster API key>>
+
+Use the following APIs to retrieve and invalidate API keys of all types:
+
+* <<security-api-get-api-key,Get API key>>
+* <<security-api-invalidate-api-key,Invalidate API key>>
+* <<security-api-query-api-key,Query API key>>
+* <<security-api-clear-api-key-cache,Clear API key cache>>
+endif::[]
 
 [discrete]
 [[security-user-apis]]
@@ -206,3 +231,7 @@ include::security/get-user-profile.asciidoc[]
 include::security/suggest-user-profile.asciidoc[]
 include::security/update-user-profile-data.asciidoc[]
 include::security/has-privileges-user-profile.asciidoc[]
+ifeval::["{release-state}"!="released"]
+include::security/create-cross-cluster-api-key.asciidoc[]
+include::security/update-cross-cluster-api-key.asciidoc[]
+endif::[]

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

@@ -0,0 +1,9 @@
+[role="xpack"]
+[[security-api-create-cross-cluster-api-key]]
+=== Create Cross-Cluster API key API
+
+++++
+<titleabbrev>Create Cross-Cluster API key</titleabbrev>
+++++
+
+TODO: Placeholder

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

@@ -0,0 +1,9 @@
+[role="xpack"]
+[[security-api-update-cross-cluster-api-key]]
+=== Update Cross-Cluster API key API
+
+++++
+<titleabbrev>Update Cross-Cluster API key</titleabbrev>
+++++
+
+TODO: Placeholder

+ 1 - 0
x-pack/plugin/build.gradle

@@ -70,6 +70,7 @@ if (BuildParams.isSnapshotBuild() == false) {
   // cross_cluster_search privilege is only available when untrusted_remote_cluster_feature_flag_registered is enabled
   // which requires snapshot build
   restTestBlacklist.add('privileges/11_builtin/Test get builtin privileges')
+  restTestBlacklist.add('api_key/50_cross_cluster/*')
 }
 
 tasks.named("yamlRestTest").configure {

+ 343 - 0
x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/api_key/50_cross_cluster.yml

@@ -0,0 +1,343 @@
+---
+setup:
+  - skip:
+      features: headers
+
+  - do:
+      cluster.health:
+        wait_for_status: yellow
+
+  - do:
+      security.put_role:
+        name: "admin_role"
+        body: >
+          {
+            "cluster": ["manage_security"]
+          }
+
+  - do:
+      security.put_user:
+        username: "admin_user"
+        body: >
+          {
+            "password" : "x-pack-test-password",
+            "roles" : [ "admin_role" ],
+            "full_name" : "Admin user"
+          }
+
+---
+teardown:
+  - do:
+      security.delete_role:
+        name: "admin_role"
+        ignore: 404
+
+  - do:
+      security.delete_user:
+        username: "admin_user"
+        ignore: 404
+
+---
+"Test create a cross-cluster API key":
+  - skip:
+      features: transform_and_set
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.create_cross_cluster_api_key:
+        body: >
+          {
+            "name": "my-cc-api-key",
+            "expiration": "1d",
+            "access": {
+              "search": [
+                {
+                  "names": ["logs*"],
+                  "query": {
+                    "term": { "category": "shared" }
+                  },
+                  "field_security": {
+                    "grant": ["*"],
+                    "except": ["private"]
+                  }
+                }
+              ],
+              "replication": [
+                {
+                  "names": ["archive"],
+                  "allow_restricted_indices": false
+                }
+              ]
+            },
+            "metadata": {
+              "answer": 42,
+              "tag": "dev"
+            }
+          }
+  - match: { name: "my-cc-api-key" }
+  - is_true: id
+  - is_true: api_key
+  - is_true: expiration
+  - set: { id: api_key_id }
+  - transform_and_set: { login_creds: "#base64EncodeCredentials(id,api_key)" }
+  - match: { encoded: $login_creds }
+
+  # Authenticate with it via the REST interface should fail
+  - do:
+      catch: unauthorized
+      headers:
+        Authorization: ApiKey ${login_creds}
+      security.authenticate: { }
+
+  - match: { "error.type": "security_exception" }
+  - match:
+      "error.reason": "authentication expected API key type of [rest], but API key [${api_key_id}] has type [cross_cluster]"
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.get_api_key:
+        id: "$api_key_id"
+        with_limited_by: true
+
+  - length: { "api_keys": 1 }
+  - match: { "api_keys.0.id": "$api_key_id" }
+  - match: { "api_keys.0.name": "my-cc-api-key" }
+  - match: { "api_keys.0.type": "cross_cluster" }
+  - is_false: api_keys.0.invalidated
+  - match: { "api_keys.0.metadata": { "answer": 42, "tag": "dev" } }
+  - match: { "api_keys.0.role_descriptors": {
+    "cross_cluster": {
+      "cluster": [
+        "cross_cluster_search",
+        "cross_cluster_replication"
+      ],
+      "indices": [
+        {
+          "names": [
+            "logs*"
+          ],
+          "privileges": [
+            "read",
+            "read_cross_cluster",
+            "view_index_metadata"
+          ],
+          "field_security": {
+            "grant": [
+              "*"
+            ],
+            "except": [
+              "private"
+            ]
+          },
+          "query": "{\"term\":{\"category\":\"shared\"}}",
+          "allow_restricted_indices": false
+        },
+        {
+          "names": [
+            "archive"
+          ],
+          "privileges": [
+            "cross_cluster_replication",
+            "cross_cluster_replication_internal"
+          ],
+          "allow_restricted_indices": false
+        }
+      ],
+      "applications": [ ],
+      "run_as": [ ],
+      "metadata": { },
+      "transient_metadata": {
+        "enabled": true
+      }
+    }
+  }
+  }
+  - is_false: api_keys.0.limited_by
+
+---
+"Test update a cross-cluster API Key":
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.create_cross_cluster_api_key:
+        body: >
+          {
+            "name": "my-cc-api-key",
+            "access": {
+              "search": [
+                {
+                  "names": ["logs*"]
+                }
+              ]
+            },
+            "metadata": { "tag": "dev" }
+          }
+  - is_true: id
+  - set: { id: api_key_id }
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.update_cross_cluster_api_key:
+        id: "$api_key_id"
+        body: >
+          {
+            "access": {
+              "replication": [
+                {
+                  "names": ["archive"]
+                }
+              ]
+            },
+            "metadata": { "tag": "prod" }
+          }
+  - match: { updated: true }
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.get_api_key:
+        id: "$api_key_id"
+        with_limited_by: true
+
+  - length: { "api_keys": 1 }
+  - match: { "api_keys.0.id": "$api_key_id" }
+  - match: { "api_keys.0.name": "my-cc-api-key" }
+  - match: { "api_keys.0.type": "cross_cluster" }
+  - is_false: api_keys.0.invalidated
+  - match: { "api_keys.0.metadata": { "tag": "prod" } }
+  - match: { "api_keys.0.role_descriptors": {
+    "cross_cluster": {
+      "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
+      }
+    }
+  }
+  }
+  - is_false: api_keys.0.limited_by
+
+  # No-op update
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.update_cross_cluster_api_key:
+        id: "$api_key_id"
+        body: >
+          {
+            "access": {
+              "replication": [
+                {
+                  "names": ["archive"]
+                }
+              ]
+            },
+            "metadata": { "tag": "prod" }
+          }
+  - match: { updated: false }
+
+---
+"Test invalidate a cross-cluster API Key":
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.create_cross_cluster_api_key:
+        body: >
+          {
+            "name": "my-cc-api-key",
+            "access": {
+              "search": [
+                {
+                  "names": ["*"],
+                  "allow_restricted_indices": true
+                }
+              ]
+            }
+          }
+  - is_true: id
+  - set: { id: api_key_id }
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.get_api_key:
+        id: "$api_key_id"
+
+  - length: { "api_keys": 1 }
+  - match: { "api_keys.0.id": "$api_key_id" }
+  - match: { "api_keys.0.name": "my-cc-api-key" }
+  - match: { "api_keys.0.type": "cross_cluster" }
+  - is_false: api_keys.0.invalidated
+  - match: { "api_keys.0.metadata": { } }
+  - match: { "api_keys.0.role_descriptors": {
+    "cross_cluster": {
+      "cluster": [
+        "cross_cluster_search"
+      ],
+      "indices": [
+        {
+          "names": [
+            "*"
+          ],
+          "privileges": [
+            "read",
+            "read_cross_cluster",
+            "view_index_metadata"
+          ],
+          "allow_restricted_indices": true
+        }
+      ],
+      "applications": [ ],
+      "run_as": [ ],
+      "metadata": { },
+      "transient_metadata": {
+        "enabled": true
+      }
+    }
+  }
+  }
+  - is_false: api_keys.0.limited_by
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.invalidate_api_key:
+        body: >
+          {
+            "ids": [ "${api_key_id}" ]
+          }
+  - length: { "invalidated_api_keys": 1 }
+  - match: { "invalidated_api_keys.0": "${api_key_id}" }
+  - length: { "previously_invalidated_api_keys": 0 }
+  - match: { "error_count": 0 }
+
+  - do:
+      headers:
+        Authorization: "Basic YWRtaW5fdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # admin_user
+      security.get_api_key:
+        id: "$api_key_id"
+
+  - length: { "api_keys": 1 }
+  - match: { "api_keys.0.id": "$api_key_id" }
+  - is_true: api_keys.0.invalidated