From 8985625ea52c421681f0e42637cb8201a208cc26 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 26 Oct 2017 15:34:58 +1100 Subject: [PATCH] [Security] BulkShardRequest may have multiple indices (elastic/x-pack-elasticsearch#2742) If a bulk update references aliases rather than concrete indices, it is possible that a single shard level request could have multiple distinct "index names", potentially including "date math". Those names will resolve to the same concrete index, but they might have different privileges. Original commit: elastic/x-pack-elasticsearch@34cfd11df8c3b16a59695c41b112f5791744f7ec --- .../security/authz/AuthorizationService.java | 49 ++- .../authz/IndicesAndAliasesResolver.java | 2 +- .../authz/AuthorizationServiceTests.java | 124 +++++-- .../authz}/10_index_doc.yml | 0 .../authz}/11_delete_doc.yml | 0 .../test/security/authz/12_index_alias.yml | 312 ++++++++++++++++++ .../test/security/authz/13_index_datemath.yml | 138 ++++++++ .../authz}/20_get_doc.yml | 0 .../authz}/21_search_doc.yml | 0 9 files changed, 577 insertions(+), 48 deletions(-) rename plugin/src/test/resources/rest-api-spec/test/{security-authz => security/authz}/10_index_doc.yml (100%) rename plugin/src/test/resources/rest-api-spec/test/{security-authz => security/authz}/11_delete_doc.yml (100%) create mode 100644 plugin/src/test/resources/rest-api-spec/test/security/authz/12_index_alias.yml create mode 100644 plugin/src/test/resources/rest-api-spec/test/security/authz/13_index_datemath.yml rename plugin/src/test/resources/rest-api-spec/test/{security-authz => security/authz}/20_get_doc.yml (100%) rename plugin/src/test/resources/rest-api-spec/test/{security-authz => security/authz}/21_search_doc.yml (100%) 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 0c59fa98643..deccd8f2ae8 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 @@ -38,6 +38,7 @@ 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.collect.Tuple; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; @@ -327,7 +328,7 @@ public class AuthorizationService extends AbstractComponent { assert request instanceof BulkShardRequest : "Action " + action + " requires " + BulkShardRequest.class + " but was " + request.getClass(); - authorizeBulkItems(authentication, action, (BulkShardRequest) request, permission, metaData, localIndices); + authorizeBulkItems(authentication, (BulkShardRequest) request, permission, metaData, localIndices, authorizedIndices); } grant(authentication, action, originalRequest); @@ -343,28 +344,48 @@ public class AuthorizationService extends AbstractComponent { * {@link DocWriteRequest.OpType types}, the number of distinct authorization checks that need to be performed is very small, but the * results must be cached, to avoid adding a high overhead to each bulk request. */ - private void authorizeBulkItems(Authentication authentication, String action, BulkShardRequest request, Role permission, - MetaData metaData, Set indices) { - if (indices.size() != 1) { - final String message = "Action " + action + " should operate on exactly 1 local index but was " + indices.size(); - assert false : message; - throw new IllegalStateException(message); - } - - final String index = indices.iterator().next(); - final Map actionAuthority = new HashMap<>(); + private void authorizeBulkItems(Authentication authentication, BulkShardRequest request, Role permission, + MetaData metaData, Set indices, AuthorizedIndices authorizedIndices) { + // Maps original-index -> expanded-index-name (expands date-math, but not aliases) + final Map resolvedIndexNames = new HashMap<>(); + // Maps (resolved-index , action) -> is-granted + final Map, Boolean> indexActionAuthority = new HashMap<>(); for (BulkItemRequest item : request.items()) { + String resolvedIndex = resolvedIndexNames.computeIfAbsent(item.index(), key -> { + final ResolvedIndices resolvedIndices = indicesAndAliasesResolver.resolveIndicesAndAliases(item.request(), metaData, + authorizedIndices); + if (resolvedIndices.getRemote().size() != 0) { + throw illegalArgument("Bulk item should not write to remote indices, but request writes to " + + String.join(",", resolvedIndices.getRemote())); + } + if (resolvedIndices.getLocal().size() != 1) { + throw illegalArgument("Bulk item should write to exactly 1 index, but request writes to " + + String.join(",", resolvedIndices.getLocal())); + } + final String resolved = resolvedIndices.getLocal().get(0); + if (indices.contains(resolved) == false) { + throw illegalArgument("Found bulk item that writes to index " + resolved + " but the request writes to " + indices); + } + return resolved; + }); final String itemAction = getAction(item); - final boolean granted = actionAuthority.computeIfAbsent(itemAction, key -> { - final IndicesAccessControl itemAccessControl = permission.authorize(itemAction, indices, metaData, fieldPermissionsCache); + final Tuple indexAndAction = new Tuple<>(resolvedIndex, itemAction); + final boolean granted = indexActionAuthority.computeIfAbsent(indexAndAction, key -> { + final IndicesAccessControl itemAccessControl = permission.authorize(itemAction, Collections.singleton(resolvedIndex), + metaData, fieldPermissionsCache); return itemAccessControl.isGranted(); }); if (granted == false) { - item.abort(index, denial(authentication, itemAction, request)); + item.abort(resolvedIndex, denial(authentication, itemAction, request)); } } } + private IllegalArgumentException illegalArgument(String message) { + assert false : message; + return new IllegalArgumentException(message); + } + private static String getAction(BulkItemRequest item) { final DocWriteRequest docWriteRequest = item.request(); switch (docWriteRequest.opType()) { diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java index 262fddc37f7..9bba9dfa503 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java @@ -102,7 +102,7 @@ public class IndicesAndAliasesResolver { return resolveIndicesAndAliases((IndicesRequest) request, metaData, authorizedIndices); } - private ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData metaData, + ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, MetaData metaData, AuthorizedIndices authorizedIndices) { boolean indicesReplacedWithNoIndices = false; final ResolvedIndices indices; 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 71aad327267..e42f1dc6814 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 @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authz; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -129,7 +130,10 @@ 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; +import org.joda.time.Instant; +import org.joda.time.format.DateTimeFormat; import org.junit.Before; +import org.mockito.Mockito; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationException; @@ -202,9 +206,10 @@ public class AuthorizationServiceTests extends ESTestCase { private void authorize(Authentication authentication, String action, TransportRequest request) { PlainActionFuture future = new PlainActionFuture(); AuthorizationUtils.AsyncAuthorizer authorizer = new AuthorizationUtils.AsyncAuthorizer(authentication, future, - (userRoles, runAsRoles) -> {authorizationService.authorize(authentication, action, request, userRoles, runAsRoles); - future.onResponse(null); - }); + (userRoles, runAsRoles) -> { + authorizationService.authorize(authentication, action, request, userRoles, runAsRoles); + future.onResponse(null); + }); authorizer.authorize(authorizationService); future.actionGet(); } @@ -332,8 +337,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testThatNonIndicesAndNonClusterActionIsDenied() { TransportRequest request = mock(TransportRequest.class); User user = new User("test user", "a_all"); - roleMap.put("a_all", new RoleDescriptor("a_role",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), "whatever", request), @@ -345,7 +350,7 @@ public class AuthorizationServiceTests extends ESTestCase { public void testThatRoleWithNoIndicesIsDenied() { TransportRequest request = new IndicesExistsRequest("a"); User user = new User("test user", "no_indices"); - roleMap.put("no_indices", new RoleDescriptor("a_role",null,null,null)); + roleMap.put("no_indices", new RoleDescriptor("a_role", null, null, null)); mockEmptyMetaData(); assertThrowsAuthorizationException( @@ -432,8 +437,8 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("b"); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", new RoleDescriptor("a_role",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), "indices:a", request), @@ -449,8 +454,8 @@ public class AuthorizationServiceTests extends ESTestCase { request.alias(new Alias("a2")); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", new RoleDescriptor("a_role",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), CreateIndexAction.NAME, request), @@ -466,8 +471,8 @@ public class AuthorizationServiceTests extends ESTestCase { request.alias(new Alias("a2")); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", new RoleDescriptor("a_all",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a", "a2").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_all", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a", "a2").privileges("all").build() }, null)); authorize(createAuthentication(user), CreateIndexAction.NAME, request); @@ -485,8 +490,8 @@ public class AuthorizationServiceTests extends ESTestCase { authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); - roleMap.put("a_all", new RoleDescriptor("a_all",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_all", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(anonymousUser), "indices:a", request), @@ -508,8 +513,8 @@ public class AuthorizationServiceTests extends ESTestCase { authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings)); - roleMap.put("a_all", new RoleDescriptor("a_all",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_all", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, () -> authorize(createAuthentication(anonymousUser), "indices:a", request)); @@ -525,8 +530,8 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("not-an-index-*").indicesOptions(options); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", new RoleDescriptor("a_all",null, - new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); + roleMap.put("a_all", new RoleDescriptor("a_all", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); final IndexNotFoundException nfe = expectThrows( IndexNotFoundException.class, @@ -565,9 +570,9 @@ public class AuthorizationServiceTests extends ESTestCase { public void testRunAsRequestRunningAsUnAllowedUser() { TransportRequest request = mock(TransportRequest.class); - User user = new User("run as me", new String[] {"doesn't exist"}, new User("test user", "can run as")); + User user = new User("run as me", new String[] { "doesn't exist" }, new User("test user", "can run as")); assertNotEquals(user.authenticatedUser(), user); - roleMap.put("can run as", new RoleDescriptor("can run as",null, + roleMap.put("can run as", new RoleDescriptor("can run as", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new String[] { "not the right user" })); @@ -580,9 +585,9 @@ public class AuthorizationServiceTests extends ESTestCase { public void testRunAsRequestWithRunAsUserWithoutPermission() { TransportRequest request = new GetIndexRequest().indices("a"); - User user = new User("run as me", new String[] {"b"}, new User("test user", "can run as")); + User user = new User("run as me", new String[] { "b" }, new User("test user", "can run as")); assertNotEquals(user.authenticatedUser(), user); - roleMap.put("can run as", new RoleDescriptor("can run as",null, + roleMap.put("can run as", new RoleDescriptor("can run as", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new String[] { "run as me" })); @@ -594,7 +599,7 @@ public class AuthorizationServiceTests extends ESTestCase { .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1).numberOfReplicas(0).build(), true) .build()); - roleMap.put("b", new RoleDescriptor("b",null, + roleMap.put("b", new RoleDescriptor("b", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("all").build() }, null)); } else { mockEmptyMetaData(); @@ -610,9 +615,9 @@ public class AuthorizationServiceTests extends ESTestCase { public void testRunAsRequestWithValidPermissions() { TransportRequest request = new GetIndexRequest().indices("b"); - User user = new User("run as me", new String[] {"b"}, new User("test user", new String[] { "can run as" })); + User user = new User("run as me", new String[] { "b" }, new User("test user", new String[] { "can run as" })); assertNotEquals(user.authenticatedUser(), user); - roleMap.put("can run as", new RoleDescriptor("can run as",null, + roleMap.put("can run as", new RoleDescriptor("can run as", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, new String[] { "run as me" })); ClusterState state = mock(ClusterState.class); @@ -622,7 +627,7 @@ public class AuthorizationServiceTests extends ESTestCase { .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1).numberOfReplicas(0).build(), true) .build()); - roleMap.put("b", new RoleDescriptor("b",null, + roleMap.put("b", new RoleDescriptor("b", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("all").build() }, null)); authorize(createAuthentication(user), "indices:a", request); @@ -633,7 +638,7 @@ public class AuthorizationServiceTests extends ESTestCase { public void testNonXPackUserCannotExecuteOperationAgainstSecurityIndex() { User user = new User("all_access_user", "all_access"); - roleMap.put("all_access", new RoleDescriptor("all access",new String[] { "all" }, + roleMap.put("all_access", new RoleDescriptor("all access", new String[] { "all" }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("*").privileges("all").build() }, null)); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); @@ -686,7 +691,7 @@ public class AuthorizationServiceTests extends ESTestCase { public void testGrantedNonXPackUserCanExecuteMonitoringOperationsAgainstSecurityIndex() { User user = new User("all_access_user", "all_access"); - roleMap.put("all_access", new RoleDescriptor("all access",new String[] { "all" }, + roleMap.put("all_access", new RoleDescriptor("all access", new String[] { "all" }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("*").privileges("all").build() }, null)); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); @@ -778,7 +783,7 @@ public class AuthorizationServiceTests extends ESTestCase { final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); - roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role",new String[] { "all" }, + roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role", new String[] { "all" }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); @@ -804,7 +809,7 @@ public class AuthorizationServiceTests extends ESTestCase { final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); - roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role",new String[] { "all" }, + roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role", new String[] { "all" }, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); PlainActionFuture rolesFuture = new PlainActionFuture<>(); @@ -895,6 +900,59 @@ public class AuthorizationServiceTests extends ESTestCase { () -> authorize(createAuthentication(userDenied), action, request), action, "userDenied"); } + public void testAuthorizationOfIndividualBulkItems() { + final String action = BulkAction.NAME + "[s]"; + final BulkItemRequest[] items = { + new BulkItemRequest(1, new DeleteRequest("concrete-index", "doc", "c1")), + new BulkItemRequest(2, new IndexRequest("concrete-index", "doc", "c2")), + new BulkItemRequest(3, new DeleteRequest("alias-1", "doc", "a1a")), + new BulkItemRequest(4, new IndexRequest("alias-1", "doc", "a1b")), + new BulkItemRequest(5, new DeleteRequest("alias-2", "doc", "a2a")), + new BulkItemRequest(6, new IndexRequest("alias-2", "doc", "a2b")) + }; + final ShardId shardId = new ShardId("concrete-index", UUID.randomUUID().toString(), 1); + final TransportRequest request = new BulkShardRequest(shardId, WriteRequest.RefreshPolicy.IMMEDIATE, items); + + User user = new User("user", "my-role"); + roleMap.put("my-role", new RoleDescriptor("my-role", null, new IndicesPrivileges[] { + IndicesPrivileges.builder().indices("concrete-index").privileges("all").build(), + IndicesPrivileges.builder().indices("alias-1").privileges("index").build(), + IndicesPrivileges.builder().indices("alias-2").privileges("delete").build() + }, null)); + + mockEmptyMetaData(); + authorize(createAuthentication(user), action, request); + + verify(auditTrail).accessDenied(user, DeleteAction.NAME, request); // alias-1 delete + verify(auditTrail).accessDenied(user, IndexAction.NAME, request); // alias-2 index + verify(auditTrail).accessGranted(user, action, request); // bulk request is allowed + verifyNoMoreInteractions(auditTrail); + } + + public void testAuthorizationOfIndividualBulkItemsWithDateMath() { + final String action = BulkAction.NAME + "[s]"; + final BulkItemRequest[] items = { + new BulkItemRequest(1, new IndexRequest("", "doc", "dy1")), + new BulkItemRequest(2, new DeleteRequest("", "doc", "dy2")), // resolves to same as above + new BulkItemRequest(3, new IndexRequest("", "doc", "dm1")), + new BulkItemRequest(4, new DeleteRequest("", "doc", "dm2")), // resolves to same as above + }; + final ShardId shardId = new ShardId("concrete-index", UUID.randomUUID().toString(), 1); + final TransportRequest request = new BulkShardRequest(shardId, WriteRequest.RefreshPolicy.IMMEDIATE, items); + + User user = new User("user", "my-role"); + roleMap.put("my-role", new RoleDescriptor("my-role", null, new IndicesPrivileges[] { + IndicesPrivileges.builder().indices("datemath-*").privileges("index").build() + }, null)); + + mockEmptyMetaData(); + authorize(createAuthentication(user), action, request); + + verify(auditTrail, Mockito.times(2)).accessDenied(user, DeleteAction.NAME, request); // both deletes should fail + verify(auditTrail).accessGranted(user, action, request); // bulk request is allowed + verifyNoMoreInteractions(auditTrail); + } + 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); @@ -946,9 +1004,9 @@ public class AuthorizationServiceTests extends ESTestCase { assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); if (request instanceof ChangePasswordRequest) { - ((ChangePasswordRequest)request).username("joe"); + ((ChangePasswordRequest) request).username("joe"); } else { - ((AuthenticateRequest)request).username("joe"); + ((AuthenticateRequest) request).username("joe"); } assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication)); } @@ -1037,7 +1095,7 @@ public class AuthorizationServiceTests extends ESTestCase { } private static Tuple randomCompositeRequest() { - switch(randomIntBetween(0, 7)) { + switch (randomIntBetween(0, 7)) { case 0: return Tuple.tuple(MultiGetAction.NAME, new MultiGetRequest().add("index", "type", "id")); case 1: 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 similarity index 100% rename from plugin/src/test/resources/rest-api-spec/test/security-authz/10_index_doc.yml rename to plugin/src/test/resources/rest-api-spec/test/security/authz/10_index_doc.yml 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 similarity index 100% rename from plugin/src/test/resources/rest-api-spec/test/security-authz/11_delete_doc.yml rename to plugin/src/test/resources/rest-api-spec/test/security/authz/11_delete_doc.yml diff --git a/plugin/src/test/resources/rest-api-spec/test/security/authz/12_index_alias.yml b/plugin/src/test/resources/rest-api-spec/test/security/authz/12_index_alias.yml new file mode 100644 index 00000000000..44d91d691e1 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security/authz/12_index_alias.yml @@ -0,0 +1,312 @@ +--- +setup: + - skip: + features: headers + + - do: + cluster.health: + wait_for_status: yellow + + - do: + xpack.security.put_role: + name: "mixed_role" + body: > + { + "indices": [ + { "names": ["can_read_1", "can_read_2" ], "privileges": ["read"] }, + { "names": ["can_write_1", "can_write_2", "can_write_3" ], "privileges": ["read", "write"] } + ] + } + + - 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: read_index + body: + settings: + index: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + doc: + properties: + name: + type: "keyword" + + - do: + indices.create: + index: write_index_1 + body: + settings: + index: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + doc: + properties: + name: + type: "keyword" + + - do: + indices.create: + index: write_index_2 + body: + settings: + index: + number_of_shards: 1 + number_of_replicas: 0 + mappings: + doc: + properties: + name: + type: "keyword" + + - do: + indices.put_alias: + index: read_index + name: can_read_1 + + - do: + indices.put_alias: + index: read_index + name: can_read_2 + + - do: + indices.put_alias: + index: write_index_1 + name: can_write_1 + + - do: + indices.put_alias: + index: write_index_1 + name: can_write_2 + + - do: + indices.put_alias: + index: write_index_2 + name: can_write_3 + +--- +teardown: + - do: + xpack.security.delete_user: + username: "test_user" + ignore: 404 + + - do: + xpack.security.delete_role: + name: "mixed_role" + ignore: 404 + + - do: + indices.delete_alias: + index: "read_index" + name: [ "can_read_1", "can_read_2" ] + ignore: 404 + + - do: + indices.delete_alias: + index: "write_index_1" + name: [ "can_write_1", "can_write_2" ] + ignore: 404 + + - do: + indices.delete: + index: [ "write_index_1", "read_index" ] + ignore: 404 + +--- +"Test indexing documents into an alias, when permitted": + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + create: + id: 1 + index: can_write_1 + type: doc + body: > + { + "name" : "doc1" + } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + create: + id: 2 + index: can_write_2 + type: doc + body: > + { + "name" : "doc2" + } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_write_1", "_type": "doc", "_id": "3"}}' + - '{"name": "doc3"}' + - '{"index": {"_index": "can_write_1", "_type": "doc", "_id": "4"}}' + - '{"name": "doc4"}' + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_write_1", "_type": "doc", "_id": "5"}}' + - '{"name": "doc5"}' + - '{"index": {"_index": "can_write_2", "_type": "doc", "_id": "6"}}' + - '{"name": "doc6"}' + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_write_1", "_type": "doc", "_id": "7"}}' + - '{"name": "doc7"}' + - '{"index": {"_index": "can_write_2", "_type": "doc", "_id": "8"}}' + - '{"name": "doc8"}' + - '{"index": {"_index": "can_write_3", "_type": "doc", "_id": "9"}}' + - '{"name": "doc9"}' + + - do: # superuser + search: + index: write_index_1 + - match: { hits.total: 8 } + + - do: # superuser + search: + index: write_index_2 + - match: { hits.total: 1 } + +--- +"Test indexing documents into an alias, when forbidden": + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + create: + refresh: true + id: 7 + index: can_read_1 + type: doc + body: > + { + "name" : "doc7" + } + + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + create: + refresh: true + id: 8 + index: can_read_2 + type: doc + body: > + { + "name" : "doc8" + } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_read_1", "_type": "doc", "_id": "9"}}' + - '{"name": "doc9"}' + - '{"index": {"_index": "can_read_1", "_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": "can_read_1", "_type": "doc", "_id": "11"}}' + - '{"name": "doc11"}' + - '{"index": {"_index": "can_read_2", "_type": "doc", "_id": "12"}}' + - '{"name": "doc12"}' + - 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: # superuser + search: + index: read_index + - match: { hits.total: 0 } + +--- +"Test bulk indexing into an alias when only some are allowed": + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_read_1", "_type": "doc", "_id": "13"}}' + - '{"name": "doc13"}' + - '{"index": {"_index": "can_write_1", "_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: write_index_1 + body: { "query": { "term": { "_id": "14" } } } + - match: { hits.total: 1 } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + refresh: true + body: + - '{"index": {"_index": "can_read_1", "_type": "doc", "_id": "15"}}' + - '{"name": "doc15"}' + - '{"index": {"_index": "can_write_1", "_type": "doc", "_id": "16"}}' + - '{"name": "doc16"}' + - '{"index": {"_index": "can_read_2", "_type": "doc", "_id": "17"}}' + - '{"name": "doc17"}' + - '{"index": {"_index": "can_write_2", "_type": "doc", "_id": "18"}}' + - '{"name": "doc18"}' + - '{"index": {"_index": "can_write_3", "_type": "doc", "_id": "19"}}' + - '{"name": "doc19"}' + - match: { errors: true } + - match: { items.0.index.status: 403 } + - match: { items.0.index.error.type: "security_exception" } + - match: { items.1.index.status: 201 } + - match: { items.2.index.status: 403 } + - match: { items.2.index.error.type: "security_exception" } + - match: { items.3.index.status: 201 } + - match: { items.4.index.status: 201 } + + - do: # superuser + search: + index: write_index_1 + body: { "query": { "terms": { "_id": [ "16", "18" ] } } } + - match: { hits.total: 2 } + - do: # superuser + search: + index: write_index_2 + body: { "query": { "terms": { "_id": [ "19" ] } } } + - match: { hits.total: 1 } diff --git a/plugin/src/test/resources/rest-api-spec/test/security/authz/13_index_datemath.yml b/plugin/src/test/resources/rest-api-spec/test/security/authz/13_index_datemath.yml new file mode 100644 index 00000000000..7f3a20a6074 --- /dev/null +++ b/plugin/src/test/resources/rest-api-spec/test/security/authz/13_index_datemath.yml @@ -0,0 +1,138 @@ +--- +setup: + - skip: + features: headers + + - do: + cluster.health: + wait_for_status: yellow + + - do: + xpack.security.put_role: + name: "mixed_role" + body: > + { + "indices": [ + { "names": ["read-*" ], "privileges": ["read"] }, + { "names": ["write-*" ], "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" + } + +--- +teardown: + - do: + xpack.security.delete_user: + username: "test_user" + ignore: 404 + + - do: + xpack.security.delete_role: + name: "mixed_role" + ignore: 404 + +--- +"Test indexing documents with datemath, when permitted": + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + index: + id: 1 + index: "" + type: doc + body: > + { + "name" : "doc1" + } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + body: + - '{"index": {"_index": "", "_type": "doc", "_id": "2"}}' + - '{"name": "doc2"}' + - '{"index": {"_index": "", "_type": "doc", "_id": "3"}}' + - '{"name": "doc3"}' + - match: { errors: false } + - match: { items.0.index.status: 201 } + - match: { items.1.index.status: 201 } + + - do: # superuser + indices.refresh: + index: "_all" + + - do: # superuser + search: + index: "write-*" + - match: { hits.total: 3 } + +--- +"Test indexing documents with datemath, when forbidden": + - do: + catch: forbidden + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + index: + id: 4 + index: "" + type: doc + body: > + { + "name" : "doc4" + } + + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + body: + - '{"index": {"_index": "", "_type": "doc", "_id": "5"}}' + - '{"name": "doc5"}' + - '{"index": {"_index": "", "_type": "doc", "_id": "6"}}' + - '{"name": "doc6"}' + - 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: # superuser + indices.refresh: + index: "_all" + + - do: # superuser + search: + index: "read-*" + - match: { hits.total: 0 } + +--- +"Test bulk indexing with datemath when only some are allowed": + - do: + headers: { Authorization: "Basic dGVzdF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # test_user + bulk: + body: + - '{"index": {"_index": "", "_type": "doc", "_id": "7"}}' + - '{"name": "doc7"}' + - '{"index": {"_index": "", "_type": "doc", "_id": "8"}}' + - '{"name": "doc8"}' + - 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 + indices.refresh: + index: "_all" + + - do: # superuser + search: + index: write-* + body: { "query": { "term": { "_id": "8" } } } + - match: { hits.total: 1 } 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 similarity index 100% rename from plugin/src/test/resources/rest-api-spec/test/security-authz/20_get_doc.yml rename to plugin/src/test/resources/rest-api-spec/test/security/authz/20_get_doc.yml 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 similarity index 100% rename from plugin/src/test/resources/rest-api-spec/test/security-authz/21_search_doc.yml rename to plugin/src/test/resources/rest-api-spec/test/security/authz/21_search_doc.yml