diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index c15c57edf06..ffde645f299 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -15,12 +15,15 @@ import java.util.function.Predicate; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.CompositeIndicesRequest; +import org.elasticsearch.action.DocWriteRequest; 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.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; 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.get.MultiGetAction; 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.support.replication.TransportReplicationAction.ConcreteShardRequest; import org.elasticsearch.action.termvectors.MultiTermVectorsAction; +import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.service.ClusterService; 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.support.Automatons; 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.User; 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} * will be thrown. * - * @param authentication The authentication information - * @param action The action - * @param request The request - * @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request + * @param authentication The authentication information + * @param action The action + * @param request The 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, Role runAsRole) throws ElasticsearchSecurityException { @@ -208,7 +211,7 @@ public class AuthorizationService extends AbstractComponent { throw denial(authentication, action, request); } else if (TransportActionProxy.isProxyAction(action)) { // 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: [" + 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); } + 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, AuthorizedIndices authorizedIndices) { try { diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 98b62892ef0..f40f215e0a3 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -12,11 +12,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.CompositeIndicesRequest; +import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.MockIndicesRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; 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.UpgradeStatusRequest; import org.elasticsearch.action.bulk.BulkAction; +import org.elasticsearch.action.bulk.BulkItemRequest; import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.settings.ClusterSettings; +import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteRequest; 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.support.IndicesOptions; import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.termvectors.MultiTermVectorsAction; import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsAction; @@ -81,13 +84,16 @@ import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.license.GetLicenseAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; @@ -724,11 +730,11 @@ public class AuthorizationServiceTests extends ESTestCase { List> requests = new ArrayList<>(); requests.add(new Tuple<>(DeleteAction.NAME, new DeleteRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); 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<>(IndexAction.NAME, new IndexRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); - requests.add(new Tuple<>(BulkAction.NAME + "[s]", new IndexRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, - "type", "id"))); + requests.add(new Tuple<>(BulkAction.NAME + "[s]", + createBulkShardRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, IndexRequest::new))); requests.add(new Tuple<>(SearchAction.NAME, new SearchRequest(SecurityLifecycleService.SECURITY_INDEX_NAME))); requests.add(new Tuple<>(TermVectorsAction.NAME, new TermVectorsRequest(SecurityLifecycleService.SECURITY_INDEX_NAME, "type", "id"))); @@ -854,30 +860,36 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() { - String action; - switch(randomIntBetween(0, 4)) { + final MockIndicesRequest mockRequest = new MockIndicesRequest(IndicesOptions.strictExpandOpen(), "index"); + final TransportRequest request; + final String action; + switch (randomIntBetween(0, 4)) { case 0: action = MultiGetAction.NAME + "[shard]"; + request = mockRequest; break; case 1: //reindex, msearch, search template, and multi search template delegate to search action = SearchAction.NAME; + request = mockRequest; break; case 2: action = MultiTermVectorsAction.NAME + "[shard]"; + request = mockRequest; break; case 3: action = BulkAction.NAME + "[s]"; + request = createBulkShardRequest("index", IndexRequest::new); break; case 4: action = "indices:data/read/mpercolate[s]"; + request = mockRequest; break; default: throw new UnsupportedOperationException(); } logger.info("--> action: {}", action); - TransportRequest request = new MockIndicesRequest(IndicesOptions.strictExpandOpen(), "index"); User userAllowed = new User("userAllowed", "roleAllowed"); roleMap.put("roleAllowed", new RoleDescriptor("roleAllowed", 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"); } + private BulkShardRequest createBulkShardRequest(String indexName, TriFunction> 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() { final User user = new User("joe"); final boolean changePasswordRequest = randomBoolean(); diff --git a/plugin/src/test/resources/rest-api-spec/test/security-authz/10_index_doc.yml b/plugin/src/test/resources/rest-api-spec/test/security-authz/10_index_doc.yml new file mode 100644 index 00000000000..b5132fc75e0 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security-authz/10_index_doc.yml @@ -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 } diff --git a/plugin/src/test/resources/rest-api-spec/test/security-authz/11_delete_doc.yml b/plugin/src/test/resources/rest-api-spec/test/security-authz/11_delete_doc.yml new file mode 100644 index 00000000000..3fd523ac495 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security-authz/11_delete_doc.yml @@ -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 } diff --git a/plugin/src/test/resources/rest-api-spec/test/security-authz/20_get_doc.yml b/plugin/src/test/resources/rest-api-spec/test/security-authz/20_get_doc.yml new file mode 100644 index 00000000000..3767ca5dd27 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security-authz/20_get_doc.yml @@ -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" } + diff --git a/plugin/src/test/resources/rest-api-spec/test/security-authz/21_search_doc.yml b/plugin/src/test/resources/rest-api-spec/test/security-authz/21_search_doc.yml new file mode 100644 index 00000000000..b26b797bd29 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security-authz/21_search_doc.yml @@ -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 diff --git a/qa/smoke-test-ml-with-security/roles.yml b/qa/smoke-test-ml-with-security/roles.yml index 86be2e16eaf..c3fcd51c72d 100644 --- a/qa/smoke-test-ml-with-security/roles.yml +++ b/qa/smoke-test-ml-with-security/roles.yml @@ -15,3 +15,4 @@ minimal: - indices:data/read/search - indices:data/write/bulk - indices:data/write/index + - indices:data/write/delete