Skip to content

Commit f67185f

Browse files
authored
Add a cluster privilege to cancel tasks and delete async searches (elastic#68679)
This change adds a new cluster privilege cancel_task that allows to: Cancel running tasks (_tasks/_cancel). Cancel and delete async searches. Today the 'manage' cluster privilege is required to cancel tasks and to delete async searches when security features are enabled. This new focused privilege allows to handle tasks and searches only. The change also adds the privilege to the internal 'kibana_system' and '_async_search' roles. They both need to be able to cancel tasks and delete async searches. Relates elastic#67965
1 parent b6598c6 commit f67185f

File tree

12 files changed

+51
-23
lines changed

12 files changed

+51
-23
lines changed

docs/reference/search/async-search.asciidoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,8 @@ Otherwise, the saved search results are deleted.
315315
DELETE /_async_search/FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=
316316
--------------------------------------------------
317317
// TEST[continued s/FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=/\${body.id}/]
318+
319+
If the {es} {security-features} are enabled, the deletion of a specific async
320+
search is restricted to:
321+
* The authenticated user that submitted the original search request.
322+
* Users that have the `cancel_task` cluster privilege.

x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ A successful call returns an object with "cluster" and "index" fields.
6262
{
6363
"cluster" : [
6464
"all",
65+
"cancel_task",
6566
"create_snapshot",
6667
"delegate_pki",
6768
"grant_api_key",

x-pack/docs/en/security/authorization/privileges.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ This section lists the privileges that you can assign to a role.
1212
All cluster administration operations, like snapshotting, node shutdown/restart,
1313
settings update, rerouting, or managing users and roles.
1414

15+
`cancel_task`::
16+
Privileges to cancel tasks and delete async searches.
17+
See <<delete-async-search,delete async search>> API for more informations.
18+
1519
`create_snapshot`::
1620
Privileges to create snapshots for existing repositories. Can also list and view
1721
details on existing repositories and snapshots.

x-pack/plugin/async-search/qa/security/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ testClusters.all {
1212
setting 'xpack.license.self_generated.type', 'trial'
1313
setting 'xpack.security.enabled', 'true'
1414
extraConfigFile 'roles.yml', file('roles.yml')
15+
user username: "test_kibana_user", password: "x-pack-test-password", role: "kibana_system"
1516
user username: "test-admin", password: 'x-pack-test-password', role: "test-admin"
1617
user username: "user1", password: 'x-pack-test-password', role: "user1"
1718
user username: "user2", password: 'x-pack-test-password', role: "user2"
1819
user username: "user-dls", password: 'x-pack-test-password', role: "user-dls"
19-
user username: "user-manage", password: 'x-pack-test-password', role: "user-manage"
20+
user username: "user-cancel", password: 'x-pack-test-password', role: "user-cancel"
2021
}

x-pack/plugin/async-search/qa/security/roles.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ user-dls:
5656
}
5757
}
5858
59-
user-manage:
59+
user-cancel:
6060
cluster:
61-
- manage
61+
- cancel_task

x-pack/plugin/async-search/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/search/AsyncSearchSecurityIT.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import static org.hamcrest.Matchers.arrayWithSize;
4545
import static org.hamcrest.Matchers.containsString;
4646
import static org.hamcrest.Matchers.equalTo;
47+
import static org.hamcrest.Matchers.greaterThan;
4748

4849
public class AsyncSearchSecurityIT extends ESRestTestCase {
4950
/**
@@ -126,8 +127,8 @@ private void testCase(String user, String other) throws Exception {
126127
ResponseException exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, other));
127128
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
128129

129-
// user-manage cannot access the result
130-
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-manage"));
130+
// user-cancel cannot access the result
131+
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-cancel"));
131132
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
132133

133134
// other cannot delete the result
@@ -145,13 +146,17 @@ private void testCase(String user, String other) throws Exception {
145146
Response delResp = deleteAsyncSearch(id, user);
146147
assertOK(delResp);
147148

148-
// check that user with 'manage' privileges can delete an async
149-
// search submitted by a different user
150-
Response newResp = submitAsyncSearch(indexName, "foo:bar", TimeValue.timeValueSeconds(10), user);
151-
assertOK(newResp);
152-
String newId = extractResponseId(newResp);
153-
delResp = deleteAsyncSearch(newId, "user-manage");
154-
assertOK(delResp);
149+
// check that users with the 'cancel_task' privilege can delete an async
150+
// search submitted by a different user.
151+
for (String runAs : new String[] { "user-cancel", "test_kibana_user" }) {
152+
Response newResp = submitAsyncSearch(indexName, "foo:bar", TimeValue.timeValueSeconds(10), user);
153+
assertOK(newResp);
154+
String newId = extractResponseId(newResp);
155+
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, runAs));
156+
assertThat(exc.getResponse().getStatusLine().getStatusCode(), greaterThan(400));
157+
delResp = deleteAsyncSearch(newId, runAs);
158+
assertOK(delResp);
159+
}
155160
}
156161
ResponseException exc = expectThrows(ResponseException.class,
157162
() -> submitAsyncSearch("index-" + other, "*", TimeValue.timeValueSeconds(10), user));

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/DeleteAsyncResultsService.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,19 @@ public DeleteAsyncResultsService(AsyncTaskIndexService<? extends AsyncResponse<?
4747

4848
public void deleteResponse(DeleteAsyncResultRequest request,
4949
ActionListener<AcknowledgedResponse> listener) {
50-
hasManagePrivilegeAsync(resp -> deleteResponseAsync(request, resp, listener));
50+
hasCancelTaskPrivilegeAsync(resp -> deleteResponseAsync(request, resp, listener));
5151
}
5252

5353
/**
54-
* Checks if the authenticated user has the right privilege (manage) to
54+
* Checks if the authenticated user has the right privilege (cancel_task) to
5555
* delete async search submitted by another user.
5656
*/
57-
private void hasManagePrivilegeAsync(Consumer<Boolean> consumer) {
57+
private void hasCancelTaskPrivilegeAsync(Consumer<Boolean> consumer) {
5858
final Authentication current = store.getAuthentication();
5959
if (current != null) {
6060
HasPrivilegesRequest req = new HasPrivilegesRequest();
6161
req.username(current.getUser().principal());
62-
req.clusterPrivileges(ClusterPrivilegeResolver.MANAGE.name());
62+
req.clusterPrivileges(ClusterPrivilegeResolver.CANCEL_TASK.name());
6363
req.indexPrivileges(new RoleDescriptor.IndicesPrivileges[]{});
6464
req.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[]{});
6565
try {
@@ -75,17 +75,17 @@ private void hasManagePrivilegeAsync(Consumer<Boolean> consumer) {
7575
}
7676

7777
private void deleteResponseAsync(DeleteAsyncResultRequest request,
78-
boolean hasManagePrivilege,
78+
boolean hasCancelTaskPrivilege,
7979
ActionListener<AcknowledgedResponse> listener) {
8080
try {
8181
AsyncExecutionId searchId = AsyncExecutionId.decode(request.getId());
82-
AsyncTask task = hasManagePrivilege ? store.getTask(taskManager, searchId, AsyncTask.class) :
82+
AsyncTask task = hasCancelTaskPrivilege ? store.getTask(taskManager, searchId, AsyncTask.class) :
8383
store.getTaskAndCheckAuthentication(taskManager, searchId, AsyncTask.class);
8484
if (task != null) {
8585
//the task was found and gets cancelled. The response may or may not be found, but we will return 200 anyways.
8686
task.cancelTask(taskManager, () -> deleteResponseFromIndex(searchId, true, listener), "cancelled by user");
8787
} else {
88-
if (hasManagePrivilege) {
88+
if (hasCancelTaskPrivilege) {
8989
deleteResponseFromIndex(searchId, false, listener);
9090
} else {
9191
store.ensureAuthenticatedUserCanDeleteFromIndex(searchId,

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ public class ClusterPrivilegeResolver {
148148
public static final NamedClusterPrivilege MANAGE_LOGSTASH_PIPELINES = new ActionClusterPrivilege("manage_logstash_pipelines",
149149
Set.of("cluster:admin/logstash/pipeline/*"));
150150

151+
public static final NamedClusterPrivilege CANCEL_TASK = new ActionClusterPrivilege("cancel_task",
152+
Set.of("cluster:admin/tasks/cancel"));
153+
151154
private static final Map<String, NamedClusterPrivilege> VALUES = sortByAccessLevel(List.of(
152155
NONE,
153156
ALL,
@@ -187,7 +190,8 @@ public class ClusterPrivilegeResolver {
187190
DELEGATE_PKI,
188191
MANAGE_OWN_API_KEY,
189192
MANAGE_ENRICH,
190-
MANAGE_LOGSTASH_PIPELINES));
193+
MANAGE_LOGSTASH_PIPELINES,
194+
CANCEL_TASK));
191195

192196
/**
193197
* Resolves a {@link NamedClusterPrivilege} from a given name if it exists.

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ private static Map<String, RoleDescriptor> initializeReservedRoles() {
123123
// The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core
124124
"cluster:admin/analyze",
125125
// To facilitate using the file uploader functionality
126-
"monitor_text_structure"
126+
"monitor_text_structure",
127+
// To cancel tasks and delete async searches
128+
"cancel_task"
127129
},
128130
new RoleDescriptor.IndicesPrivileges[] {
129131
RoleDescriptor.IndicesPrivileges.builder()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class AsyncSearchUser extends User {
1717
public static final AsyncSearchUser INSTANCE = new AsyncSearchUser();
1818
public static final String ROLE_NAME = UsernamesField.ASYNC_SEARCH_ROLE;
1919
public static final Role ROLE = Role.builder(new RoleDescriptor(ROLE_NAME,
20-
null,
20+
new String[] { "cancel_task" },
2121
new RoleDescriptor.IndicesPrivileges[] {
2222
RoleDescriptor.IndicesPrivileges.builder()
2323
.indices(RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + "*")

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.elasticsearch.xpack.core.security.authz.privilege;
88

99
import org.apache.lucene.util.automaton.Operations;
10+
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksAction;
1011
import org.elasticsearch.common.util.set.Sets;
1112
import org.elasticsearch.test.ESTestCase;
1213
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
@@ -281,4 +282,9 @@ public void testIngestPipelinePrivileges() {
281282

282283
}
283284
}
285+
286+
public void testCancelTasksPrivilege() {
287+
verifyClusterActionAllowed(ClusterPrivilegeResolver.CANCEL_TASK, CancelTasksAction.NAME);
288+
verifyClusterActionDenied(ClusterPrivilegeResolver.CANCEL_TASK, "cluster:admin/whatever");
289+
}
284290
}

x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ setup:
1515
# This is fragile - it needs to be updated every time we add a new cluster/index privilege
1616
# I would much prefer we could just check that specific entries are in the array, but we don't have
1717
# an assertion for that
18-
- length: { "cluster" : 39 }
18+
- length: { "cluster" : 40 }
1919
- length: { "index" : 19 }

0 commit comments

Comments
 (0)