Submit async search to not require read privilege (#58942)

When we execute search against remote indices, the remote indices are authorized on the remote cluster and not on the CCS cluster. When we introduced submit async search we added a check that requires that the user running it has the privilege to execute it on some index. That prevents users from executing async searches against remote indices unless they also have read access on the CCS cluster, which is common when the CCS cluster holds no data.

The solution is to let the submit async search go through as we already do for get and delete async search. Note that the inner search action will still check that the user can access local indices, and remote indices on the remote cluster, like search always does.
This commit is contained in:
Luca Cavanna 2020-07-03 12:16:43 +02:00
parent f6a0c2c59d
commit 4f86f6fb38
4 changed files with 136 additions and 17 deletions

View File

@ -32,8 +32,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.transport.TransportActionProxy;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.eql.EqlAsyncActionNames;
import org.elasticsearch.xpack.core.async.DeleteAsyncResultAction;
import org.elasticsearch.xpack.core.eql.EqlAsyncActionNames;
import org.elasticsearch.xpack.core.search.action.GetAsyncSearchAction;
import org.elasticsearch.xpack.core.search.action.SubmitAsyncSearchAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
@ -268,16 +268,8 @@ public class RBACEngine implements AuthorizationEngine {
listener.onResponse(new IndexAuthorizationResult(true, IndicesAccessControl.ALLOW_NO_INDICES));
}
} else if (isAsyncRelatedAction(action)) {
if (SubmitAsyncSearchAction.NAME.equals(action)) {
// we check if the user has any indices permission when submitting an async-search request in order to be
// able to fail the request early. Fine grained index-level permissions are handled by the search action
// that is triggered internally by the submit API.
authorizeIndexActionName(action, authorizationInfo, null, listener);
} else {
// async-search actions other than submit have a custom security layer that checks if the current user is
// the same as the user that submitted the original request so we can skip security here.
listener.onResponse(new IndexAuthorizationResult(true, IndicesAccessControl.ALLOW_NO_INDICES));
}
//index-level permissions are handled by the search action that is triggered internally by the submit API.
listener.onResponse(new IndexAuthorizationResult(true, IndicesAccessControl.ALLOW_NO_INDICES));
} else {
assert false : "only scroll and async-search related requests are known indices api that don't " +
"support retrieving the indices they relate to";

View File

@ -9,7 +9,7 @@ dependencies {
restResources {
restApi {
includeXpack 'security'
includeXpack 'security', 'async_search'
}
}

View File

@ -22,11 +22,26 @@ setup:
"cluster": [],
"indices": [
{
"names": ["local_index", "my_remote_cluster:test_i*", "my_remote_cluster:aliased_test_index", "test_remote_cluster:test_i*", "my_remote_cluster:secure_alias"],
"names": ["local_index"],
"privileges": ["read"]
}
]
}
- do:
security.put_user:
username: "remote"
body: >
{
"password": "s3krit",
"roles" : [ "remote_ccs" ]
}
- do:
security.put_role:
name: "remote_ccs"
body: >
{
}
---
teardown:
- do:
@ -107,6 +122,58 @@ teardown:
- match: { aggregations.cluster.buckets.0.key: "remote_cluster" }
- match: { aggregations.cluster.buckets.0.doc_count: 6 }
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
async_search.submit:
index: local_index,my_remote_cluster:test_index
wait_for_completion_timeout: 10s
keep_on_completion: true
body:
query:
term:
f1: remote_cluster
aggs:
cluster:
terms:
field: f1.keyword
- set: { id: id }
- match: { is_partial: false }
- match: { response._clusters.total: 2}
- match: { response._clusters.successful: 2}
- match: { response._clusters.skipped: 0}
- match: { response._shards.total: 5 }
- match: { response.hits.total.value: 6}
- match: { response.hits.total.relation: eq}
- match: { response.hits.hits.0._index: "my_remote_cluster:test_index"}
- length: { response.aggregations.cluster.buckets: 1 }
- match: { response.aggregations.cluster.buckets.0.key: "remote_cluster" }
- match: { response.aggregations.cluster.buckets.0.doc_count: 6 }
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
async_search.get:
id: "$id"
- match: { is_partial: false }
- match: { response._clusters.total: 2}
- match: { response._clusters.successful: 2}
- match: { response._clusters.skipped: 0}
- match: { response._shards.total: 5 }
- match: { response.hits.total.value: 6}
- match: { response.hits.total.relation: eq}
- match: { response.hits.hits.0._index: "my_remote_cluster:test_index"}
- length: { response.aggregations.cluster.buckets: 1 }
- match: { response.aggregations.cluster.buckets.0.key: "remote_cluster" }
- match: { response.aggregations.cluster.buckets.0.doc_count: 6 }
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
async_search.delete:
id: "$id"
- match: { acknowledged: true }
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
search:
@ -130,7 +197,7 @@ teardown:
# Test wildcard in cluster name
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
search:
rest_total_hits_as_int: true
index: "my_*:test_index"
@ -201,7 +268,7 @@ teardown:
# Test wildcard that matches multiple (two) cluster names
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
search:
rest_total_hits_as_int: true
index: "*_remote_cluster:test_ind*"
@ -216,7 +283,7 @@ teardown:
"Search an filtered alias on the remote cluster":
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
search:
rest_total_hits_as_int: true
index: my_remote_cluster:aliased_test_index
@ -233,7 +300,7 @@ teardown:
"Search across clusters via a secured alias":
- do:
headers: { Authorization: "Basic am9lOnMza3JpdA==" }
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
search:
rest_total_hits_as_int: true
index: my_remote_cluster:secure_alias # TODO make this a wildcard once
@ -246,3 +313,40 @@ teardown:
- is_true: hits.hits.0._source.secure
- match: { hits.hits.0._index: "my_remote_cluster:secured_via_alias" }
---
"Async search against filtered alias on the remote cluster":
- do:
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
async_search.submit:
index: my_remote_cluster:aliased_test_index
wait_for_completion_timeout: 10s
keep_on_completion: true
- set: { id: id }
- match: { is_partial: false }
- match: { response._clusters.total: 1}
- match: { response._clusters.successful: 1}
- match: { response._shards.total: 3 }
- length: { response.hits.hits: 2 }
- match: { response.hits.hits.0._source.filter_field: 1 }
- match: { response.hits.hits.0._index: "my_remote_cluster:test_index" }
- do:
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
async_search.get:
id: "$id"
- match: { is_partial: false }
- is_true: response._clusters
- match: { response._shards.total: 3 }
- length: { response.hits.hits: 2 }
- match: { response.hits.hits.0._source.filter_field: 1 }
- match: { response.hits.hits.0._index: "my_remote_cluster:test_index" }
- do:
headers: { Authorization: "Basic cmVtb3RlOnMza3JpdA==" }
async_search.delete:
id: "$id"
- match: { acknowledged: true }

View File

@ -28,6 +28,29 @@ setup:
}
]
}
- do:
security.put_user:
username: "remote"
body: >
{
"password": "s3krit",
"roles" : [ "remote_ccs" ]
}
- do:
security.put_role:
name: "remote_ccs"
body: >
{
"cluster": ["monitor"],
"indices": [
{
"names": ["single_doc_index", "secure_alias", "test_index", "aliased_test_index", "field_caps_index_1",
"field_caps_index_3"],
"privileges": ["read", "read_cross_cluster"]
}
]
}
---
"Index data and search on the remote cluster":