mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-23 13:26:02 +00:00
Support builtin privileges in get privileges API (#43901)
Adds a new "/_security/privilege/_builtin" endpoint so that builtin index and cluster privileges can be retrieved via the Rest API Backport of: #42134
This commit is contained in:
parent
deacc2038e
commit
2a8f30eb9a
@ -43,6 +43,8 @@ import org.elasticsearch.client.security.DisableUserRequest;
|
||||
import org.elasticsearch.client.security.EnableUserRequest;
|
||||
import org.elasticsearch.client.security.GetApiKeyRequest;
|
||||
import org.elasticsearch.client.security.GetApiKeyResponse;
|
||||
import org.elasticsearch.client.security.GetBuiltinPrivilegesRequest;
|
||||
import org.elasticsearch.client.security.GetBuiltinPrivilegesResponse;
|
||||
import org.elasticsearch.client.security.GetPrivilegesRequest;
|
||||
import org.elasticsearch.client.security.GetPrivilegesResponse;
|
||||
import org.elasticsearch.client.security.GetRoleMappingsRequest;
|
||||
@ -751,6 +753,34 @@ public final class SecurityClient {
|
||||
InvalidateTokenResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously get builtin (cluster & index) privilege(s).
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-builtin-privileges.html">
|
||||
* the docs</a> for more.
|
||||
*
|
||||
* @param options the request options (e.g. headers), use
|
||||
* {@link RequestOptions#DEFAULT} if nothing needs to be customized
|
||||
* @return the response from the get builtin privileges call
|
||||
* @throws IOException in case there is a problem sending the request or parsing back the response
|
||||
*/
|
||||
public GetBuiltinPrivilegesResponse getBuiltinPrivileges(final RequestOptions options) throws IOException {
|
||||
return restHighLevelClient.performRequestAndParseEntity(GetBuiltinPrivilegesRequest.INSTANCE,
|
||||
GetBuiltinPrivilegesRequest::getRequest, options, GetBuiltinPrivilegesResponse::fromXContent, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously get builtin (cluster & index) privilege(s).
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-builtin-privileges.html">
|
||||
* the docs</a> for more.
|
||||
*
|
||||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
|
||||
* @param listener the listener to be notified upon request completion
|
||||
*/
|
||||
public void getBuiltinPrivilegesAsync(final RequestOptions options, final ActionListener<GetBuiltinPrivilegesResponse> listener) {
|
||||
restHighLevelClient.performRequestAsyncAndParseEntity(GetBuiltinPrivilegesRequest.INSTANCE,
|
||||
GetBuiltinPrivilegesRequest::getRequest, options, GetBuiltinPrivilegesResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously get application privilege(s).
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-privileges.html">
|
||||
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.client.security;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.Validatable;
|
||||
|
||||
/**
|
||||
* Request object to retrieve the privilege that are builtin to the Elasticsearch cluster.
|
||||
*/
|
||||
public final class GetBuiltinPrivilegesRequest implements Validatable {
|
||||
|
||||
public static final GetBuiltinPrivilegesRequest INSTANCE = new GetBuiltinPrivilegesRequest();
|
||||
|
||||
private GetBuiltinPrivilegesRequest() {
|
||||
}
|
||||
|
||||
public Request getRequest() {
|
||||
return new Request(HttpGet.METHOD_NAME, "/_security/privilege/_builtin");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Licensed to Elasticsearch under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.client.security;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
|
||||
/**
|
||||
* Get builtin privileges response
|
||||
*/
|
||||
public final class GetBuiltinPrivilegesResponse {
|
||||
|
||||
private final Set<String> clusterPrivileges;
|
||||
private final Set<String> indexPrivileges;
|
||||
|
||||
public GetBuiltinPrivilegesResponse(Collection<String> cluster, Collection<String> index) {
|
||||
this.clusterPrivileges = Collections.unmodifiableSet(new HashSet<>(cluster));
|
||||
this.indexPrivileges = Collections.unmodifiableSet(new HashSet<>(index));
|
||||
}
|
||||
|
||||
public Set<String> getClusterPrivileges() {
|
||||
return clusterPrivileges;
|
||||
}
|
||||
|
||||
public Set<String> getIndexPrivileges() {
|
||||
return indexPrivileges;
|
||||
}
|
||||
|
||||
public static GetBuiltinPrivilegesResponse fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
GetBuiltinPrivilegesResponse that = (GetBuiltinPrivilegesResponse) o;
|
||||
return Objects.equals(this.clusterPrivileges, that.clusterPrivileges)
|
||||
&& Objects.equals(this.indexPrivileges, that.indexPrivileges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(clusterPrivileges, indexPrivileges);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final ConstructingObjectParser<GetBuiltinPrivilegesResponse, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"get_builtin_privileges", true,
|
||||
args -> new GetBuiltinPrivilegesResponse((Collection<String>) args[0], (Collection<String>) args[1]));
|
||||
|
||||
static {
|
||||
PARSER.declareStringArray(constructorArg(), new ParseField("cluster"));
|
||||
PARSER.declareStringArray(constructorArg(), new ParseField("index"));
|
||||
}
|
||||
}
|
@ -141,7 +141,7 @@ public class RestHighLevelClientTests extends ESTestCase {
|
||||
// core
|
||||
"ping", "info",
|
||||
// security
|
||||
"security.get_ssl_certificates", "security.authenticate", "security.get_user_privileges",
|
||||
"security.get_ssl_certificates", "security.authenticate", "security.get_user_privileges", "security.get_builtin_privileges",
|
||||
// license
|
||||
"license.get_trial_status", "license.get_basic_status"
|
||||
|
||||
|
@ -316,7 +316,7 @@ public class SecurityRequestConvertersTests extends ESTestCase {
|
||||
assertNull(request.getEntity());
|
||||
}
|
||||
|
||||
public void testGetAllApplicationPrivileges() throws Exception {
|
||||
public void testGetAllPrivilegesForApplication() throws Exception {
|
||||
final String application = randomAlphaOfLength(6);
|
||||
GetPrivilegesRequest getPrivilegesRequest = GetPrivilegesRequest.getApplicationPrivileges(application);
|
||||
Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest);
|
||||
@ -340,7 +340,7 @@ public class SecurityRequestConvertersTests extends ESTestCase {
|
||||
assertNull(request.getEntity());
|
||||
}
|
||||
|
||||
public void testGetAllPrivileges() throws Exception {
|
||||
public void testGetAllApplicationPrivileges() throws Exception {
|
||||
GetPrivilegesRequest getPrivilegesRequest = GetPrivilegesRequest.getAllPrivileges();
|
||||
Request request = SecurityRequestConverters.getPrivileges(getPrivilegesRequest);
|
||||
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
|
||||
|
@ -50,6 +50,7 @@ import org.elasticsearch.client.security.EnableUserRequest;
|
||||
import org.elasticsearch.client.security.ExpressionRoleMapping;
|
||||
import org.elasticsearch.client.security.GetApiKeyRequest;
|
||||
import org.elasticsearch.client.security.GetApiKeyResponse;
|
||||
import org.elasticsearch.client.security.GetBuiltinPrivilegesResponse;
|
||||
import org.elasticsearch.client.security.GetPrivilegesRequest;
|
||||
import org.elasticsearch.client.security.GetPrivilegesResponse;
|
||||
import org.elasticsearch.client.security.GetRoleMappingsRequest;
|
||||
@ -118,6 +119,7 @@ import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.emptyIterable;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.isIn;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
@ -1497,6 +1499,60 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetBuiltinPrivileges() throws Exception {
|
||||
final RestHighLevelClient client = highLevelClient();
|
||||
{
|
||||
//tag::get-builtin-privileges-execute
|
||||
GetBuiltinPrivilegesResponse response = client.security().getBuiltinPrivileges(RequestOptions.DEFAULT);
|
||||
//end::get-builtin-privileges-execute
|
||||
|
||||
assertNotNull(response);
|
||||
//tag::get-builtin-privileges-response
|
||||
final Set<String> cluster = response.getClusterPrivileges();
|
||||
final Set<String> index = response.getIndexPrivileges();
|
||||
//end::get-builtin-privileges-response
|
||||
|
||||
assertThat(cluster, hasItem("all"));
|
||||
assertThat(cluster, hasItem("manage"));
|
||||
assertThat(cluster, hasItem("monitor"));
|
||||
assertThat(cluster, hasItem("manage_security"));
|
||||
|
||||
assertThat(index, hasItem("all"));
|
||||
assertThat(index, hasItem("manage"));
|
||||
assertThat(index, hasItem("monitor"));
|
||||
assertThat(index, hasItem("read"));
|
||||
assertThat(index, hasItem("write"));
|
||||
}
|
||||
{
|
||||
// tag::get-builtin-privileges-execute-listener
|
||||
ActionListener<GetBuiltinPrivilegesResponse> listener = new ActionListener<GetBuiltinPrivilegesResponse>() {
|
||||
@Override
|
||||
public void onResponse(GetBuiltinPrivilegesResponse response) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
// end::get-builtin-privileges-execute-listener
|
||||
|
||||
// Replace the empty listener by a blocking listener in test
|
||||
final PlainActionFuture<GetBuiltinPrivilegesResponse> future = new PlainActionFuture<>();
|
||||
listener = future;
|
||||
|
||||
// tag::get-builtin-privileges-execute-async
|
||||
client.security().getBuiltinPrivilegesAsync(RequestOptions.DEFAULT, listener); // <1>
|
||||
// end::get-builtin-privileges-execute-async
|
||||
|
||||
final GetBuiltinPrivilegesResponse response = future.get(30, TimeUnit.SECONDS);
|
||||
assertNotNull(response);
|
||||
assertThat(response.getClusterPrivileges(), hasItem("manage_security"));
|
||||
assertThat(response.getIndexPrivileges(), hasItem("read"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetPrivileges() throws Exception {
|
||||
final RestHighLevelClient client = highLevelClient();
|
||||
final ApplicationPrivilege readTestappPrivilege =
|
||||
@ -1556,9 +1612,9 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
||||
|
||||
assertNotNull(response);
|
||||
assertThat(response.getPrivileges().size(), equalTo(3));
|
||||
final GetPrivilegesResponse exptectedResponse =
|
||||
final GetPrivilegesResponse expectedResponse =
|
||||
new GetPrivilegesResponse(Arrays.asList(readTestappPrivilege, writeTestappPrivilege, allTestappPrivilege));
|
||||
assertThat(response, equalTo(exptectedResponse));
|
||||
assertThat(response, equalTo(expectedResponse));
|
||||
//tag::get-privileges-response
|
||||
Set<ApplicationPrivilege> privileges = response.getPrivileges();
|
||||
//end::get-privileges-response
|
||||
|
@ -0,0 +1,27 @@
|
||||
--
|
||||
:api: get-builtin-privileges
|
||||
:request: GetBuiltinPrivilegesRequest
|
||||
:response: GetBuiltinPrivilegesResponse
|
||||
--
|
||||
|
||||
[id="{upid}-{api}"]
|
||||
=== Get Builtin Privileges API
|
||||
|
||||
include::../execution-no-req.asciidoc[]
|
||||
|
||||
[id="{upid}-{api}-response"]
|
||||
==== Get Builtin Privileges Response
|
||||
|
||||
The returned +{response}+ contains the following properties
|
||||
|
||||
`clusterPrivileges`::
|
||||
A `Set` of all _cluster_ privileges that are understood by this node.
|
||||
|
||||
`indexPrivileges`::
|
||||
A `Set` of all _index_ privileges that are understood by this node.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests-file}[{api}-response]
|
||||
--------------------------------------------------
|
||||
|
@ -418,6 +418,7 @@ The Java High Level REST Client supports the following Security APIs:
|
||||
* <<java-rest-high-security-delete-role-mapping>>
|
||||
* <<java-rest-high-security-create-token>>
|
||||
* <<{upid}-invalidate-token>>
|
||||
* <<{upid}-get-builtin-privileges>>
|
||||
* <<{upid}-get-privileges>>
|
||||
* <<{upid}-put-privileges>>
|
||||
* <<{upid}-delete-privileges>>
|
||||
@ -435,6 +436,7 @@ include::security/put-role.asciidoc[]
|
||||
include::security/get-roles.asciidoc[]
|
||||
include::security/delete-role.asciidoc[]
|
||||
include::security/delete-privileges.asciidoc[]
|
||||
include::security/get-builtin-privileges.asciidoc[]
|
||||
include::security/get-privileges.asciidoc[]
|
||||
include::security/clear-roles-cache.asciidoc[]
|
||||
include::security/clear-realm-cache.asciidoc[]
|
||||
|
@ -8,6 +8,7 @@ You can use the following APIs to perform security activities.
|
||||
* <<security-api-clear-cache>>
|
||||
* <<security-api-has-privileges>>
|
||||
* <<security-api-ssl>>
|
||||
* <<security-api-get-builtin-privileges>>
|
||||
|
||||
[float]
|
||||
[[security-api-app-privileges]]
|
||||
@ -105,6 +106,7 @@ include::security/disable-users.asciidoc[]
|
||||
include::security/enable-users.asciidoc[]
|
||||
include::security/get-api-keys.asciidoc[]
|
||||
include::security/get-app-privileges.asciidoc[]
|
||||
include::security/get-builtin-privileges.asciidoc[]
|
||||
include::security/get-role-mappings.asciidoc[]
|
||||
include::security/get-roles.asciidoc[]
|
||||
include::security/get-tokens.asciidoc[]
|
||||
|
107
x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc
Normal file
107
x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc
Normal file
@ -0,0 +1,107 @@
|
||||
[role="xpack"]
|
||||
[[security-api-get-builtin-privileges]]
|
||||
=== Get builtin privileges API
|
||||
++++
|
||||
<titleabbrev>Get builtin privileges</titleabbrev>
|
||||
++++
|
||||
|
||||
Retrieves the list of
|
||||
{stack-ov}/security-privileges.html#privileges-list-cluster[cluster privileges] and
|
||||
{stack-ov}/security-privileges.html#privileges-list-indices[index privileges] that are
|
||||
available in this version of {es}.
|
||||
|
||||
==== Request
|
||||
|
||||
`GET /_security/privilege/_builtin`
|
||||
|
||||
|
||||
==== Description
|
||||
|
||||
This API retrieves the set of cluster and index privilege names that are available in the
|
||||
version of {es} that is being queried.
|
||||
|
||||
To check whether a user has particular privileges, use the
|
||||
<<security-api-has-privileges,has privileges API>>.
|
||||
|
||||
==== Results
|
||||
|
||||
The response is an object with two fields:
|
||||
|
||||
`cluster`:: (array of string) The list of
|
||||
{stack-ov}/security-privileges.html#privileges-list-cluster[cluster privileges] that are
|
||||
understood by this version of {es}
|
||||
|
||||
`index`:: (array of string) The list of
|
||||
{stack-ov}/security-privileges.html#privileges-list-indices[index privileges] that are
|
||||
understood by this version of {es}
|
||||
|
||||
==== Authorization
|
||||
|
||||
To use this API, you must have - the `manage_security` cluster privilege
|
||||
(or a greater privilege such as `all`)
|
||||
|
||||
==== Examples
|
||||
|
||||
The following example retrieves the names of all builtin privileges:
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
GET /_security/privilege/_builtin
|
||||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
// TEST
|
||||
|
||||
A successful call returns an object with "cluster" and "index" fields.
|
||||
|
||||
[source,js]
|
||||
--------------------------------------------------
|
||||
{
|
||||
"cluster" : [
|
||||
"all",
|
||||
"create_snapshot",
|
||||
"manage",
|
||||
"manage_api_key",
|
||||
"manage_ccr",
|
||||
"manage_data_frame_transforms",
|
||||
"manage_ilm",
|
||||
"manage_index_templates",
|
||||
"manage_ingest_pipelines",
|
||||
"manage_ml",
|
||||
"manage_oidc",
|
||||
"manage_pipeline",
|
||||
"manage_rollup",
|
||||
"manage_saml",
|
||||
"manage_security",
|
||||
"manage_token",
|
||||
"manage_watcher",
|
||||
"monitor",
|
||||
"monitor_data_frame_transforms",
|
||||
"monitor_ml",
|
||||
"monitor_rollup",
|
||||
"monitor_watcher",
|
||||
"none",
|
||||
"read_ccr",
|
||||
"read_ilm",
|
||||
"transport_client"
|
||||
],
|
||||
"index" : [
|
||||
"all",
|
||||
"create",
|
||||
"create_index",
|
||||
"delete",
|
||||
"delete_index",
|
||||
"index",
|
||||
"manage",
|
||||
"manage_follow_index",
|
||||
"manage_ilm",
|
||||
"manage_leader_index",
|
||||
"monitor",
|
||||
"none",
|
||||
"read",
|
||||
"read_cross_cluster",
|
||||
"view_index_metadata",
|
||||
"write"
|
||||
]
|
||||
}
|
||||
--------------------------------------------------
|
||||
// TESTRESPONSE
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.security.action.privilege;
|
||||
|
||||
import org.elasticsearch.action.StreamableResponseActionType;
|
||||
|
||||
/**
|
||||
* ActionType for retrieving builtin privileges from security
|
||||
*/
|
||||
public final class GetBuiltinPrivilegesAction extends StreamableResponseActionType<GetBuiltinPrivilegesResponse> {
|
||||
|
||||
public static final GetBuiltinPrivilegesAction INSTANCE = new GetBuiltinPrivilegesAction();
|
||||
public static final String NAME = "cluster:admin/xpack/security/privilege/builtin/get";
|
||||
|
||||
private GetBuiltinPrivilegesAction() {
|
||||
super(NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GetBuiltinPrivilegesResponse newResponse() {
|
||||
return new GetBuiltinPrivilegesResponse();
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.security.action.privilege;
|
||||
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
|
||||
/**
|
||||
* Request to retrieve built-in (cluster/index) privileges.
|
||||
*/
|
||||
public final class GetBuiltinPrivilegesRequest extends ActionRequest {
|
||||
|
||||
public GetBuiltinPrivilegesRequest() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionRequestValidationException validate() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.core.security.action.privilege;
|
||||
|
||||
import org.elasticsearch.action.ActionResponse;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Response containing one or more application privileges retrieved from the security index
|
||||
*/
|
||||
public final class GetBuiltinPrivilegesResponse extends ActionResponse {
|
||||
|
||||
private String[] clusterPrivileges;
|
||||
private String[] indexPrivileges;
|
||||
|
||||
public GetBuiltinPrivilegesResponse(String[] clusterPrivileges, String[] indexPrivileges) {
|
||||
this.clusterPrivileges = Objects.requireNonNull(clusterPrivileges, "Cluster privileges cannot be null");
|
||||
this.indexPrivileges = Objects.requireNonNull(indexPrivileges, "Index privileges cannot be null");
|
||||
}
|
||||
|
||||
public GetBuiltinPrivilegesResponse(Collection<String> clusterPrivileges,
|
||||
Collection<String> indexPrivileges) {
|
||||
this(clusterPrivileges.toArray(Strings.EMPTY_ARRAY), indexPrivileges.toArray(Strings.EMPTY_ARRAY));
|
||||
}
|
||||
|
||||
public GetBuiltinPrivilegesResponse() {
|
||||
this(Collections.emptySet(), Collections.emptySet());
|
||||
}
|
||||
|
||||
public String[] getClusterPrivileges() {
|
||||
return clusterPrivileges;
|
||||
}
|
||||
|
||||
public String[] getIndexPrivileges() {
|
||||
return indexPrivileges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFrom(StreamInput in) throws IOException {
|
||||
super.readFrom(in);
|
||||
this.clusterPrivileges = in.readStringArray();
|
||||
this.indexPrivileges = in.readStringArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
super.writeTo(out);
|
||||
out.writeStringArray(clusterPrivileges);
|
||||
out.writeStringArray(indexPrivileges);
|
||||
}
|
||||
}
|
@ -74,5 +74,4 @@ public final class GetPrivilegesRequest extends ActionRequest implements Applica
|
||||
out.writeOptionalString(application);
|
||||
out.writeStringArray(privileges);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivileg
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Response containing one or more application privileges retrieved from the security index
|
||||
@ -21,11 +22,15 @@ public final class GetPrivilegesResponse extends ActionResponse {
|
||||
private ApplicationPrivilegeDescriptor[] privileges;
|
||||
|
||||
public GetPrivilegesResponse(ApplicationPrivilegeDescriptor... privileges) {
|
||||
this.privileges = privileges;
|
||||
this.privileges = Objects.requireNonNull(privileges, "Application privileges cannot be null");
|
||||
}
|
||||
|
||||
public GetPrivilegesResponse(Collection<ApplicationPrivilegeDescriptor> privileges) {
|
||||
this(privileges.toArray(new ApplicationPrivilegeDescriptor[privileges.size()]));
|
||||
this(privileges.toArray(new ApplicationPrivilegeDescriptor[0]));
|
||||
}
|
||||
|
||||
public GetPrivilegesResponse() {
|
||||
this(new ApplicationPrivilegeDescriptor[0]);
|
||||
}
|
||||
|
||||
public ApplicationPrivilegeDescriptor[] privileges() {
|
||||
@ -44,4 +49,7 @@ public final class GetPrivilegesResponse extends ActionResponse {
|
||||
out.writeArray(privileges);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return privileges.length == 0;
|
||||
}
|
||||
}
|
||||
|
@ -177,4 +177,8 @@ public final class ClusterPrivilege extends Privilege {
|
||||
}
|
||||
return new ClusterPrivilege(name, Automatons.unionAndMinimize(automata));
|
||||
}
|
||||
|
||||
public static Set<String> names() {
|
||||
return Collections.unmodifiableSet(VALUES.keySet());
|
||||
}
|
||||
}
|
||||
|
@ -161,4 +161,9 @@ public final class IndexPrivilege extends Privilege {
|
||||
static Map<String, IndexPrivilege> values() {
|
||||
return VALUES;
|
||||
}
|
||||
|
||||
public static Set<String> names() {
|
||||
return Collections.unmodifiableSet(VALUES.keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesAction;
|
||||
import org.elasticsearch.common.collect.MapBuilder;
|
||||
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
|
||||
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.ConditionalClusterPrivilege;
|
||||
@ -109,7 +110,8 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
|
||||
null))
|
||||
.put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME,
|
||||
new String[] {
|
||||
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc"
|
||||
"monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc",
|
||||
GetBuiltinPrivilegesAction.NAME
|
||||
},
|
||||
new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.security.action.privilege;
|
||||
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class GetBuiltinPrivilegesResponseTests extends ESTestCase {
|
||||
|
||||
public void testSerialization() throws IOException {
|
||||
final String[] cluster = generateRandomStringArray(8, randomIntBetween(3, 8), false, true);
|
||||
final String[] index = generateRandomStringArray(8, randomIntBetween(3, 8), false, true);
|
||||
final GetBuiltinPrivilegesResponse original = new GetBuiltinPrivilegesResponse(cluster, index);
|
||||
|
||||
final BytesStreamOutput out = new BytesStreamOutput();
|
||||
original.writeTo(out);
|
||||
|
||||
final GetBuiltinPrivilegesResponse copy = new GetBuiltinPrivilegesResponse();
|
||||
copy.readFrom(out.bytes().streamInput());
|
||||
|
||||
assertThat(copy.getClusterPrivileges(), Matchers.equalTo(cluster));
|
||||
assertThat(copy.getIndexPrivileges(), Matchers.equalTo(index));
|
||||
}
|
||||
|
||||
}
|
@ -45,7 +45,8 @@ public class GetPrivilegesRequestTests extends ESTestCase {
|
||||
assertThat(request("my_app", "read", "write").validate(), nullValue());
|
||||
final ActionRequestValidationException exception = request("my_app", ((String[]) null)).validate();
|
||||
assertThat(exception, notNullValue());
|
||||
assertThat(exception.validationErrors(), containsInAnyOrder("privileges cannot be null"));
|
||||
assertThat(exception.validationErrors(),
|
||||
containsInAnyOrder("privileges cannot be null"));
|
||||
}
|
||||
|
||||
private GetPrivilegesRequest request(String application, String... privileges) {
|
||||
|
@ -19,15 +19,7 @@ import java.util.Locale;
|
||||
public class GetPrivilegesResponseTests extends ESTestCase {
|
||||
|
||||
public void testSerialization() throws IOException {
|
||||
ApplicationPrivilegeDescriptor[] privileges = randomArray(6, ApplicationPrivilegeDescriptor[]::new, () ->
|
||||
new ApplicationPrivilegeDescriptor(
|
||||
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
|
||||
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
|
||||
Sets.newHashSet(randomArray(3, String[]::new, () -> randomAlphaOfLength(3).toLowerCase(Locale.ROOT) + "/*")),
|
||||
Collections.emptyMap()
|
||||
)
|
||||
);
|
||||
final GetPrivilegesResponse original = new GetPrivilegesResponse(privileges);
|
||||
final GetPrivilegesResponse original = randomResponse();
|
||||
|
||||
final BytesStreamOutput out = new BytesStreamOutput();
|
||||
original.writeTo(out);
|
||||
@ -38,4 +30,16 @@ public class GetPrivilegesResponseTests extends ESTestCase {
|
||||
assertThat(copy.privileges(), Matchers.equalTo(original.privileges()));
|
||||
}
|
||||
|
||||
private static GetPrivilegesResponse randomResponse() {
|
||||
ApplicationPrivilegeDescriptor[] application = randomArray(6, ApplicationPrivilegeDescriptor[]::new, () ->
|
||||
new ApplicationPrivilegeDescriptor(
|
||||
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
|
||||
randomAlphaOfLengthBetween(3, 8).toLowerCase(Locale.ROOT),
|
||||
Sets.newHashSet(randomArray(3, String[]::new, () -> randomAlphaOfLength(3).toLowerCase(Locale.ROOT) + "/*")),
|
||||
Collections.emptyMap()
|
||||
)
|
||||
);
|
||||
return new GetPrivilegesResponse(application);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -113,6 +113,7 @@ import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
||||
import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
|
||||
@ -317,6 +318,8 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
||||
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges), is(true));
|
||||
assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges), is(false));
|
||||
|
||||
assertThat(kibanaRole.cluster().check(GetBuiltinPrivilegesAction.NAME, request), is(true));
|
||||
|
||||
// Everything else
|
||||
assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false));
|
||||
|
||||
|
@ -83,6 +83,7 @@ import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthentica
|
||||
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutAction;
|
||||
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction;
|
||||
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
|
||||
@ -142,6 +143,7 @@ import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectAuthen
|
||||
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectLogoutAction;
|
||||
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.security.action.privilege.TransportDeletePrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.action.privilege.TransportGetBuiltinPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.action.privilege.TransportGetPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.action.privilege.TransportPutPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.action.realm.TransportClearRealmCacheAction;
|
||||
@ -204,6 +206,7 @@ import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectAuthen
|
||||
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectLogoutAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectPrepareAuthenticationAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestDeletePrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetBuiltinPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegesAction;
|
||||
import org.elasticsearch.xpack.security.rest.action.realm.RestClearRealmCacheAction;
|
||||
@ -769,6 +772,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
||||
TransportOpenIdConnectPrepareAuthenticationAction.class),
|
||||
new ActionHandler<>(OpenIdConnectAuthenticateAction.INSTANCE, TransportOpenIdConnectAuthenticateAction.class),
|
||||
new ActionHandler<>(OpenIdConnectLogoutAction.INSTANCE, TransportOpenIdConnectLogoutAction.class),
|
||||
new ActionHandler<>(GetBuiltinPrivilegesAction.INSTANCE, TransportGetBuiltinPrivilegesAction.class),
|
||||
new ActionHandler<>(GetPrivilegesAction.INSTANCE, TransportGetPrivilegesAction.class),
|
||||
new ActionHandler<>(PutPrivilegesAction.INSTANCE, TransportPutPrivilegesAction.class),
|
||||
new ActionHandler<>(DeletePrivilegesAction.INSTANCE, TransportDeletePrivilegesAction.class),
|
||||
@ -824,6 +828,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
||||
new RestOpenIdConnectPrepareAuthenticationAction(settings, restController, getLicenseState()),
|
||||
new RestOpenIdConnectAuthenticateAction(settings, restController, getLicenseState()),
|
||||
new RestOpenIdConnectLogoutAction(settings, restController, getLicenseState()),
|
||||
new RestGetBuiltinPrivilegesAction(settings, restController, getLicenseState()),
|
||||
new RestGetPrivilegesAction(settings, restController, getLicenseState()),
|
||||
new RestPutPrivilegesAction(settings, restController, getLicenseState()),
|
||||
new RestDeletePrivilegesAction(settings, restController, getLicenseState()),
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.action.privilege;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.ActionFilters;
|
||||
import org.elasticsearch.action.support.HandledTransportAction;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.tasks.Task;
|
||||
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.privilege.ClusterPrivilege;
|
||||
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
|
||||
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Transport action to retrieve one or more application privileges from the security index
|
||||
*/
|
||||
public class TransportGetBuiltinPrivilegesAction extends HandledTransportAction<GetBuiltinPrivilegesRequest, GetBuiltinPrivilegesResponse> {
|
||||
|
||||
@Inject
|
||||
public TransportGetBuiltinPrivilegesAction(ActionFilters actionFilters, TransportService transportService) {
|
||||
super(GetBuiltinPrivilegesAction.NAME, transportService, actionFilters, GetBuiltinPrivilegesRequest::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(Task task, GetBuiltinPrivilegesRequest request, ActionListener<GetBuiltinPrivilegesResponse> listener) {
|
||||
final TreeSet<String> cluster = new TreeSet<>(ClusterPrivilege.names());
|
||||
final TreeSet<String> index = new TreeSet<>(IndexPrivilege.names());
|
||||
listener.onResponse(new GetBuiltinPrivilegesResponse(cluster, index));
|
||||
}
|
||||
|
||||
}
|
@ -46,10 +46,12 @@ public class TransportGetPrivilegesAction extends HandledTransportAction<GetPriv
|
||||
} else {
|
||||
names = new HashSet<>(Arrays.asList(request.privileges()));
|
||||
}
|
||||
|
||||
final Collection<String> applications = isNullOrEmpty(request.application()) ? null : Collections.singleton(request.application());
|
||||
this.privilegeStore.getPrivileges(applications, names, ActionListener.wrap(
|
||||
privileges -> listener.onResponse(new GetPrivilegesResponse(privileges)),
|
||||
listener::onFailure
|
||||
privileges -> listener.onResponse(new GetPrivilegesResponse(privileges)),
|
||||
listener::onFailure
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.rest.action.privilege;
|
||||
|
||||
import org.elasticsearch.client.node.NodeClient;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.rest.BytesRestResponse;
|
||||
import org.elasticsearch.rest.RestController;
|
||||
import org.elasticsearch.rest.RestRequest;
|
||||
import org.elasticsearch.rest.RestResponse;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||
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.security.rest.action.SecurityBaseRestHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||
|
||||
/**
|
||||
* Rest action to retrieve an application privilege from the security index
|
||||
*/
|
||||
public class RestGetBuiltinPrivilegesAction extends SecurityBaseRestHandler {
|
||||
|
||||
public RestGetBuiltinPrivilegesAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
|
||||
super(settings, licenseState);
|
||||
controller.registerHandler(
|
||||
GET, "/_security/privilege/_builtin", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "security_get_builtin_privileges_action";
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
|
||||
return channel -> client.execute(GetBuiltinPrivilegesAction.INSTANCE, new GetBuiltinPrivilegesRequest(),
|
||||
new RestBuilderListener<GetBuiltinPrivilegesResponse>(channel) {
|
||||
@Override
|
||||
public RestResponse buildResponse(GetBuiltinPrivilegesResponse response, XContentBuilder builder) throws Exception {
|
||||
builder.startObject();
|
||||
builder.array("cluster", response.getClusterPrivileges());
|
||||
builder.array("index", response.getIndexPrivileges());
|
||||
builder.endObject();
|
||||
return new BytesRestResponse(RestStatus.OK, builder);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"security.get_builtin_privileges": {
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-builtin-privileges.html",
|
||||
"stability": "stable",
|
||||
"methods": [ "GET" ],
|
||||
"url": {
|
||||
"paths": [
|
||||
"/_security/privilege/_builtin"
|
||||
],
|
||||
"parts": {},
|
||||
"params": {}
|
||||
},
|
||||
"body": null
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"security.get_privileges": {
|
||||
"documentation": "TODO",
|
||||
"documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-privileges.html",
|
||||
"stability": "stable",
|
||||
"methods": [ "GET" ],
|
||||
"url": {
|
||||
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
setup:
|
||||
- skip:
|
||||
features: headers
|
||||
|
||||
- do:
|
||||
cluster.health:
|
||||
wait_for_status: yellow
|
||||
|
||||
---
|
||||
"Test get builtin privileges":
|
||||
- do:
|
||||
security.get_builtin_privileges: {}
|
||||
|
||||
# This is fragile - it needs to be updated every time we add a new cluster/index privilege
|
||||
# I would much prefer we could just check that specific entries are in the array, but we don't have
|
||||
# an assertion for that
|
||||
- length: { "cluster" : 26 }
|
||||
- length: { "index" : 16 }
|
Loading…
x
Reference in New Issue
Block a user