Authorize on shard requests for bulk actions (elastic/x-pack-elasticsearch#2369)

* Add support for authz checks at on shard requests

* Add Rest Tests for authorization

* Bulk security - Only reject individual items, rather than a whole shard

* Sync with core change

* Grant "delete" priv in ML smoketest

This role had index and+bulk privileges but it also needs delete (in order to delete ML model-snapshots)

Original commit: elastic/x-pack-elasticsearch@830e89e652
This commit is contained in:
Tim Vernum 2017-09-01 01:49:46 +10:00 committed by Jason Tedor
parent 2033b027ed
commit 57a07d6b5a
7 changed files with 1217 additions and 15 deletions

View File

@ -15,12 +15,15 @@ import java.util.function.Predicate;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction;
import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.bulk.BulkAction; import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.bulk.BulkItemRequest;
import org.elasticsearch.action.bulk.BulkShardRequest;
import org.elasticsearch.action.bulk.TransportShardBulkAction;
import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteAction;
import org.elasticsearch.action.get.MultiGetAction; import org.elasticsearch.action.get.MultiGetAction;
import org.elasticsearch.action.index.IndexAction; import org.elasticsearch.action.index.IndexAction;
@ -30,6 +33,7 @@ import org.elasticsearch.action.search.SearchScrollAction;
import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.SearchTransportService;
import org.elasticsearch.action.support.replication.TransportReplicationAction.ConcreteShardRequest; import org.elasticsearch.action.support.replication.TransportReplicationAction.ConcreteShardRequest;
import org.elasticsearch.action.termvectors.MultiTermVectorsAction; import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
import org.elasticsearch.action.update.UpdateAction;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
@ -62,7 +66,6 @@ import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore;
import org.elasticsearch.xpack.security.support.Automatons; import org.elasticsearch.xpack.security.support.Automatons;
import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.AnonymousUser;
import org.elasticsearch.xpack.security.user.ElasticUser;
import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.SystemUser;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.xpack.security.user.XPackUser; import org.elasticsearch.xpack.security.user.XPackUser;
@ -118,10 +121,10 @@ public class AuthorizationService extends AbstractComponent {
* have the appropriate privileges for this action/request, an {@link ElasticsearchSecurityException} * have the appropriate privileges for this action/request, an {@link ElasticsearchSecurityException}
* will be thrown. * will be thrown.
* *
* @param authentication The authentication information * @param authentication The authentication information
* @param action The action * @param action The action
* @param request The request * @param request The request
* @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request * @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request
*/ */
public void authorize(Authentication authentication, String action, TransportRequest request, Role userRole, public void authorize(Authentication authentication, String action, TransportRequest request, Role userRole,
Role runAsRole) throws ElasticsearchSecurityException { Role runAsRole) throws ElasticsearchSecurityException {
@ -208,7 +211,7 @@ public class AuthorizationService extends AbstractComponent {
throw denial(authentication, action, request); throw denial(authentication, action, request);
} else if (TransportActionProxy.isProxyAction(action)) { } else if (TransportActionProxy.isProxyAction(action)) {
// we authorize proxied actions once they are "unwrapped" on the next node // we authorize proxied actions once they are "unwrapped" on the next node
if (TransportActionProxy.isProxyRequest(originalRequest) == false ) { if (TransportActionProxy.isProxyRequest(originalRequest) == false) {
throw new IllegalStateException("originalRequest is not a proxy request: [" + originalRequest + "] but action: [" throw new IllegalStateException("originalRequest is not a proxy request: [" + originalRequest + "] but action: ["
+ action + "] is a proxy action"); + action + "] is a proxy action");
} }
@ -317,9 +320,45 @@ public class AuthorizationService extends AbstractComponent {
} }
} }
if (action.equals(TransportShardBulkAction.ACTION_NAME)) {
// is this is performing multiple actions on the index, then check each of those actions.
assert request instanceof BulkShardRequest
: "Action " + action + " requires " + BulkShardRequest.class + " but was " + request.getClass();
if (localIndices.size() != 1) {
throw new IllegalStateException("Action " + action + " should operate on exactly 1 local index but was "
+ localIndices.size());
}
String index = localIndices.iterator().next();
BulkShardRequest bulk = (BulkShardRequest) request;
for (BulkItemRequest item : bulk.items()) {
final String itemAction = getAction(item);
final IndicesAccessControl itemAccessControl = permission.authorize(itemAction, localIndices, metaData,
fieldPermissionsCache);
if (itemAccessControl.isGranted() == false) {
item.abort(index, denial(authentication, itemAction, request));
}
}
}
grant(authentication, action, originalRequest); grant(authentication, action, originalRequest);
} }
private String getAction(BulkItemRequest item) {
final DocWriteRequest docWriteRequest = item.request();
switch (docWriteRequest.opType()) {
case INDEX:
case CREATE:
return IndexAction.NAME;
case UPDATE:
return UpdateAction.NAME;
case DELETE:
return DeleteAction.NAME;
}
throw new IllegalArgumentException("No equivalent action for opType [" + docWriteRequest.opType() + "]");
}
private ResolvedIndices resolveIndexNames(Authentication authentication, String action, TransportRequest request, MetaData metaData, private ResolvedIndices resolveIndexNames(Authentication authentication, String action, TransportRequest request, MetaData metaData,
AuthorizedIndices authorizedIndices) { AuthorizedIndices authorizedIndices) {
try { try {

View File

@ -12,11 +12,13 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.MockIndicesRequest; import org.elasticsearch.action.MockIndicesRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
@ -49,9 +51,9 @@ import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusAction; import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusAction;
import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusRequest; import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusRequest;
import org.elasticsearch.action.bulk.BulkAction; import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.bulk.BulkItemRequest;
import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.action.bulk.BulkShardRequest;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteAction;
import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetAction;
@ -71,6 +73,7 @@ import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.SearchTransportService;
import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.termvectors.MultiTermVectorsAction; import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.action.termvectors.MultiTermVectorsRequest;
import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsAction;
@ -81,13 +84,16 @@ import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.TriFunction;
import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.license.GetLicenseAction; import org.elasticsearch.license.GetLicenseAction;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
@ -724,11 +730,11 @@ public class AuthorizationServiceTests extends ESTestCase {
List<Tuple<String, TransportRequest>> requests = new ArrayList<>(); List<Tuple<String, TransportRequest>> requests = new ArrayList<>();
requests.add(new Tuple<>(DeleteAction.NAME, new DeleteRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); requests.add(new Tuple<>(DeleteAction.NAME, new DeleteRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id")));
requests.add(new Tuple<>(BulkAction.NAME + "[s]", requests.add(new Tuple<>(BulkAction.NAME + "[s]",
new DeleteRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); createBulkShardRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, DeleteRequest::new)));
requests.add(new Tuple<>(UpdateAction.NAME, new UpdateRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); requests.add(new Tuple<>(UpdateAction.NAME, new UpdateRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id")));
requests.add(new Tuple<>(IndexAction.NAME, new IndexRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); requests.add(new Tuple<>(IndexAction.NAME, new IndexRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id")));
requests.add(new Tuple<>(BulkAction.NAME + "[s]", new IndexRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, requests.add(new Tuple<>(BulkAction.NAME + "[s]",
"type", "id"))); createBulkShardRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, IndexRequest::new)));
requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(SecurityLifecycleService.SECURITY_INDEX_NAME))); requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(SecurityLifecycleService.SECURITY_INDEX_NAME)));
requests.add(new Tuple<>(TermVectorsAction.NAME, requests.add(new Tuple<>(TermVectorsAction.NAME,
new TermVectorsRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); new TermVectorsRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id")));
@ -854,30 +860,36 @@ public class AuthorizationServiceTests extends ESTestCase {
} }
public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() { public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() {
String action; final MockIndicesRequest mockRequest = new MockIndicesRequest(IndicesOptions.strictExpandOpen(), "index");
switch(randomIntBetween(0, 4)) { final TransportRequest request;
final String action;
switch (randomIntBetween(0, 4)) {
case 0: case 0:
action = MultiGetAction.NAME + "[shard]"; action = MultiGetAction.NAME + "[shard]";
request = mockRequest;
break; break;
case 1: case 1:
//reindex, msearch, search template, and multi search template delegate to search //reindex, msearch, search template, and multi search template delegate to search
action = SearchAction.NAME; action = SearchAction.NAME;
request = mockRequest;
break; break;
case 2: case 2:
action = MultiTermVectorsAction.NAME + "[shard]"; action = MultiTermVectorsAction.NAME + "[shard]";
request = mockRequest;
break; break;
case 3: case 3:
action = BulkAction.NAME + "[s]"; action = BulkAction.NAME + "[s]";
request = createBulkShardRequest("index", IndexRequest::new);
break; break;
case 4: case 4:
action = "indices:data/read/mpercolate[s]"; action = "indices:data/read/mpercolate[s]";
request = mockRequest;
break; break;
default: default:
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
logger.info("--> action: {}", action); logger.info("--> action: {}", action);
TransportRequest request = new MockIndicesRequest(IndicesOptions.strictExpandOpen(), "index");
User userAllowed = new User("userAllowed", "roleAllowed"); User userAllowed = new User("userAllowed", "roleAllowed");
roleMap.put("roleAllowed", new RoleDescriptor("roleAllowed", null, roleMap.put("roleAllowed", new RoleDescriptor("roleAllowed", null,
new IndicesPrivileges[] { IndicesPrivileges.builder().indices("index").privileges("all").build() }, null)); new IndicesPrivileges[] { IndicesPrivileges.builder().indices("index").privileges("all").build() }, null));
@ -890,6 +902,11 @@ public class AuthorizationServiceTests extends ESTestCase {
() -> authorize(createAuthentication(userDenied), action, request), action, "userDenied"); () -> authorize(createAuthentication(userDenied), action, request), action, "userDenied");
} }
private BulkShardRequest createBulkShardRequest(String indexName, TriFunction<String, String, String, DocWriteRequest<?>> req) {
final BulkItemRequest[] items = { new BulkItemRequest(1, req.apply(indexName, "type", "id")) };
return new BulkShardRequest(new ShardId(indexName, UUID.randomUUID().toString(), 1), WriteRequest.RefreshPolicy.IMMEDIATE, items);
}
public void testSameUserPermission() { public void testSameUserPermission() {
final User user = new User("joe"); final User user = new User("joe");
final boolean changePasswordRequest = randomBoolean(); final boolean changePasswordRequest = randomBoolean();

View File

@ -0,0 +1,257 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_role:
name: "mixed_role"
body: >
{
"indices": [
{ "names": ["only_read"], "privileges": ["read"] },
{ "names": ["only_index"], "privileges": ["index"] },
{ "names": ["only_delete"], "privileges": ["delete"] },
{ "names": ["everything"], "privileges": ["all"] }
]
}
- do:
xpack.security.put_user:
username: "test_user"
body: >
{
"password" : "x-pack-test-password",
"roles" : [ "mixed_role" ],
"full_name" : "user with mixed privileges to multiple indices"
}
- do:
indices.create:
index: only_read
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_index
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_delete
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: everything
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
---
teardown:
- do:
xpack.security.delete_user:
username: "test_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "mixed_role"
ignore: 404
---
"Test indexing a document when allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
create:
id: 1
index: only_index
type: doc
body: >
{
"name" : "doc1"
}
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
create:
id: 2
index: everything
type: doc
body: >
{
"name" : "doc2"
}
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "3"}}'
- '{"name": "doc3"}'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "4"}}'
- '{"name": "doc4"}'
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "5"}}'
- '{"name": "doc5"}'
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "everything", "_type": "doc", "_id": "6"}}'
- '{"name": "doc6"}'
- do: # superuser
search:
index: only_index
- match: { hits.total: 3 }
- do: # superuser
search:
index: everything
- match: { hits.total: 3 }
---
"Test indexing a document when not allowed":
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
create:
refresh: true
id: 7
index: only_read
type: doc
body: >
{
"name" : "doc7"
}
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
create:
refresh: true
id: 8
index: only_delete
type: doc
body: >
{
"name" : "doc8"
}
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "9"}}'
- '{"name": "doc9"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "10"}}'
- '{"name": "doc10"}'
- match: { errors: true }
- match: { items.0.index.status: 403 }
- match: { items.0.index.error.type: "security_exception" }
- match: { items.1.index.status: 403 }
- match: { items.1.index.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "11"}}'
- '{"name": "doc11"}'
- match: { errors: true }
- match: { items.0.index.status: 403 }
- match: { items.0.index.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "12"}}'
- '{"name": "doc12"}'
- match: { errors: true }
- match: { items.0.index.status: 403 }
- match: { items.0.index.error.type: "security_exception" }
- do: # superuser
search:
index: only_read
- match: { hits.total: 0 }
- do: # superuser
search:
index: only_delete
- match: { hits.total: 0 }
---
"Test bulk indexing documents when only some are allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "13"}}'
- '{"name": "doc13"}'
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "14"}}'
- '{"name": "doc14"}'
- match: { errors: true }
- match: { items.0.index.status: 403 }
- match: { items.0.index.error.type: "security_exception" }
- match: { items.1.index.status: 201 }
- do: # superuser
search:
index: only_index
body: { "query": { "term": { "_id": "14" } } }
- match: { hits.total: 1 }

View File

@ -0,0 +1,331 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_role:
name: "mixed_role"
body: >
{
"indices": [
{ "names": ["only_read"], "privileges": ["read"] },
{ "names": ["only_index"], "privileges": ["index"] },
{ "names": ["only_delete"], "privileges": ["delete"] },
{ "names": ["everything"], "privileges": ["all"] }
]
}
- do:
xpack.security.put_user:
username: "test_user"
body: >
{
"password" : "x-pack-test-password",
"roles" : [ "mixed_role" ],
"full_name" : "user with mixed privileges to multiple indices"
}
- do:
indices.create:
index: only_read
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_index
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_delete
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: everything
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "1"}}'
- '{"name": "doc1"}'
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- '{"name": "doc2"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "3"}}'
- '{"name": "doc3"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "4"}}'
- '{"name": "doc4"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "5"}}'
- '{"name": "doc5"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "6"}}'
- '{"name": "doc6"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "7"}}'
- '{"name": "doc7"}'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "8"}}'
- '{"name": "doc8"}'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "9"}}'
- '{"name": "doc9"}'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "10"}}'
- '{"name": "doc10"}'
---
teardown:
- do:
xpack.security.delete_user:
username: "test_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "mixed_role"
ignore: 404
---
"Test deleting a document when allowed":
- do: # superuser
search:
index: only_delete
body: { "query": { "terms": { "_id": [ "3", "4", "5" ] } } }
- match: { hits.total: 3 }
- do: # superuser
search:
index: everything
body: { "query": { "terms": { "_id": [ "8", "9", "10" ] } } }
- match: { hits.total: 3 }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
delete:
refresh: true
index: only_delete
type: doc
id: 3
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
delete:
refresh: true
index: everything
type: doc
id: 8
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"delete": {"_index": "only_delete", "_type": "doc", "_id": "4"}}'
- '{"delete": {"_index": "everything" , "_type": "doc", "_id": "9"}}'
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body: # The rest test won't send streaming content unless it has multiple bodies, so we send the same delete twice
- '{"delete": {"_index": "only_delete", "_type": "doc", "_id": "5"}}'
- '{"delete": {"_index": "only_delete", "_type": "doc", "_id": "5"}}'
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body: # The rest test won't send streaming content unless it has multiple bodies, so we send the same delete twice
- delete:
_index: everything
_type: doc
_id: 10
- delete:
_index: everything
_type: doc
_id: 10
- do: # superuser
search:
index: only_delete
body: { "query": { "terms": { "_id": [ "3", "4", "5" ] } } }
- match: { hits.total: 0 }
- do: # superuser
search:
index: everything
body: { "query": { "terms": { "_id": [ "8", "9", "10" ] } } }
- match: { hits.total: 0 }
---
"Test deleting a document when not allowed":
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
delete:
refresh: true
index: only_read
type: doc
id: 1
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
delete:
refresh: true
index: only_index
type: doc
id: 2
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"delete": {"_index": "only_read" , "_type": "doc", "_id": "1"}}'
- '{"delete": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- match: { errors: true }
- match: { items.0.delete.status: 403 }
- match: { items.0.delete.error.type: "security_exception" }
- match: { items.1.delete.status: 403 }
- match: { items.1.delete.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body: # The rest test won't send streaming content unless it has multiple bodies, so we send the same delete twice
- '{"delete": {"_index": "only_read" , "_type": "doc", "_id": "1"}}'
- '{"delete": {"_index": "only_read" , "_type": "doc", "_id": "1"}}'
- match: { errors: true }
- match: { items.0.delete.status: 403 }
- match: { items.0.delete.error.type: "security_exception" }
- match: { items.1.delete.status: 403 }
- match: { items.1.delete.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body: # The rest test won't send streaming content unless it has multiple bodies, so we send the same delete twice
- '{"delete": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- '{"delete": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- match: { errors: true }
- match: { items.0.delete.status: 403 }
- match: { items.0.delete.error.type: "security_exception" }
- match: { items.1.delete.status: 403 }
- match: { items.1.delete.error.type: "security_exception" }
- do: # superuser
search:
index: only_read
- match: { hits.total: 1 }
- do: # superuser
search:
index: only_index
- match: { hits.total: 1 }
---
"Test bulk delete documents when only some are allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"delete": {"_index": "only_read" , "_type": "doc", "_id": "1"}}'
- '{"delete": {"_index": "only_delete", "_type": "doc", "_id": "6"}}'
- match: { errors: true }
- match: { items.0.delete.status: 403 }
- match: { items.0.delete.error.type: "security_exception" }
- match: { items.1.delete.status: 200 }
- do: # superuser
search:
index: only_read
body: { "query": { "term": { "_id": "1" } } }
- match: { hits.total: 1 }
- do: # superuser
search:
index: only_delete
body: { "query": { "term": { "_id": "6" } } }
- match: { hits.total: 0 }
---
"Test bulk delete and index documents when only some are allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
bulk:
refresh: true
body:
- '{"index" : {"_index": "only_delete", "_type": "doc", "_id": "11"}}'
- '{"name" : "doc11"}'
- '{"delete": {"_index": "only_delete", "_type": "doc", "_id": "7"}}'
- '{"index" : {"_index": "only_index", "_type": "doc", "_id": "12"}}'
- '{"name" : "doc12"}'
- '{"delete": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- match: { errors: true }
- match: { items.0.index.status: 403 }
- match: { items.0.index.error.type: "security_exception" }
- match: { items.1.delete.status: 200 }
- match: { items.2.index.status: 201 }
- match: { items.3.delete.status: 403 }
- match: { items.3.delete.error.type: "security_exception" }
- do: # superuser
search:
index: only_delete
body: { "query": { "terms": { "_id": [ "11", "7" ] } } }
# 11 wasn't created, 7 was deleted
- match: { hits.total: 0 }
- do: # superuser
search:
index: only_index
body: { "query": { "terms": { "_id": [ "12", "2" ] } } }
# 12 was created, 2 wasn't deleted
- match: { hits.total: 2 }

View File

@ -0,0 +1,291 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_role:
name: "mixed_role"
body: >
{
"indices": [
{ "names": ["only_read"], "privileges": ["read"] },
{ "names": ["only_index"], "privileges": ["index"] },
{ "names": ["only_delete"], "privileges": ["delete"] },
{ "names": ["read_write"], "privileges": ["read", "write"] },
{ "names": ["everything"], "privileges": ["all"] }
]
}
- do:
xpack.security.put_user:
username: "test_user"
body: >
{
"password" : "x-pack-test-password",
"roles" : [ "mixed_role" ],
"full_name" : "user with mixed privileges to multiple indices"
}
- do:
indices.create:
index: only_read
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_index
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: only_delete
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: read_write
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
indices.create:
index: everything
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
- do:
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "1"}}'
- '{"name": "doc1"}'
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "2"}}'
- '{"name": "doc2"}'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "3"}}'
- '{"name": "doc3"}'
- '{"index": {"_index": "read_write", "_type": "doc", "_id": "4"}}'
- '{"name": "doc4"}'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "5"}}'
- '{"name": "doc5"}'
---
teardown:
- do:
xpack.security.delete_user:
username: "test_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "mixed_role"
ignore: 404
---
"Test get a document when authorized":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
get:
id: 1
index: only_read
type: doc
- match: { _index: only_read }
- match: { _id: "1" }
- match: { _source.name: "doc1" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
get:
id: 4
index: read_write
type: doc
- match: { _index: read_write }
- match: { _id: "4" }
- match: { _source.name: "doc4" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
get:
id: 5
index: everything
type: doc
- match: { _index: everything }
- match: { _id: "5" }
- match: { _source.name: "doc5" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_read", _type: "doc", _id: "1" }
- { _index: "read_write", _type: "doc", _id: "4" }
- { _index: "everything", _type: "doc", _id: "5" }
- match: { docs.0._index: "only_read" }
- match: { docs.0._id: "1" }
- match: { docs.0._source.name: "doc1" }
- match: { docs.1._index: "read_write" }
- match: { docs.1._id: "4" }
- match: { docs.1._source.name: "doc4" }
- match: { docs.2._index: "everything"}
- match: { docs.2._id: "5" }
- match: { docs.2._source.name: "doc5" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_read", _type: "doc", _id: "1" }
- match: { docs.0._index: "only_read"}
- match: { docs.0._id: "1" }
- match: { docs.0._source.name: "doc1" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "read_write", _type: "doc", _id: "4" }
- match: { docs.0._index: read_write}
- match: { docs.0._id: "4" }
- match: { docs.0._source.name: "doc4" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "everything", _type: "doc", _id: "5" }
- match: { docs.0._index: "everything"}
- match: { docs.0._id: "5" }
- match: { docs.0._source.name: "doc5" }
---
"Test get a document when not allowed":
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
get:
id: 2
index: only_index
type: doc
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
get:
id: 3
index: only_delete
type: doc
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_index", _type: "doc", _id: "2" }
- { _index: "only_delete", _type: "doc", _id: "3" }
- match: { docs.0._index: "only_index"}
- match: { docs.0._id: "2" }
- match: { docs.0.error.type: "security_exception" }
- match: { docs.1._index: "only_delete"}
- match: { docs.1._id: "3" }
- match: { docs.1.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_index", _type: "doc", _id: "2" }
- match: { docs.0._index: "only_index"}
- match: { docs.0._id: "2" }
- match: { docs.0.error.type: "security_exception" }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_delete", _type: "doc", _id: "3" }
- match: { docs.0._index: "only_delete"}
- match: { docs.0._id: "3" }
- match: { docs.0.error.type: "security_exception" }
---
"Test mget documents when only some are allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
mget:
body:
docs:
- { _index: "only_read" , _type: "doc", _id: "1" }
- { _index: "only_index" , _type: "doc", _id: "2" }
- { _index: "only_delete", _type: "doc", _id: "3" }
- { _index: "read_write" , _type: "doc", _id: "4" }
- { _index: "everything" , _type: "doc", _id: "5" }
- match: { docs.0._index: "only_read" }
- match: { docs.0._id: "1" }
- match: { docs.0._source.name: "doc1" }
- match: { docs.1._index: "only_index"}
- match: { docs.1._id: "2" }
- match: { docs.1.error.type: "security_exception" }
- match: { docs.2._index: "only_delete"}
- match: { docs.2._id: "3" }
- match: { docs.2.error.type: "security_exception" }
- match: { docs.3._index: "read_write" }
- match: { docs.3._id: "4" }
- match: { docs.3._source.name: "doc4" }
- match: { docs.4._index: "everything" }
- match: { docs.4._id: "5" }
- match: { docs.4._source.name: "doc5" }

View File

@ -0,0 +1,266 @@
---
setup:
- skip:
features: headers
- do:
cluster.health:
wait_for_status: yellow
- do:
xpack.security.put_role:
name: "mixed_role"
body: >
{
"indices": [
{ "names": ["only_read"], "privileges": ["read"] },
{ "names": ["only_index"], "privileges": ["index"] },
{ "names": ["only_delete"], "privileges": ["delete"] },
{ "names": ["read_write"], "privileges": ["read", "write"] },
{ "names": ["everything"], "privileges": ["all"] }
]
}
- do:
xpack.security.put_user:
username: "test_user"
body: >
{
"password" : "x-pack-test-password",
"roles" : [ "mixed_role" ],
"full_name" : "user with mixed privileges to multiple indices"
}
- do:
indices.create:
index: only_read
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
tag:
type: "keyword"
- do:
indices.create:
index: only_index
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
tag:
type: "keyword"
- do:
indices.create:
index: only_delete
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
tag:
type: "keyword"
- do:
indices.create:
index: read_write
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
tag:
type: "keyword"
- do:
indices.create:
index: everything
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 0
mappings:
doc:
properties:
name:
type: "keyword"
tag:
type: "keyword"
- do:
bulk:
refresh: true
body:
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "1"}}'
- '{"name": "doc1", "tag": [ "can-read", "tag-a" ] }'
- '{"index": {"_index": "only_read", "_type": "doc", "_id": "2"}}'
- '{"name": "doc2", "tag": [ "can-read", "tag-b"] }'
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "3"}}'
- '{"name": "doc3", "tag": [ "no-read", "tag-a"] }'
- '{"index": {"_index": "only_index", "_type": "doc", "_id": "4"}}'
- '{"name": "doc4", "tag": [ "no-read", "tag-b"] }'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "5"}}'
- '{"name": "doc5", "tag": [ "no-read", "tag-a"] }'
- '{"index": {"_index": "only_delete", "_type": "doc", "_id": "6"}}'
- '{"name": "doc6", "tag": [ "no-read", "tag-b"] }'
- '{"index": {"_index": "read_write", "_type": "doc", "_id": "7"}}'
- '{"name": "doc7", "tag": [ "can-read", "tag-a" ] }'
- '{"index": {"_index": "read_write", "_type": "doc", "_id": "8"}}'
- '{"name": "doc8", "tag": [ "can-read", "tag-b"] }'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "9"}}'
- '{"name": "doc9", "tag": [ "can-read", "tag-a" ] }'
- '{"index": {"_index": "everything", "_type": "doc", "_id": "10"}}'
- '{"name": "doc10", "tag": [ "can-read", "tag-b"] }'
---
teardown:
- do:
xpack.security.delete_user:
username: "test_user"
ignore: 404
- do:
xpack.security.delete_role:
name: "mixed_role"
ignore: 404
---
"Test search for document when authorized":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
index: only_read
body:
- match: { hits.total: 2 }
- match: { hits.hits.0._index: only_read }
- match: { hits.hits.1._index: only_read }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
index: read_write
body:
- match: { hits.total: 2 }
- match: { hits.hits.0._index: read_write }
- match: { hits.hits.1._index: read_write }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
index: everything
body:
- match: { hits.total: 2 }
- match: { hits.hits.0._index: everything }
- match: { hits.hits.1._index: everything }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
body: { "query": { "term": { "tag": "can-read" } } }
- match: { hits.total: 6 }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
msearch:
body:
- { }
- { "query": { "term": { "tag": "can-read" } } }
- { "index": "only_read" }
- { "query": { "term": { "tag": "tag-a" } } }
- { "index": "read_write" }
- { }
- match: { responses.0.hits.total: 6 }
- match: { responses.1.hits.total: 1 }
- match: { responses.2.hits.total: 2 }
---
"Test search for documents when not allowed":
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
index: only_index
body:
- do:
catch: forbidden
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
index: only_delete
body:
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
body: { "query": { "term": { "tag": "no-read" } } }
- match: { hits.total: 0 }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
body: { "query": { "term": { "_index": "only_index" } } }
- match: { hits.total: 0 }
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
msearch:
body:
- { }
- { "query": { "term": { "tag": "no-read" } } }
- { }
- { "query": { "term": { "_index": "only_index" } } }
- { "index": "only_delete" }
- { }
- match: { responses.0.hits.total: 0 }
- match: { responses.1.hits.total: 0 }
- match: { responses.2.error.type: "security_exception" }
---
"Test search documents when only some are allowed":
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
search:
body: { "query": { "term": { "tag": "tag-a" } } }
- match: { hits.total: 3 } # can-read, read_write, everything
- do:
headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user
msearch:
body:
- { }
- { "query": { "term": { "tag": "tag-a" } } }
- { }
- { "query": { "term": { "tag": "can-read" } } }
- { }
- { "query": { "term": { "tag": "no-read" } } }
- { "index": "only_read" }
- { "query": { "term": { "tag": "tag-a" } } }
- { "index": "only_delete" }
- { "query": { "term": { "tag": "tag-a" } } }
- match: { responses.0.hits.total: 3 } # tag-a (in readable indices)
- match: { responses.1.hits.total: 6 } # can-read
- match: { responses.2.hits.total: 0 } # no-read
- match: { responses.3.hits.total: 1 } # only_read + tag-a
- match: { responses.4.error.type: "security_exception" } # only_delete + tag-a

View File

@ -15,3 +15,4 @@ minimal:
- indices:data/read/search - indices:data/read/search
- indices:data/write/bulk - indices:data/write/bulk
- indices:data/write/index - indices:data/write/index
- indices:data/write/delete