From 4bb6e856f35ceb47d42b12f757b8b17326ca565d Mon Sep 17 00:00:00 2001 From: javanna Date: Wed, 5 Oct 2016 11:58:27 +0200 Subject: [PATCH] Authorize composite actions based on their action name only, subrequests and their indices will be later authorized individually Eagerly authorizing CompositeIndicesRequests allowed the security plugin to fail fast up until now, but it makes it very hard to reason about each specific item in a multi items request. Either all items fail, or none do. We would rather want to adopt a similar behaviour to es core, where individual items fail without affecting other items that are part of the same request. We can rely on the fact that es core always authorizes both main action and every subaction too, and skip authorization for the main action. By subaction we mean either all sub search requests in msearch, as well as each shard level get in mget or shard level bulk request for bulk. BulkRequestInterceptor was converted to intercept BulkShardRequests rather than BulkRequest as that is where bulk is authorized after this change. Split IndicesAndAliasesResolverIntegrationTests into ReadActionsTests and WriteActionsTests as they require different set of permissions, lots of tests added. Explicitly listing the composite actions makes sure that the actions that can bypass security are known, somebody adding a similar action must to add it to the list, so we know it doesn't happen by mistake. At this point the CompositeIndicesRequest can be used as a marker interface only (it is not really needed but can be used to verify that composite actions use a request that implements such interface). Given that we don't authorize composite actions based on their indices anymore, but only their sub-requests which implement IndicesRequest, printing out the indices names in the audit log for requests like bulk and msearch is confusing. Removed support for that. Authorize composite indices actions based on their name only, their indices will be authorized at the sub-request/shard level Rather than simply granting bulk, mget, msearch etc. and relying on authorization at the sub-request/shard level, we check that the current user can at least execute the action. This justifies the grant line that gets written in the audit log, the action is potentially possible without looking at the indices. Each specific item will fail or succeed later and will yield its own specific audit log entry. Original commit: elastic/x-pack-elasticsearch@4570caf0197ea7f83f88a3a98298bac17415aa5c --- .../security/action/SecurityActionModule.java | 4 +- ....java => BulkShardRequestInterceptor.java} | 41 +- .../xpack/security/audit/AuditUtil.java | 12 - .../security/authz/AuthorizationService.java | 35 +- .../DefaultIndicesAndAliasesResolver.java | 14 +- .../authz/permission/ClusterPermission.java | 14 +- .../authz/permission/GlobalPermission.java | 2 +- .../authz/permission/IndicesPermission.java | 46 +- .../AbstractPrivilegeTestCase.java | 20 +- .../DocumentLevelSecurityTests.java | 34 +- .../integration/FieldLevelSecurityTests.java | 28 +- .../integration/IndexPrivilegeTests.java | 124 ++++- .../test/SecurityIntegTestCase.java | 41 +- .../xpack/security/audit/AuditUtilTests.java | 44 -- .../authz/AuthorizationServiceTests.java | 191 ++++++-- .../security/authz/ReadActionsTests.java | 422 ++++++++++++++++++ .../security/authz/WriteActionsTests.java | 164 +++++++ .../accesscontrol/IndicesPermissionTests.java | 6 + .../DefaultIndicesResolverTests.java | 177 +------- ...cesAndAliasesResolverIntegrationTests.java | 288 ------------ .../authz/permission/SuperuserRoleTests.java | 2 + .../xpack/security/ReindexWithSecurityIT.java | 31 +- .../rest-api-spec/test/10_reindex.yaml | 5 +- .../test/15_reindex_from_remote.yaml | 5 +- 24 files changed, 1066 insertions(+), 684 deletions(-) rename elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/{BulkRequestInterceptor.java => BulkShardRequestInterceptor.java} (55%) create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/WriteActionsTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/IndicesAndAliasesResolverIntegrationTests.java diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/SecurityActionModule.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/SecurityActionModule.java index 5642769f2a0..cdaf31ca3e1 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/SecurityActionModule.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/SecurityActionModule.java @@ -9,7 +9,7 @@ import org.elasticsearch.common.inject.multibindings.Multibinder; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter; -import org.elasticsearch.xpack.security.action.interceptor.BulkRequestInterceptor; +import org.elasticsearch.xpack.security.action.interceptor.BulkShardRequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.FieldStatsRequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.SearchRequestInterceptor; @@ -32,7 +32,7 @@ public class SecurityActionModule extends AbstractSecurityModule.Node { if (XPackSettings.DLS_FLS_ENABLED.get(settings)) { multibinder.addBinding().to(SearchRequestInterceptor.class); multibinder.addBinding().to(UpdateRequestInterceptor.class); - multibinder.addBinding().to(BulkRequestInterceptor.class); + multibinder.addBinding().to(BulkShardRequestInterceptor.class); multibinder.addBinding().to(FieldStatsRequestInterceptor.class); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkShardRequestInterceptor.java similarity index 55% rename from elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java rename to elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkShardRequestInterceptor.java index 4e7b335a74d..51419772fdd 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkRequestInterceptor.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/interceptor/BulkShardRequestInterceptor.java @@ -6,8 +6,8 @@ package org.elasticsearch.xpack.security.action.interceptor; import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.IndicesRequest; -import org.elasticsearch.action.bulk.BulkRequest; +import org.elasticsearch.action.bulk.BulkItemRequest; +import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; @@ -15,53 +15,52 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.authz.AuthorizationService; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; +import org.elasticsearch.xpack.security.user.User; /** * Similar to {@link UpdateRequestInterceptor}, but checks if there are update requests embedded in a bulk request. */ -public class BulkRequestInterceptor extends AbstractComponent implements RequestInterceptor { +public class BulkShardRequestInterceptor extends AbstractComponent implements RequestInterceptor { private final ThreadContext threadContext; private final XPackLicenseState licenseState; @Inject - public BulkRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { + public BulkShardRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { super(settings); this.threadContext = threadPool.getThreadContext(); this.licenseState = licenseState; } - public void intercept(BulkRequest request, User user) { + public void intercept(BulkShardRequest request, User user) { if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { return; } IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY); - for (IndicesRequest indicesRequest : request.subRequests()) { - for (String index : indicesRequest.indices()) { - IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index); - if (indexAccessControl != null) { - boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); - boolean dls = indexAccessControl.getQueries() != null; - if (fls || dls) { - if (indicesRequest instanceof UpdateRequest) { - throw new ElasticsearchSecurityException("Can't execute a bulk request with update requests embedded if " + - "field or document level security is enabled", RestStatus.BAD_REQUEST); - } + for (BulkItemRequest bulkItemRequest : request.items()) { + IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(bulkItemRequest.index()); + if (indexAccessControl != null) { + boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); + boolean dls = indexAccessControl.getQueries() != null; + if (fls || dls) { + if (bulkItemRequest.request() instanceof UpdateRequest) { + throw new ElasticsearchSecurityException("Can't execute a bulk request with update requests embedded if " + + "field or document level security is enabled", RestStatus.BAD_REQUEST); } } - logger.trace("intercepted bulk request for index [{}] without any update requests, continuing execution", index); } + logger.trace("intercepted bulk request for index [{}] without any update requests, continuing execution", + bulkItemRequest.index()); } } @Override public boolean supports(TransportRequest request) { - return request instanceof BulkRequest; + return request instanceof BulkShardRequest; } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/AuditUtil.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/AuditUtil.java index a6131cf59b8..01d924b1b61 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/AuditUtil.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/audit/AuditUtil.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.audit; -import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.rest.RestRequest; @@ -13,7 +12,6 @@ import org.elasticsearch.transport.TransportMessage; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -33,16 +31,6 @@ public class AuditUtil { public static Set indices(TransportMessage message) { if (message instanceof IndicesRequest) { return arrayToSetOrNull(((IndicesRequest) message).indices()); - } else if (message instanceof CompositeIndicesRequest) { - Set indices = new HashSet<>(); - for (IndicesRequest indicesRequest : ((CompositeIndicesRequest)message).subRequests()) { - if (indicesRequest.indices() != null) { - Collections.addAll(indices, indicesRequest.indices()); - } - } - if (indices.isEmpty() == false) { - return indices; - } } return null; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index babc8869608..2ad12e9205e 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -11,10 +11,14 @@ import org.elasticsearch.action.IndicesRequest; 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.get.MultiGetAction; import org.elasticsearch.action.search.ClearScrollAction; +import org.elasticsearch.action.search.MultiSearchAction; 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.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; @@ -227,14 +231,27 @@ public class AuthorizationService extends AbstractComponent { throw denial(authentication, action, request); } + //composite actions are explicitly listed and will be authorized at the sub-request / shard level + if (isCompositeAction(action)) { + if (request instanceof CompositeIndicesRequest == false) { + throw new IllegalStateException("Composite actions must implement " + CompositeIndicesRequest.class.getSimpleName() + + ", " + request.getClass().getSimpleName() + " doesn't"); + } + //we check if the user can execute the action, without looking at indices, whici will be authorized at the shard level + if (permission.indices().check(action)) { + grant(authentication, action, request); + return; + } + throw denial(authentication, action, request); + } + // some APIs are indices requests that are not actually associated with indices. For example, // search scroll request, is categorized under the indices context, but doesn't hold indices names // (in this case, the security check on the indices was done on the search request that initialized // the scroll... and we rely on the signed scroll id to provide security over this request). // so we only check indices if indeed the request is an actual IndicesRequest, if it's not, // we just grant it if it's a scroll, deny otherwise - if (request instanceof IndicesRequest == false && request instanceof CompositeIndicesRequest == false - && request instanceof IndicesAliasesRequest == false) { + if (request instanceof IndicesRequest == false && request instanceof IndicesAliasesRequest == false) { if (isScrollRelatedAction(action)) { //note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any //indices permission as it's categorized under cluster. This is why the scroll check is performed @@ -254,8 +271,8 @@ public class AuthorizationService extends AbstractComponent { Set indexNames = resolveIndices(authentication, action, request, clusterState); assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty"; - //security plugin is the only responsible for the presence of "-*", as wildcards just got resolved. - //'-*' matches no indices, hence we can simply let it go through, it will yield an empty response. + //all wildcard expressions have been resolved and only the security plugin could have set '-*' here. + //'-*' matches no indices so we allow the request to go through, which will yield an empty response if (indexNames.size() == 1 && indexNames.contains(DefaultIndicesAndAliasesResolver.NO_INDEX)) { setIndicesAccessControl(IndicesAccessControl.ALLOW_NO_INDICES); grant(authentication, action, request); @@ -348,6 +365,16 @@ public class AuthorizationService extends AbstractComponent { throw denial(authentication, action, request); } + private static boolean isCompositeAction(String action) { + return action.equals(BulkAction.NAME) || + action.equals(MultiGetAction.NAME) || + action.equals(MultiTermVectorsAction.NAME) || + action.equals(MultiSearchAction.NAME) || + action.equals("indices:data/read/mpercolate") || + action.equals("indices:data/read/msearch/template") || + action.equals("indices:data/write/reindex"); + } + private static boolean isScrollRelatedAction(String action) { return action.equals(SearchScrollAction.NAME) || action.equals(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME) || diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java index 2924f2295c0..cd9ebd13746 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesAndAliasesResolver.java @@ -6,7 +6,6 @@ package org.elasticsearch.xpack.security.authz.indicesresolver; import org.elasticsearch.action.AliasesRequest; -import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; @@ -62,21 +61,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv return indices; } - boolean isIndicesRequest = request instanceof CompositeIndicesRequest || request instanceof IndicesRequest; // if for some reason we are missing an action... just for safety we'll reject - if (isIndicesRequest == false) { + if (request instanceof IndicesRequest == false) { throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be."); } - - if (request instanceof CompositeIndicesRequest) { - Set indices = new HashSet<>(); - CompositeIndicesRequest compositeIndicesRequest = (CompositeIndicesRequest) request; - for (IndicesRequest indicesRequest : compositeIndicesRequest.subRequests()) { - indices.addAll(resolveIndicesAndAliases(user, action, indicesRequest, metaData)); - } - return indices; - } - return resolveIndicesAndAliases(user, action, (IndicesRequest) request, metaData); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java index 9cfb0d75387..9f1fc321209 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java @@ -5,11 +5,12 @@ */ package org.elasticsearch.xpack.security.authz.permission; +import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.transport.TransportRequest; import java.util.List; +import java.util.Objects; import java.util.function.Predicate; /** @@ -19,7 +20,7 @@ public interface ClusterPermission extends Permission { boolean check(String action, TransportRequest request, Authentication authentication); - public static class Core implements ClusterPermission { + class Core implements ClusterPermission { public static final Core NONE = new Core(ClusterPrivilege.NONE) { @Override @@ -56,11 +57,11 @@ public interface ClusterPermission extends Permission { } } - static class Globals implements ClusterPermission { + class Globals implements ClusterPermission { private final List globals; - public Globals(List globals) { + Globals(List globals) { this.globals = globals; } @@ -70,9 +71,8 @@ public interface ClusterPermission extends Permission { return false; } for (GlobalPermission global : globals) { - if (global == null || global.cluster() == null) { - throw new RuntimeException(); - } + Objects.requireNonNull(global, "global must not be null"); + Objects.requireNonNull(global.indices(), "global.indices() must not be null"); if (global.cluster().check(action, request, authentication)) { return true; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java index 450d9ffc46e..5c0add3f370 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java @@ -72,7 +72,7 @@ public class GlobalPermission implements Permission { public static class Compound extends GlobalPermission { - public Compound(List globals) { + Compound(List globals) { super(new ClusterPermission.Globals(globals), new IndicesPermission.Globals(globals), new RunAsPermission.Globals(globals)); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java index 0a29bc6f7b1..daa3dd7b964 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java @@ -40,9 +40,20 @@ import static java.util.Collections.unmodifiableSet; */ public interface IndicesPermission extends Permission, Iterable { + /** + * Authorizes the provided action against the provided indices, given the current cluster metadata + */ Map authorize(String action, Set requestedIndicesOrAliases, MetaData metaData); - public static class Core implements IndicesPermission { + /** + * Checks if the permission matches the provided action, without looking at indices. + * To be used in very specific cases where indices actions need to be authorized regardless of their indices. + * The usecase for this is composite actions that are initially only authorized based on the action name (indices are not + * checked on the coordinating node), and properly authorized later at the shard level checking their indices as well. + */ + boolean check(String action); + + class Core implements IndicesPermission { public static final Core NONE = new Core() { @Override @@ -101,6 +112,16 @@ public interface IndicesPermission extends Permission, Iterable authorize(String action, Set requestedIndicesOrAliases, MetaData metaData) { @@ -203,6 +224,21 @@ public interface IndicesPermission extends Permission, Iterable authorize(String action, Set requestedIndicesOrAliases, MetaData metaData) { @@ -321,13 +357,13 @@ public interface IndicesPermission extends Permission, Iterable params) throws IOException { - try { - getRestClient().performRequest(method, uri, params, entityOrNull(body), - new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, - UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray())))); - fail("request should have failed"); - } catch(ResponseException e) { - StatusLine statusLine = e.getResponse().getStatusLine(); - String message = String.format(Locale.ROOT, "%s %s body %s: Expected 403, got %s %s with body %s", method, uri, body, - statusLine.getStatusCode(), statusLine.getReasonPhrase(), EntityUtils.toString(e.getResponse().getEntity())); - assertThat(message, statusLine.getStatusCode(), is(403)); - } + ResponseException responseException = expectThrows(ResponseException.class, + () -> getRestClient().performRequest(method, uri, params, entityOrNull(body), + new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, + UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray()))))); + StatusLine statusLine = responseException.getResponse().getStatusLine(); + String message = String.format(Locale.ROOT, "%s %s body %s: Expected 403, got %s %s with body %s", method, uri, body, + statusLine.getStatusCode(), statusLine.getReasonPhrase(), + EntityUtils.toString(responseException.getResponse().getEntity())); + assertThat(message, statusLine.getStatusCode(), is(403)); } private static HttpEntity entityOrNull(String body) { diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java b/elasticsearch/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java index e7155086264..c36177995bc 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/DocumentLevelSecurityTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.integration; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.search.SearchResponse; @@ -25,10 +27,10 @@ import org.elasticsearch.search.aggregations.bucket.children.Children; import org.elasticsearch.search.aggregations.bucket.global.Global; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.sort.SortOrder; +import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.SecuredString; -import org.elasticsearch.test.SecurityIntegTestCase; import java.util.Collections; @@ -37,13 +39,14 @@ import static org.elasticsearch.index.query.QueryBuilders.hasChildQuery; import static org.elasticsearch.index.query.QueryBuilders.hasParentQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; -import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; -import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; +import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; +import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; public class DocumentLevelSecurityTests extends SecurityIntegTestCase { @@ -724,17 +727,20 @@ public class DocumentLevelSecurityTests extends SecurityIntegTestCase { assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value2")); // With document level security enabled the update in bulk is not allowed: - try { - client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) - .prepareBulk() - .add(new UpdateRequest("test", "type", "1").doc("field1", "value3")) - .get(); - fail("failed, because bulk request with updates shouldn't be allowed if field or document level security is enabled"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST)); - assertThat(e.getMessage(), - equalTo("Can't execute a bulk request with update requests embedded if field or document level security is enabled")); - } + BulkResponse bulkResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue + ("user1", USERS_PASSWD))) + .prepareBulk() + .add(new UpdateRequest("test", "type", "1").doc("field1", "value3")) + .get(); + assertEquals(1, bulkResponse.getItems().length); + BulkItemResponse bulkItem = bulkResponse.getItems()[0]; + assertTrue(bulkItem.isFailed()); + assertThat(bulkItem.getFailure().getCause(), instanceOf(ElasticsearchSecurityException.class)); + ElasticsearchSecurityException securityException = (ElasticsearchSecurityException) bulkItem.getFailure().getCause(); + assertThat(securityException.status(), equalTo(RestStatus.BAD_REQUEST)); + assertThat(securityException.getMessage(), + equalTo("Can't execute a bulk request with update requests embedded if field or document level security is enabled")); + assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value2")); client().prepareBulk() diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java b/elasticsearch/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java index 42b74e8f875..3a743325167 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/FieldLevelSecurityTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.integration; import org.apache.lucene.search.join.ScoreMode; import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.bulk.BulkItemResponse; +import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.fieldstats.FieldStatsResponse; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.MultiGetResponse; @@ -43,6 +45,7 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcke import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -1178,17 +1181,20 @@ public class FieldLevelSecurityTests extends SecurityIntegTestCase { assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value2")); // With field level security enabled the update in bulk is not allowed: - try { - client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) - .prepareBulk() - .add(new UpdateRequest("test", "type", "1").doc("field2", "value3")) - .get(); - fail("failed, because bulk request with updates shouldn't be allowed if field level security is enabled"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST)); - assertThat(e.getMessage(), - equalTo("Can't execute a bulk request with update requests embedded if field or document level security is enabled")); - } + BulkResponse bulkResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue + ("user1", USERS_PASSWD))) + .prepareBulk() + .add(new UpdateRequest("test", "type", "1").doc("field2", "value3")) + .get(); + assertEquals(1, bulkResponse.getItems().length); + BulkItemResponse bulkItem = bulkResponse.getItems()[0]; + assertTrue(bulkItem.isFailed()); + assertThat(bulkItem.getFailure().getCause(), instanceOf(ElasticsearchSecurityException.class)); + ElasticsearchSecurityException securityException = (ElasticsearchSecurityException) bulkItem.getFailure().getCause(); + assertThat(securityException.status(), equalTo(RestStatus.BAD_REQUEST)); + assertThat(securityException.getMessage(), + equalTo("Can't execute a bulk request with update requests embedded if field or document level security is enabled")); + assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value2")); client().prepareBulk() diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java b/elasticsearch/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java index ffd1aa21e20..ca3558dc5a5 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/IndexPrivilegeTests.java @@ -34,7 +34,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { private String jsonDoc = "{ \"name\" : \"elasticsearch\", \"body\": \"foo bar\" }"; - public static final String ROLES = + private static final String ROLES = "all_cluster_role:\n" + " cluster: [ all ]\n" + "all_indices_role:\n" + @@ -95,7 +95,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { " privileges: [ index ]\n" + "\n"; - public static final String USERS = + private static final String USERS = "admin:" + USERS_PASSWD_HASHED + "\n" + "u1:" + USERS_PASSWD_HASHED + "\n" + "u2:" + USERS_PASSWD_HASHED + "\n" + @@ -111,7 +111,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { "u13:" + USERS_PASSWD_HASHED + "\n" + "u14:" + USERS_PASSWD_HASHED + "\n"; - public static final String USERS_ROLES = + private static final String USERS_ROLES = "all_indices_role:admin,u8\n" + "all_cluster_role:admin\n" + "all_a_role:u1,u2,u6\n" + @@ -179,11 +179,22 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsAllowed("admin", "PUT", "/abc/foo/1", jsonDoc, params); } + private static String randomIndex() { + return randomFrom("a", "b", "c", "abc"); + } + public void testUserU1() throws Exception { // u1 has all_a_role and read_a_role assertUserIsAllowed("u1", "all", "a"); assertUserIsDenied("u1", "all", "b"); assertUserIsDenied("u1", "all", "c"); + assertAccessIsAllowed("u1", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u1", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u1", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u1", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU2() throws Exception { @@ -194,6 +205,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsDenied("u2", "monitor", "b"); assertUserIsDenied("u2", "create_index", "b"); assertUserIsDenied("u2", "all", "c"); + assertAccessIsAllowed("u2", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u2", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u2", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u2", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU3() throws Exception { @@ -201,6 +219,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u3", "all", "a"); assertUserIsAllowed("u3", "all", "b"); assertUserIsDenied("u3", "all", "c"); + assertAccessIsAllowed("u3", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u3", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u3", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u3", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU4() throws Exception { @@ -217,6 +242,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u4", "create_index", "an_index"); assertUserIsAllowed("u4", "manage", "an_index"); + + assertAccessIsAllowed("u4", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u4", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u4", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u4", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU5() throws Exception { @@ -228,6 +261,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u5", "read", "b"); assertUserIsDenied("u5", "manage", "b"); assertUserIsDenied("u5", "write", "b"); + + assertAccessIsAllowed("u5", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u5", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u5", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u5", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU6() throws Exception { @@ -237,6 +278,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsDenied("u6", "manage", "b"); assertUserIsDenied("u6", "write", "b"); assertUserIsDenied("u6", "all", "c"); + assertAccessIsAllowed("u6", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u6", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u6", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u6", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU7() throws Exception { @@ -244,6 +292,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsDenied("u7", "all", "a"); assertUserIsDenied("u7", "all", "b"); assertUserIsDenied("u7", "all", "c"); + assertAccessIsDenied("u7", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsDenied("u7", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u7", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsDenied("u7", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU8() throws Exception { @@ -251,6 +306,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u8", "all", "a"); assertUserIsAllowed("u8", "all", "b"); assertUserIsAllowed("u8", "all", "c"); + assertAccessIsAllowed("u8", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u8", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u8", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u8", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU9() throws Exception { @@ -261,6 +323,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsDenied("u9", "manage", "b"); assertUserIsDenied("u9", "write", "b"); assertUserIsDenied("u9", "all", "c"); + assertAccessIsAllowed("u9", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u9", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u9", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u9", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU11() throws Exception { @@ -276,6 +345,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u11", "create_index", "c"); assertUserIsDenied("u11", "data_access", "c"); assertUserIsDenied("u11", "monitor", "c"); + + assertAccessIsDenied("u11", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsDenied("u11", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u11", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsDenied("u11", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU12() throws Exception { @@ -286,6 +363,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u12", "data_access", "b"); assertUserIsDenied("u12", "manage", "c"); assertUserIsAllowed("u12", "data_access", "c"); + assertAccessIsAllowed("u12", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u12", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsAllowed("u12", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u12", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU13() throws Exception { @@ -300,6 +384,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsAllowed("u13", "read", "b"); assertUserIsDenied("u13", "all", "c"); + + assertAccessIsAllowed("u13", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u13", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u13", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u13", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testUserU14() throws Exception { @@ -314,6 +406,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertUserIsDenied("u14", "data_access", "b"); assertUserIsDenied("u14", "all", "c"); + + assertAccessIsAllowed("u14", + "GET", "/" + randomIndex() + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); + assertAccessIsAllowed("u14", "POST", "/" + randomIndex() + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); + assertAccessIsDenied("u14", "PUT", + "/" + randomIndex() + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); + assertAccessIsAllowed("u14", + "GET", "/" + randomIndex() + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); } public void testThatUnknownUserIsRejectedProperly() throws Exception { @@ -427,10 +527,6 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsAllowed(user, "GET", "/" + index + "/foo/1/_termvector"); assertAccessIsAllowed(user, "GET", "/" + index + "/_suggest", "{ \"sgs\" : { \"text\" : \"foo\", \"term\" : { \"field\" : \"body\" } } }"); - assertAccessIsAllowed(user, "GET", - "/" + index + "/foo/_mget", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); - assertAccessIsAllowed(user, "GET", - "/" + index + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); assertUserIsAllowed(user, "search", index); } else { assertAccessIsDenied(user, "GET", "/" + index + "/_count"); @@ -439,10 +535,6 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsDenied(user, "GET", "/" + index + "/foo/1/_termvector"); assertAccessIsDenied(user, "GET", "/" + index + "/_suggest", "{ \"sgs\" : { \"text\" : \"foo\", \"term\" : { \"field\" : \"body\" } } }"); - assertAccessIsDenied(user, - "GET", "/" + index + "/foo/_mget", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); - assertAccessIsDenied(user, - "GET", "/" + index + "/foo/_mtermvectors", "{ \"docs\" : [ { \"_id\": \"1\" }, { \"_id\": \"2\" } ] }"); assertUserIsDenied(user, "search", index); } break; @@ -452,24 +544,18 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { assertAccessIsAllowed(user, "GET", "/" + index + "/_search"); assertAccessIsAllowed(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " + "\"term\" : { \"field\" : \"name\" } } }"); - assertAccessIsAllowed(user, - "GET", "/" + index + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); } else { assertAccessIsDenied(user, "GET", "/" + index + "/_search"); assertAccessIsDenied(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " + "\"term\" : { \"field\" : \"name\" } } }"); - assertAccessIsDenied(user, - "GET", "/" + index + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n"); } break; case "get" : if (userIsAllowed) { assertAccessIsAllowed(user, "GET", "/" + index + "/foo/1"); - assertAccessIsAllowed(user, "POST", "/" + index + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); } else { assertAccessIsDenied(user, "GET", "/" + index + "/foo/1"); - assertAccessIsDenied(user, "POST", "/" + index + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } "); } break; @@ -498,13 +584,9 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase { if (userIsAllowed) { assertUserIsAllowed(user, "index", index); assertUserIsAllowed(user, "delete", index); - assertAccessIsAllowed(user, "PUT", - "/" + index + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); } else { assertUserIsDenied(user, "index", index); assertUserIsDenied(user, "delete", index); - assertAccessIsDenied(user, - "PUT", "/" + index + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n"); } break; diff --git a/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java b/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java index 0bfaf3d10f6..d51085a2ab3 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java +++ b/elasticsearch/src/test/java/org/elasticsearch/test/SecurityIntegTestCase.java @@ -6,9 +6,12 @@ package org.elasticsearch.test; import org.elasticsearch.AbstractOldXPackIndicesBackwardsCompatibilityTestCase; +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.client.Client; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.cluster.health.ClusterHealthStatus; @@ -16,13 +19,13 @@ import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.xpack.XPackClient; +import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.client.SecurityClient; -import org.elasticsearch.xpack.XPackClient; -import org.elasticsearch.xpack.XPackPlugin; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -38,8 +41,9 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; +import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsCollectionContaining.hasItem; @@ -352,12 +356,41 @@ public abstract class SecurityIntegTestCase extends ESIntegTestCase { } } - protected void assertGreenClusterState(Client client) { + protected static void assertGreenClusterState(Client client) { ClusterHealthResponse clusterHealthResponse = client.admin().cluster().prepareHealth().get(); assertNoTimeout(clusterHealthResponse); assertThat(clusterHealthResponse.getStatus(), is(ClusterHealthStatus.GREEN)); } + protected static void assertThrowsAuthorizationException(ActionRequestBuilder actionRequestBuilder) { + ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, actionRequestBuilder::get); + SecurityTestsUtils.assertAuthorizationException(e, containsString("is unauthorized for user [")); + } + + protected void createIndicesWithRandomAliases(String... indices) { + if (randomBoolean()) { + //no aliases + createIndex(indices); + } else { + if (randomBoolean()) { + //one alias per index with suffix "-alias" + for (String index : indices) { + client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias(index + "-alias")); + } + } else { + //same alias pointing to all indices + for (String index : indices) { + client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias("alias")); + } + } + } + + for (String index : indices) { + client().prepareIndex(index, "type").setSource("field", "value").get(); + } + refresh(); + } + @Override protected Function getClientWrapper() { Map headers = Collections.singletonMap("Authorization", diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/AuditUtilTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/AuditUtilTests.java index 33948c0b286..5acc97ef407 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/AuditUtilTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/audit/AuditUtilTests.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.audit; -import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.Strings; @@ -13,7 +12,6 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportMessage; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -44,34 +42,6 @@ public class AuditUtilTests extends ESTestCase { assertThat(result, hasItems(uniqueExpectedIndices.toArray(Strings.EMPTY_ARRAY))); } - public void testCompositeIndicesRequest() { - assertNull(AuditUtil.indices(new MockCompositeIndicesRequest(Collections.emptyList()))); - assertNull(AuditUtil.indices(new MockCompositeIndicesRequest(Collections.singletonList(new MockIndicesRequest(null))))); - final int numberOfIndicesRequests = randomIntBetween(1, 10); - final boolean includeDuplicates = randomBoolean(); - List expectedIndices = new ArrayList<>(); - List indicesRequests = new ArrayList<>(numberOfIndicesRequests); - for (int i = 0; i < numberOfIndicesRequests; i++) { - final int numberOfIndices = randomIntBetween(1, 12); - List indices = new ArrayList<>(numberOfIndices); - for (int j = 0; j < numberOfIndices; j++) { - String name = randomAsciiOfLengthBetween(1, 30); - indices.add(name); - if (includeDuplicates) { - indices.add(name); - } - } - expectedIndices.addAll(indices); - indicesRequests.add(new MockIndicesRequest(indices.toArray(Strings.EMPTY_ARRAY))); - } - - final Set uniqueExpectedIndices = new HashSet<>(expectedIndices); - final Set result = AuditUtil.indices(new MockCompositeIndicesRequest(indicesRequests)); - assertNotNull(result); - assertEquals(uniqueExpectedIndices.size(), result.size()); - assertThat(result, hasItems(uniqueExpectedIndices.toArray(Strings.EMPTY_ARRAY))); - } - private static class MockIndicesRequest extends TransportMessage implements IndicesRequest { private final String[] indices; @@ -90,18 +60,4 @@ public class AuditUtilTests extends ESTestCase { return null; } } - - private static class MockCompositeIndicesRequest extends TransportMessage implements CompositeIndicesRequest { - - private final List requests; - - private MockCompositeIndicesRequest(List requests) { - this.requests = requests; - } - - @Override - public List subRequests() { - return requests; - } - } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index b1ef5772d22..82af04a076b 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.authz; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; +import org.elasticsearch.action.CompositeIndicesRequest; +import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.indices.alias.Alias; @@ -32,20 +34,28 @@ import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction; 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.BulkRequest; import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.MultiGetAction; +import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.index.IndexAction; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.ClearScrollAction; import org.elasticsearch.action.search.ClearScrollRequest; +import org.elasticsearch.action.search.MultiSearchAction; +import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchScrollAction; import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.termvectors.MultiTermVectorsAction; +import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.update.UpdateAction; @@ -58,7 +68,7 @@ import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; @@ -83,6 +93,7 @@ import org.junit.Before; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; @@ -587,9 +598,9 @@ public class AuthorizationServiceTests extends ESTestCase { verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); SearchRequest searchRequest = new SearchRequest("_all"); - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, - () -> authorizationService.authorize(createAuthentication(user), SearchAction.NAME, searchRequest)); - assertThat(e.getMessage(), containsString("no such index")); + authorizationService.authorize(createAuthentication(user), SearchAction.NAME, searchRequest); + assertEquals(1, searchRequest.indices().length); + assertEquals(DefaultIndicesAndAliasesResolver.NO_INDEX, searchRequest.indices()[0]); } public void testGrantedNonXPackUserCanExecuteMonitoringOperationsAgainstSecurityIndex() { @@ -662,6 +673,29 @@ public class AuthorizationServiceTests extends ESTestCase { } } + public void testXPackUserAndSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() { + final User superuser = new User("custom_admin", SuperuserRole.NAME); + when(rolesStore.role(SuperuserRole.NAME)).thenReturn(Role.builder(SuperuserRole.DESCRIPTOR).build()); + ClusterState state = mock(ClusterState.class); + when(clusterService.state()).thenReturn(state); + when(state.metaData()).thenReturn(MetaData.builder() + .put(new IndexMetaData.Builder(SecurityTemplateService.SECURITY_INDEX_NAME) + .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) + .numberOfShards(1).numberOfReplicas(0).build(), true) + .build()); + + String action = SearchAction.NAME; + SearchRequest request = new SearchRequest("_all"); + authorizationService.authorize(createAuthentication(XPackUser.INSTANCE), action, request); + verify(auditTrail).accessGranted(XPackUser.INSTANCE, action, request); + assertThat(request.indices(), arrayContaining(".security")); + + request = new SearchRequest("_all"); + authorizationService.authorize(createAuthentication(superuser), action, request); + verify(auditTrail).accessGranted(superuser, action, request); + assertThat(request.indices(), arrayContaining(".security")); + } + public void testAnonymousRolesAreAppliedToOtherUsers() { TransportRequest request = new ClusterHealthRequest(); ClusterState state = mock(ClusterState.class); @@ -688,31 +722,136 @@ public class AuthorizationServiceTests extends ESTestCase { authorizationService.authorize(createAuthentication(userWithNoRoles), IndicesExistsAction.NAME, new IndicesExistsRequest("a")); } - public void testXPackUserAndSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() { - final User superuser = new User("custom_admin", SuperuserRole.NAME); - when(rolesStore.role(SuperuserRole.NAME)).thenReturn(Role.builder(SuperuserRole.DESCRIPTOR).build()); - ClusterState state = mock(ClusterState.class); - when(clusterService.state()).thenReturn(state); - when(state.metaData()).thenReturn(MetaData.builder() - .put(new IndexMetaData.Builder(SecurityTemplateService.SECURITY_INDEX_NAME) - .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) - .numberOfShards(1).numberOfReplicas(0).build(), true) - .build()); - - String action = SearchAction.NAME; - SearchRequest request = new SearchRequest("_all"); - authorizationService.authorize(createAuthentication(XPackUser.INSTANCE), action, request); - verify(auditTrail).accessGranted(XPackUser.INSTANCE, action, request); - assertThat(request.indices(), arrayContaining(".security")); - - request = new SearchRequest("_all"); - authorizationService.authorize(createAuthentication(superuser), action, request); - verify(auditTrail).accessGranted(superuser, action, request); - assertThat(request.indices(), arrayContaining(".security")); + public void testCompositeActionsAreImmediatelyRejected() { + //if the user has no permission for composite actions against any index, the request fails straight-away in the main action + Tuple compositeRequest = randomCompositeRequest(); + String action = compositeRequest.v1(); + TransportRequest request = compositeRequest.v2(); + User user = new User("test user", "no_indices"); + when(rolesStore.role("no_indices")).thenReturn(Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build()); + ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, + () -> authorizationService.authorize(createAuthentication(user), action, request)); + assertThat(securityException.status(), is(RestStatus.FORBIDDEN)); + assertThat(securityException.getMessage(), containsString("[" + action + "] is unauthorized for user [" + user.principal() + "]")); + verify(auditTrail).accessDenied(user, action, request); + verifyNoMoreInteractions(auditTrail); } - private Authentication createAuthentication(User user) { + public void testCompositeActionsIndicesAreNotChecked() { + //if the user has permission for some index, the request goes through without looking at the indices, they will be checked later + Tuple compositeRequest = randomCompositeRequest(); + String action = compositeRequest.v1(); + TransportRequest request = compositeRequest.v2(); + User user = new User("test user", "role"); + when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, randomBoolean() ? "a" : "index").build()); + authorizationService.authorize(createAuthentication(user), action, request); + verify(auditTrail).accessGranted(user, action, request); + verifyNoMoreInteractions(auditTrail); + } + + public void testCompositeActionsMustImplementCompositeIndicesRequest() { + String action = randomCompositeRequest().v1(); + TransportRequest request = mock(TransportRequest.class); + User user = new User("test user", "role"); + when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, randomBoolean() ? "a" : "index").build()); + IllegalStateException illegalStateException = expectThrows(IllegalStateException.class, + () -> authorizationService.authorize(createAuthentication(user), action, request)); + assertThat(illegalStateException.getMessage(), containsString("Composite actions must implement CompositeIndicesRequest")); + } + + public void testCompositeActionsIndicesAreCheckedAtTheShardLevel() { + String action; + switch(randomIntBetween(0, 6)) { + case 0: + action = MultiGetAction.NAME + "[shard]"; + break; + case 1: + action = SearchAction.NAME; + break; + case 2: + action = MultiTermVectorsAction.NAME + "[shard]"; + break; + case 3: + action = BulkAction.NAME + "[s]"; + break; + case 4: + action = "indices:data/read/mpercolate[s]"; + break; + case 5: + action = "indices:data/read/search/template"; + break; + case 6: + //reindex delegates to search and index + action = randomBoolean() ? SearchAction.NAME : IndexAction.NAME; + break; + default: + throw new UnsupportedOperationException(); + } + + TransportRequest request = new MockIndicesRequest(); + User userAllowed = new User("userAllowed", "roleAllowed"); + when(rolesStore.role("roleAllowed")).thenReturn(Role.builder("roleAllowed").add(IndexPrivilege.ALL, "index").build()); + User userDenied = new User("userDenied", "roleDenied"); + when(rolesStore.role("roleDenied")).thenReturn(Role.builder("roleDenied").add(IndexPrivilege.ALL, "a").build()); + mockEmptyMetaData(); + authorizationService.authorize(createAuthentication(userAllowed), action, request); + ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, + () -> authorizationService.authorize(createAuthentication(userDenied), action, request)); + assertThat(securityException.status(), is(RestStatus.FORBIDDEN)); + assertThat(securityException.getMessage(), + containsString("[" + action + "] is unauthorized for user [" + userDenied.principal() + "]")); + } + + private static Tuple randomCompositeRequest() { + switch(randomIntBetween(0, 6)) { + case 0: + return Tuple.tuple(MultiGetAction.NAME, new MultiGetRequest().add("index", "type", "id")); + case 1: + return Tuple.tuple(MultiSearchAction.NAME, new MultiSearchRequest().add(new SearchRequest())); + case 2: + return Tuple.tuple(MultiTermVectorsAction.NAME, new MultiTermVectorsRequest().add("index", "type", "id")); + case 3: + return Tuple.tuple(BulkAction.NAME, new BulkRequest().add(new DeleteRequest("index", "type", "id"))); + case 4: + return Tuple.tuple("indices:data/read/mpercolate", new MockCompositeIndicesRequest()); + case 5: + return Tuple.tuple("indices:data/read/msearch/template", new MockCompositeIndicesRequest()); + case 6: + return Tuple.tuple("indices:data/write/reindex", new MockCompositeIndicesRequest()); + default: + throw new UnsupportedOperationException(); + } + } + + private static class MockIndicesRequest extends TransportRequest implements IndicesRequest { + @Override + public String[] indices() { + return new String[]{"index"}; + } + + @Override + public IndicesOptions indicesOptions() { + return IndicesOptions.strictExpandOpen(); + } + } + + private static class MockCompositeIndicesRequest extends TransportRequest implements CompositeIndicesRequest { + + @Override + public List subRequests() { + return Collections.singletonList(new MockIndicesRequest()); + } + } + + private static Authentication createAuthentication(User user) { RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by"); return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy); } + + private ClusterState mockEmptyMetaData() { + ClusterState state = mock(ClusterState.class); + when(clusterService.state()).thenReturn(state); + when(state.metaData()).thenReturn(MetaData.EMPTY_META_DATA); + return state; + } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java new file mode 100644 index 00000000000..ed64de0a5de --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java @@ -0,0 +1,422 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authz; + +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.get.MultiGetResponse; +import org.elasticsearch.action.search.MultiSearchResponse; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.termvectors.MultiTermVectorsResponse; +import org.elasticsearch.client.Requests; +import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.search.SearchHit; +import org.elasticsearch.test.SecurityIntegTestCase; +import org.elasticsearch.test.SecuritySettingsSource; + +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.test.SecurityTestsUtils.assertAuthorizationException; +import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.IsCollectionContaining.hasItems; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.number.OrderingComparison.greaterThan; + +public class ReadActionsTests extends SecurityIntegTestCase { + + @Override + protected String configRoles() { + return SecuritySettingsSource.DEFAULT_ROLE + ":\n" + + " cluster: [ ALL ]\n" + + " indices:\n" + + " - names: '*'\n" + + " privileges: [ manage, write ]\n" + + " - names: '/test.*/'\n" + + " privileges: [ read ]\n"; + } + + public void testSearchForAll() { + //index1 is not authorized and referred to through wildcard + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + + SearchResponse searchResponse = client().prepareSearch().get(); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + } + + public void testSearchForWildcard() { + //index1 is not authorized and referred to through wildcard + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + + SearchResponse searchResponse = client().prepareSearch("*").get(); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + } + + public void testSearchNonAuthorizedWildcard() { + //wildcard doesn't match any authorized index + createIndicesWithRandomAliases("test1", "test2", "index1", "index2"); + assertNoSearchHits(client().prepareSearch("index*").get()); + } + + public void testSearchNonAuthorizedWildcardDisallowNoIndices() { + //wildcard doesn't match any authorized index + createIndicesWithRandomAliases("test1", "test2", "index1", "index2"); + IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("index*") + .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); + assertEquals("no such index", e.getMessage()); + } + + public void testEmptyClusterSearchForAll() { + assertNoSearchHits(client().prepareSearch().get()); + } + + public void testEmptyClusterSearchForAllDisallowNoIndices() { + IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch() + .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); + assertEquals("no such index", e.getMessage()); + } + + public void testEmptyClusterSearchForWildcard() { + SearchResponse searchResponse = client().prepareSearch("*").get(); + assertNoSearchHits(searchResponse); + } + + public void testEmptyClusterSearchForWildcardDisallowNoIndices() { + IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("*") + .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); + assertEquals("no such index", e.getMessage()); + } + + public void testEmptyAuthorizedIndicesSearchForAll() { + createIndicesWithRandomAliases("index1", "index2"); + assertNoSearchHits(client().prepareSearch().get()); + } + + public void testEmptyAuthorizedIndicesSearchForAllDisallowNoIndices() { + createIndicesWithRandomAliases("index1", "index2"); + IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch() + .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); + assertEquals("no such index", e.getMessage()); + } + + public void testEmptyAuthorizedIndicesSearchForWildcard() { + createIndicesWithRandomAliases("index1", "index2"); + assertNoSearchHits(client().prepareSearch("*").get()); + } + + public void testEmptyAuthorizedIndicesSearchForWildcardDisallowNoIndices() { + createIndicesWithRandomAliases("index1", "index2"); + IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("*") + .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); + assertEquals("no such index", e.getMessage()); + } + + public void testExplicitNonAuthorizedIndex() { + createIndicesWithRandomAliases("test1", "test2", "index1"); + assertThrowsAuthorizationException(client().prepareSearch("test*", "index1")); + } + + public void testIndexNotFound() { + createIndicesWithRandomAliases("test1", "test2", "index1"); + assertThrowsAuthorizationException(client().prepareSearch("missing")); + } + + public void testIndexNotFoundIgnoreUnavailable() { + IndicesOptions indicesOptions = IndicesOptions.lenientExpandOpen(); + createIndicesWithRandomAliases("test1", "test2", "index1"); + + String index = randomFrom("test1", "test2"); + assertReturnedIndices(client().prepareSearch("missing", index).setIndicesOptions(indicesOptions).get(), index); + + assertReturnedIndices(client().prepareSearch("missing", "test*").setIndicesOptions(indicesOptions).get(), "test1", "test2"); + + assertReturnedIndices(client().prepareSearch("missing_*", "test*").setIndicesOptions(indicesOptions).get(), "test1", "test2"); + + //an unauthorized index is the same as a missing one + assertNoSearchHits(client().prepareSearch("missing").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("index1").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("missing", "index1").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("does_not_match_any_*").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("does_not_match_any_*", "index1").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("index*").setIndicesOptions(indicesOptions).get()); + + assertNoSearchHits(client().prepareSearch("index*", "missing").setIndicesOptions(indicesOptions).get()); + } + + public void testExplicitExclusion() { + //index1 is not authorized and referred to through wildcard, test2 is excluded + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + + SearchResponse searchResponse = client().prepareSearch("-test2").get(); + assertReturnedIndices(searchResponse, "test1", "test3"); + } + + public void testWildcardExclusion() { + //index1 is not authorized and referred to through wildcard, test2 is excluded + createIndicesWithRandomAliases("test1", "test2", "test21", "test3", "index1"); + + SearchResponse searchResponse = client().prepareSearch("-test2*").get(); + assertReturnedIndices(searchResponse, "test1", "test3"); + } + + public void testInclusionAndWildcardsExclusion() { + //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded + createIndicesWithRandomAliases("test1", "test10", "test111", "test112", "test2", "index1"); + + SearchResponse searchResponse = client().prepareSearch("test1*", "index*", "-test11*").get(); + assertReturnedIndices(searchResponse, "test1", "test10"); + } + + public void testExplicitAndWildcardsInclusionAndWildcardExclusion() { + //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded + createIndicesWithRandomAliases("test1", "test10", "test111", "test112", "test2", "index1"); + + SearchResponse searchResponse = client().prepareSearch("+test2", "+test11*", "index*", "-test2*").get(); + assertReturnedIndices(searchResponse, "test111", "test112"); + } + + public void testExplicitAndWildcardInclusionAndExplicitExclusions() { + //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded + createIndicesWithRandomAliases("test1", "test10", "test111", "test112", "test2", "index1"); + + SearchResponse searchResponse = client().prepareSearch("+test10", "+test11*", "index*", "-test111", "-test112").get(); + assertReturnedIndices(searchResponse, "test10"); + } + + public void testMissingDateMath() { + expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("").get()); + } + + public void testMultiSearchUnauthorizedIndex() { + //index1 is not authorized, the whole request fails due to that + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("index1")).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertTrue(multiSearchResponse.getResponses()[1].isFailure()); + Exception exception = multiSearchResponse.getResponses()[1].getFailure(); + assertThat(exception, instanceOf(ElasticsearchSecurityException.class)); + assertAuthorizationException((ElasticsearchSecurityException)exception, containsString("is unauthorized for user [")); + } + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("index1") + .indicesOptions(IndicesOptions.fromOptions(true, true, true, randomBoolean()))).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertFalse(multiSearchResponse.getResponses()[1].isFailure()); + assertNoSearchHits(multiSearchResponse.getResponses()[1].getResponse()); + } + } + + public void testMultiSearchMissingUnauthorizedIndex() { + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("missing")).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertTrue(multiSearchResponse.getResponses()[1].isFailure()); + Exception exception = multiSearchResponse.getResponses()[1].getFailure(); + assertThat(exception, instanceOf(ElasticsearchSecurityException.class)); + assertAuthorizationException((ElasticsearchSecurityException)exception, containsString("is unauthorized for user [")); + } + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("missing") + .indicesOptions(IndicesOptions.fromOptions(true, true, true, randomBoolean()))).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertFalse(multiSearchResponse.getResponses()[1].isFailure()); + assertNoSearchHits(multiSearchResponse.getResponses()[1].getResponse()); + } + } + + public void testMultiSearchMissingAuthorizedIndex() { + //test4 is missing but authorized, only that specific item fails + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + { + //default indices options for search request don't ignore unavailable indices, only individual items fail. + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("test4")).get(); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + assertReturnedIndices(multiSearchResponse.getResponses()[0].getResponse(), "test1", "test2", "test3"); + assertTrue(multiSearchResponse.getResponses()[1].isFailure()); + assertThat(multiSearchResponse.getResponses()[1].getFailure().toString(), + equalTo("[test4] IndexNotFoundException[no such index]")); + } + { + //we set ignore_unavailable and allow_no_indices to true, no errors returned, second item doesn't have hits. + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() + .add(Requests.searchRequest()) + .add(Requests.searchRequest("test4") + .indicesOptions(IndicesOptions.fromOptions(true, true, true, randomBoolean()))).get(); + assertReturnedIndices(multiSearchResponse.getResponses()[0].getResponse(), "test1", "test2", "test3"); + assertNoSearchHits(multiSearchResponse.getResponses()[1].getResponse()); + } + } + + public void testMultiSearchWildcard() { + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch().add(Requests.searchRequest()) + .add(Requests.searchRequest("index*")).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertNoSearchHits(multiSearchResponse.getResponses()[1].getResponse()); + } + { + MultiSearchResponse multiSearchResponse = client().prepareMultiSearch().add(Requests.searchRequest()) + .add(Requests.searchRequest("index*") + .indicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean()))).get(); + assertEquals(2, multiSearchResponse.getResponses().length); + assertFalse(multiSearchResponse.getResponses()[0].isFailure()); + SearchResponse searchResponse = multiSearchResponse.getResponses()[0].getResponse(); + assertThat(searchResponse.getHits().getTotalHits(), greaterThan(0L)); + assertReturnedIndices(searchResponse, "test1", "test2", "test3"); + assertTrue(multiSearchResponse.getResponses()[1].isFailure()); + Exception exception = multiSearchResponse.getResponses()[1].getFailure(); + assertThat(exception, instanceOf(IndexNotFoundException.class)); + } + } + + public void testIndicesExists() { + createIndicesWithRandomAliases("test1", "test2", "test3"); + + assertEquals(true, client().admin().indices().prepareExists("*").get().isExists()); + + assertEquals(true, client().admin().indices().prepareExists("_all").get().isExists()); + + assertEquals(true, client().admin().indices().prepareExists("test1", "test2").get().isExists()); + + assertEquals(true, client().admin().indices().prepareExists("test*").get().isExists()); + + assertEquals(false, client().admin().indices().prepareExists("does_not_exist").get().isExists()); + + assertEquals(false, client().admin().indices().prepareExists("does_not_exist*").get().isExists()); + } + + public void testGet() { + createIndicesWithRandomAliases("test1", "index1"); + + client().prepareGet("test1", "type", "id").get(); + + ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareGet("index1", "type", "id").get()); + assertAuthorizationException(securityException); + + securityException = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareGet("missing", "type", "id").get()); + assertAuthorizationException(securityException); + + expectThrows(IndexNotFoundException.class, () -> client().prepareGet("test5", "type", "id").get()); + } + + public void testMultiGet() { + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + MultiGetResponse multiGetResponse = client().prepareMultiGet() + .add("test1", "type", "id") + .add("index1", "type", "id") + .add("test3", "type", "id") + .add("missing", "type", "id") + .add("test5", "type", "id").get(); + assertEquals(5, multiGetResponse.getResponses().length); + assertFalse(multiGetResponse.getResponses()[0].isFailed()); + assertEquals("test1", multiGetResponse.getResponses()[0].getResponse().getIndex()); + assertTrue(multiGetResponse.getResponses()[1].isFailed()); + assertEquals("index1", multiGetResponse.getResponses()[1].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) multiGetResponse.getResponses()[1].getFailure().getFailure()); + assertFalse(multiGetResponse.getResponses()[2].isFailed()); + assertEquals("test3", multiGetResponse.getResponses()[2].getResponse().getIndex()); + assertTrue(multiGetResponse.getResponses()[3].isFailed()); + assertEquals("missing", multiGetResponse.getResponses()[3].getFailure().getIndex()); + //different behaviour compared to get api: we leak information about a non existing index that the current user is not + //authorized for. Should rather be an authorization exception but we only authorize at the shard level in mget. If we + //authorized globally, we would fail the whole mget request which is not desirable. + assertThat(multiGetResponse.getResponses()[3].getFailure().getFailure(), instanceOf(IndexNotFoundException.class)); + assertTrue(multiGetResponse.getResponses()[4].isFailed()); + assertThat(multiGetResponse.getResponses()[4].getFailure().getFailure(), instanceOf(IndexNotFoundException.class)); + } + + public void testTermVectors() { + createIndicesWithRandomAliases("test1", "index1"); + client().prepareTermVectors("test1", "type", "id").get(); + + ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareTermVectors("index1", "type", "id").get()); + assertAuthorizationException(securityException); + + securityException = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareTermVectors("missing", "type", "id").get()); + assertAuthorizationException(securityException); + + expectThrows(IndexNotFoundException.class, () -> client().prepareTermVectors("test5", "type", "id").get()); + } + + public void testMultiTermVectors() { + createIndicesWithRandomAliases("test1", "test2", "test3", "index1"); + MultiTermVectorsResponse response = client().prepareMultiTermVectors() + .add("test1", "type", "id") + .add("index1", "type", "id") + .add("test3", "type", "id") + .add("missing", "type", "id") + .add("test5", "type", "id").get(); + assertEquals(5, response.getResponses().length); + assertFalse(response.getResponses()[0].isFailed()); + assertEquals("test1", response.getResponses()[0].getResponse().getIndex()); + assertTrue(response.getResponses()[1].isFailed()); + assertEquals("index1", response.getResponses()[1].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) response.getResponses()[1].getFailure().getCause()); + assertFalse(response.getResponses()[2].isFailed()); + assertEquals("test3", response.getResponses()[2].getResponse().getIndex()); + assertTrue(response.getResponses()[3].isFailed()); + assertEquals("missing", response.getResponses()[3].getFailure().getIndex()); + //different behaviour compared to get api: we leak information about a non existing index that the current user is not + //authorized for. Should rather be an authorization exception but we only authorize at the shard level in mget. If we + //authorized globally, we would fail the whole mget request which is not desirable. + assertThat(response.getResponses()[3].getFailure().getCause(), instanceOf(IndexNotFoundException.class)); + assertTrue(response.getResponses()[4].isFailed()); + assertThat(response.getResponses()[4].getFailure().getCause(), instanceOf(IndexNotFoundException.class)); + } + + private static void assertReturnedIndices(SearchResponse searchResponse, String... indices) { + List foundIndices = new ArrayList<>(); + for (SearchHit searchHit : searchResponse.getHits().getHits()) { + foundIndices.add(searchHit.index()); + } + assertThat(foundIndices.size(), equalTo(indices.length)); + assertThat(foundIndices, hasItems(indices)); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/WriteActionsTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/WriteActionsTests.java new file mode 100644 index 00000000000..434b59c61c0 --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/WriteActionsTests.java @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.security.authz; + +import org.elasticsearch.ElasticsearchSecurityException; +import org.elasticsearch.action.DocWriteRequest; +import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.index.IndexRequest; +import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.index.engine.DocumentMissingException; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.test.SecurityIntegTestCase; +import org.elasticsearch.test.SecuritySettingsSource; + +import static org.elasticsearch.test.SecurityTestsUtils.assertAuthorizationException; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +public class WriteActionsTests extends SecurityIntegTestCase { + + @Override + protected String configRoles() { + return SecuritySettingsSource.DEFAULT_ROLE + ":\n" + + " cluster: [ ALL ]\n" + + " indices:\n" + + " - names: 'missing'\n" + + " privileges: [ 'indices:admin/create', 'indices:admin/delete' ]\n" + + " - names: ['/index.*/']\n" + + " privileges: [ manage ]\n" + + " - names: ['/test.*/']\n" + + " privileges: [ manage, write ]\n" + + " - names: '/test.*/'\n" + + " privileges: [ read ]\n"; + } + + public void testIndex() { + createIndex("test1", "index1"); + client().prepareIndex("test1", "type", "id").setSource("field", "value").get(); + + assertThrowsAuthorizationException(client().prepareIndex("index1", "type", "id").setSource("field", "value")); + + client().prepareIndex("test4", "type", "id").setSource("field", "value").get(); + //the missing index gets automatically created (user has permissions for that), but indexing fails due to missing authorization + ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareIndex("missing", "type", "id").setSource("field", "value").get()); + assertAuthorizationException(exception); + assertThat(exception.getMessage(), containsString("[indices:data/write/index] is unauthorized")); + } + + public void testDelete() { + createIndex("test1", "index1"); + client().prepareIndex("test1", "type", "id").setSource("field", "value").get(); + assertEquals(RestStatus.OK, client().prepareDelete("test1", "type", "id").get().status()); + + assertThrowsAuthorizationException(client().prepareDelete("index1", "type", "id")); + + assertEquals(RestStatus.NOT_FOUND, client().prepareDelete("test4", "type", "id").get().status()); + + ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareDelete("missing", "type", "id").get()); + assertAuthorizationException(exception); + assertThat(exception.getMessage(), containsString("[indices:data/write/delete] is unauthorized")); + } + + public void testUpdate() { + createIndex("test1", "index1"); + client().prepareIndex("test1", "type", "id").setSource("field", "value").get(); + assertEquals(RestStatus.OK, client().prepareUpdate("test1", "type", "id").setDoc("field2", "value2").get().status()); + + assertThrowsAuthorizationException(client().prepareUpdate("index1", "type", "id").setDoc("field2", "value2")); + + expectThrows(DocumentMissingException.class, () -> client().prepareUpdate("test4", "type", "id").setDoc("field2", "value2").get()); + + ElasticsearchSecurityException exception = expectThrows(ElasticsearchSecurityException.class, + () -> client().prepareUpdate("missing", "type", "id").setDoc("field2", "value2").get()); + assertAuthorizationException(exception); + assertThat(exception.getMessage(), containsString("[indices:data/write/update] is unauthorized")); + } + + public void testBulk() { + createIndex("test1", "test2", "test3", "index1"); + BulkResponse bulkResponse = client().prepareBulk() + .add(new IndexRequest("test1", "type", "id").source("field", "value")) + .add(new IndexRequest("index1", "type", "id").source("field", "value")) + .add(new IndexRequest("test4", "type", "id").source("field", "value")) + .add(new IndexRequest("missing", "type", "id").source("field", "value")) + .add(new DeleteRequest("test1", "type", "id")) + .add(new DeleteRequest("index1", "type", "id")) + .add(new DeleteRequest("test4", "type", "id")) + .add(new DeleteRequest("missing", "type", "id")) + .add(new IndexRequest("test1", "type", "id").source("field", "value")) + .add(new UpdateRequest("test1", "type", "id").doc("field", "value")) + .add(new UpdateRequest("index1", "type", "id").doc("field", "value")) + .add(new UpdateRequest("test4", "type", "id").doc("field", "value")) + .add(new UpdateRequest("missing", "type", "id").doc("field", "value")).get(); + assertTrue(bulkResponse.hasFailures()); + assertEquals(13, bulkResponse.getItems().length); + assertFalse(bulkResponse.getItems()[0].isFailed()); + assertEquals(DocWriteRequest.OpType.INDEX, bulkResponse.getItems()[0].getOpType()); + assertEquals("test1", bulkResponse.getItems()[0].getIndex()); + assertTrue(bulkResponse.getItems()[1].isFailed()); + assertEquals(DocWriteRequest.OpType.INDEX, bulkResponse.getItems()[1].getOpType()); + assertEquals("index1", bulkResponse.getItems()[1].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[1].getFailure().getCause()); + assertThat(bulkResponse.getItems()[1].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + assertFalse(bulkResponse.getItems()[2].isFailed()); + assertEquals(DocWriteRequest.OpType.INDEX, bulkResponse.getItems()[2].getOpType()); + assertEquals("test4", bulkResponse.getItems()[2].getResponse().getIndex()); + assertTrue(bulkResponse.getItems()[3].isFailed()); + assertEquals(DocWriteRequest.OpType.INDEX, bulkResponse.getItems()[3].getOpType()); + //the missing index gets automatically created (user has permissions for that), but indexing fails due to missing authorization + assertEquals("missing", bulkResponse.getItems()[3].getFailure().getIndex()); + assertThat(bulkResponse.getItems()[3].getFailure().getCause(), instanceOf(ElasticsearchSecurityException.class)); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[3].getFailure().getCause()); + assertThat(bulkResponse.getItems()[3].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + assertFalse(bulkResponse.getItems()[4].isFailed()); + assertEquals(DocWriteRequest.OpType.DELETE, bulkResponse.getItems()[4].getOpType()); + assertEquals("test1", bulkResponse.getItems()[4].getIndex()); + assertTrue(bulkResponse.getItems()[5].isFailed()); + assertEquals(DocWriteRequest.OpType.DELETE, bulkResponse.getItems()[5].getOpType()); + assertEquals("index1", bulkResponse.getItems()[5].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[5].getFailure().getCause()); + assertThat(bulkResponse.getItems()[5].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + assertFalse(bulkResponse.getItems()[6].isFailed()); + assertEquals(DocWriteRequest.OpType.DELETE, bulkResponse.getItems()[6].getOpType()); + assertEquals("test4", bulkResponse.getItems()[6].getIndex()); + assertTrue(bulkResponse.getItems()[7].isFailed()); + assertEquals(DocWriteRequest.OpType.DELETE, bulkResponse.getItems()[7].getOpType()); + assertEquals("missing", bulkResponse.getItems()[7].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[7].getFailure().getCause()); + assertThat(bulkResponse.getItems()[7].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + assertFalse(bulkResponse.getItems()[8].isFailed()); + assertEquals(DocWriteRequest.OpType.INDEX, bulkResponse.getItems()[8].getOpType()); + assertEquals("test1", bulkResponse.getItems()[8].getIndex()); + assertFalse(bulkResponse.getItems()[9].isFailed()); + assertEquals(DocWriteRequest.OpType.UPDATE, bulkResponse.getItems()[9].getOpType()); + assertEquals("test1", bulkResponse.getItems()[9].getIndex()); + assertTrue(bulkResponse.getItems()[10].isFailed()); + assertEquals(DocWriteRequest.OpType.UPDATE, bulkResponse.getItems()[10].getOpType()); + assertEquals("index1", bulkResponse.getItems()[10].getFailure().getIndex()); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[10].getFailure().getCause()); + assertThat(bulkResponse.getItems()[10].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + assertTrue(bulkResponse.getItems()[11].isFailed()); + assertEquals(DocWriteRequest.OpType.UPDATE, bulkResponse.getItems()[11].getOpType()); + assertEquals("test4", bulkResponse.getItems()[11].getIndex()); + assertThat(bulkResponse.getItems()[11].getFailure().getCause(), instanceOf(DocumentMissingException.class)); + assertTrue(bulkResponse.getItems()[12].isFailed()); + assertEquals(DocWriteRequest.OpType.UPDATE, bulkResponse.getItems()[12].getOpType()); + assertEquals("missing", bulkResponse.getItems()[12].getFailure().getIndex()); + assertThat(bulkResponse.getItems()[12].getFailure().getCause(), instanceOf(ElasticsearchSecurityException.class)); + assertAuthorizationException((ElasticsearchSecurityException) bulkResponse.getItems()[12].getFailure().getCause()); + assertThat(bulkResponse.getItems()[12].getFailure().getCause().getMessage(), + containsString("[indices:data/write/bulk[s]] is unauthorized")); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java index 272b70149c0..07b3471515f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java @@ -126,6 +126,9 @@ public class IndicesPermissionTests extends ESTestCase { // did not define anything for ba so we allow all assertFalse(authzMap.get("ba").getFieldPermissions().hasFieldLevelSecurity()); + assertTrue(core.check(SearchAction.NAME)); + assertFalse(core.check("unknown")); + // test with two indices group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1"); group2 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(null, new @@ -142,5 +145,8 @@ public class IndicesPermissionTests extends ESTestCase { assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field")); assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field2")); assertTrue(authzMap.get("a2").getFieldPermissions().hasFieldLevelSecurity()); + + assertTrue(core.check(SearchAction.NAME)); + assertFalse(core.check("unknown")); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesResolverTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesResolverTests.java index 86f9cd92cb2..648072f415f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesResolverTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/DefaultIndicesResolverTests.java @@ -16,14 +16,14 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsAction; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; -import org.elasticsearch.action.get.MultiGetAction; +import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.get.MultiGetRequest; import org.elasticsearch.action.search.MultiSearchAction; import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.client.Requests; +import org.elasticsearch.action.termvectors.MultiTermVectorsRequest; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData; @@ -34,11 +34,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.security.SecurityTemplateService; -import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; -import org.elasticsearch.xpack.security.user.AnonymousUser; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.user.XPackUser; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; import org.elasticsearch.xpack.security.authz.AuthorizationService; @@ -46,11 +43,14 @@ import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; +import org.elasticsearch.xpack.security.user.AnonymousUser; +import org.elasticsearch.xpack.security.user.User; +import org.elasticsearch.xpack.security.user.XPackUser; import org.junit.Before; import java.util.Set; -import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasItem; @@ -880,137 +880,10 @@ public class DefaultIndicesResolverTests extends ESTestCase { assertEquals("no such index", e.getMessage()); } - //msearch is a CompositeIndicesRequest whose items (SearchRequests) implement IndicesRequest.Replaceable, wildcards will get replaced - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchNoWildcards() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("foo", "bar")); - request.add(Requests.searchRequest("bar2")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"foo", "bar", "bar2"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"foo", "bar"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"bar2"})); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchNoWildcardsMissingIndex() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("foo", "bar")); - request.add(Requests.searchRequest("bar2")); - request.add(Requests.searchRequest("missing")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"foo", "bar", "bar2", "missing"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"foo", "bar"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"bar2"})); - assertThat(request.subRequests().get(2).indices(), equalTo(new String[]{"missing"})); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchWildcardsExpandOpen() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("bar*")).indicesOptions( - randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen())); - request.add(Requests.searchRequest("foobar")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"bar", "foobar"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"bar"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"foobar"})); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchWildcardsExpandOpenAndClose() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("bar*").indicesOptions(IndicesOptions.strictExpand())); - request.add(Requests.searchRequest("foobar")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"bar", "bar-closed", "foobar"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"bar", "bar-closed"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"foobar"})); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchWildcardsMissingIndex() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("bar*")); - request.add(Requests.searchRequest("missing")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"bar", "missing"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"bar"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"missing"})); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchWildcardsNoMatchingIndices() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("missing*")); - request.add(Requests.searchRequest("foobar")); - try { - defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData); - fail("Expected IndexNotFoundException"); - } catch (IndexNotFoundException e) { - assertThat(e.getMessage(), is("no such index")); - } - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testMultiSearchWildcardsNoAuthorizedIndices() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("foofoo*")); - request.add(Requests.searchRequest("foobar")); - try { - defaultIndicesResolver.resolve(userNoIndices, MultiSearchAction.NAME, request, metaData); - fail("Expected IndexNotFoundException"); - } catch (IndexNotFoundException e) { - assertThat(e.getMessage(), is("no such index")); - } - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testResolveMultiSearchWildcardsNoAuthorizedIndices() { - MultiSearchRequest request = new MultiSearchRequest(); - request.add(Requests.searchRequest("foofoo*")); - request.add(Requests.searchRequest("foobar")); - try { - defaultIndicesResolver.resolve(userNoIndices, MultiSearchAction.NAME, request, metaData); - fail("Expected IndexNotFoundException"); - } catch (IndexNotFoundException e) { - assertThat(e.getMessage(), is("no such index")); - } - } - - //mget is a CompositeIndicesRequest whose items don't support expanding wildcards - public void testResolveMultiGet() { - MultiGetRequest request = new MultiGetRequest(); - request.add("foo", "type", "id"); - request.add("bar", "type", "id"); - Set indices = defaultIndicesResolver.resolve(user, MultiGetAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"foo", "bar"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"foo"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"bar"})); - } - - public void testResolveMultiGetMissingIndex() { - MultiGetRequest request = new MultiGetRequest(); - request.add("foo", "type", "id"); - request.add("missing", "type", "id"); - Set indices = defaultIndicesResolver.resolve(user, MultiGetAction.NAME, request, metaData); - String[] expectedIndices = new String[]{"foo", "missing"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(request.subRequests().get(0).indices(), equalTo(new String[]{"foo"})); - assertThat(request.subRequests().get(1).indices(), equalTo(new String[]{"missing"})); + public void testCompositeIndicesRequestIsNotSupported() { + TransportRequest request = randomFrom(new MultiSearchRequest(), new MultiGetRequest(), + new MultiTermVectorsRequest(), new BulkRequest()); + expectThrows(IllegalStateException.class, () -> defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData)); } public void testResolveAdminAction() { @@ -1139,34 +1012,6 @@ public class DefaultIndicesResolverTests extends ESTestCase { assertThat(request.aliases(), arrayContainingInAnyOrder("")); } - public void testCompositeRequestsCanResolveExpressions() { - // make the user authorized - String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", - indexNameExpressionResolver.resolveDateMathExpression("")}; - when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); - MultiSearchRequest multiSearchRequest = new MultiSearchRequest(); - multiSearchRequest.add(new SearchRequest("")); - multiSearchRequest.add(new SearchRequest("bar")); - Set indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, multiSearchRequest, metaData); - - String resolvedName = indexNameExpressionResolver.resolveDateMathExpression(""); - String[] expectedIndices = new String[]{resolvedName, "bar"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(multiSearchRequest.requests().get(0).indices(), arrayContaining(resolvedName)); - assertThat(multiSearchRequest.requests().get(1).indices(), arrayContaining("bar")); - - // multi get doesn't support replacing indices but we should authorize the proper indices - MultiGetRequest multiGetRequest = new MultiGetRequest(); - multiGetRequest.add("", "type", "1"); - multiGetRequest.add("bar", "type", "1"); - indices = defaultIndicesResolver.resolve(user, MultiGetAction.NAME, multiGetRequest, metaData); - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - assertThat(multiGetRequest.getItems().get(0).indices(), arrayContaining("")); - assertThat(multiGetRequest.getItems().get(1).indices(), arrayContaining("bar")); - } - // TODO with the removal of DeleteByQuery is there another way to test resolving a write action? private static IndexMetaData.Builder indexBuilder(String index) { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/IndicesAndAliasesResolverIntegrationTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/IndicesAndAliasesResolverIntegrationTests.java deleted file mode 100644 index 59daa8c8e77..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/indicesresolver/IndicesAndAliasesResolverIntegrationTests.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.xpack.security.authz.indicesresolver; - -import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.ActionRequestBuilder; -import org.elasticsearch.action.admin.indices.alias.Alias; -import org.elasticsearch.action.search.MultiSearchResponse; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.action.support.IndicesOptions; -import org.elasticsearch.client.Requests; -import org.elasticsearch.index.IndexNotFoundException; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.test.SecuritySettingsSource; - -import java.util.ArrayList; -import java.util.List; - -import static org.elasticsearch.test.SecurityTestsUtils.assertAuthorizationException; -import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoSearchHits; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItems; - -public class IndicesAndAliasesResolverIntegrationTests extends SecurityIntegTestCase { - - @Override - protected String configRoles() { - return SecuritySettingsSource.DEFAULT_ROLE + ":\n" + - " cluster: [ ALL ]\n" + - " indices:\n" + - " - names: '*'\n" + - " privileges: [ manage, write ]\n" + - " - names: '/test.*/'\n" + - " privileges: [ read ]\n"; - } - - public void testSearchForAll() { - //index1 is not authorized and referred to through wildcard - createIndices("test1", "test2", "test3", "index1"); - - SearchResponse searchResponse = client().prepareSearch().get(); - assertReturnedIndices(searchResponse, "test1", "test2", "test3"); - } - - public void testSearchForWildcard() { - //index1 is not authorized and referred to through wildcard - createIndices("test1", "test2", "test3", "index1"); - - SearchResponse searchResponse = client().prepareSearch("*").get(); - assertReturnedIndices(searchResponse, "test1", "test2", "test3"); - } - - public void testSearchNonAuthorizedWildcard() { - //wildcard doesn't match any authorized index - createIndices("test1", "test2", "index1", "index2"); - assertNoSearchHits(client().prepareSearch("index*").get()); - } - - public void testSearchNonAuthorizedWildcardDisallowNoIndices() { - //wildcard doesn't match any authorized index - createIndices("test1", "test2", "index1", "index2"); - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("index*") - .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); - assertEquals("no such index", e.getMessage()); - } - - public void testEmptyClusterSearchForAll() { - assertNoSearchHits(client().prepareSearch().get()); - } - - public void testEmptyClusterSearchForAllDisallowNoIndices() { - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch() - .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); - assertEquals("no such index", e.getMessage()); - } - - public void testEmptyClusterSearchForWildcard() { - SearchResponse searchResponse = client().prepareSearch("*").get(); - assertNoSearchHits(searchResponse); - } - - public void testEmptyClusterSearchForWildcardDisallowNoIndices() { - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("*") - .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); - assertEquals("no such index", e.getMessage()); - } - - public void testEmptyAuthorizedIndicesSearchForAll() { - createIndices("index1", "index2"); - assertNoSearchHits(client().prepareSearch().get()); - } - - public void testEmptyAuthorizedIndicesSearchForAllDisallowNoIndices() { - createIndices("index1", "index2"); - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch() - .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); - assertEquals("no such index", e.getMessage()); - } - - public void testEmptyAuthorizedIndicesSearchForWildcard() { - createIndices("index1", "index2"); - assertNoSearchHits(client().prepareSearch("*").get()); - } - - public void testEmptyAuthorizedIndicesSearchForWildcardDisallowNoIndices() { - createIndices("index1", "index2"); - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("*") - .setIndicesOptions(IndicesOptions.fromOptions(randomBoolean(), false, true, randomBoolean())).get()); - assertEquals("no such index", e.getMessage()); - } - - public void testExplicitNonAuthorizedIndex() { - createIndices("test1", "test2", "index1"); - assertThrowsAuthorizationException(client().prepareSearch("test*", "index1")); - } - - public void testIndexNotFound() { - createIndices("test1", "test2", "index1"); - assertThrowsAuthorizationException(client().prepareSearch("missing")); - } - - public void testIndexNotFoundIgnoreUnavailable() { - IndicesOptions indicesOptions = IndicesOptions.lenientExpandOpen(); - createIndices("test1", "test2", "index1"); - - String index = randomFrom("test1", "test2"); - assertReturnedIndices(client().prepareSearch("missing", index).setIndicesOptions(indicesOptions).get(), index); - - assertReturnedIndices(client().prepareSearch("missing", "test*").setIndicesOptions(indicesOptions).get(), "test1", "test2"); - - assertReturnedIndices(client().prepareSearch("missing_*", "test*").setIndicesOptions(indicesOptions).get(), "test1", "test2"); - - //an unauthorized index is the same as a missing one - assertNoSearchHits(client().prepareSearch("missing").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("index1").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("missing", "index1").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("does_not_match_any_*").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("does_not_match_any_*", "index1").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("index*").setIndicesOptions(indicesOptions).get()); - - assertNoSearchHits(client().prepareSearch("index*", "missing").setIndicesOptions(indicesOptions).get()); - } - - public void testExplicitExclusion() { - //index1 is not authorized and referred to through wildcard, test2 is excluded - createIndices("test1", "test2", "test3", "index1"); - - SearchResponse searchResponse = client().prepareSearch("-test2").get(); - assertReturnedIndices(searchResponse, "test1", "test3"); - } - - public void testWildcardExclusion() { - //index1 is not authorized and referred to through wildcard, test2 is excluded - createIndices("test1", "test2", "test21", "test3", "index1"); - - SearchResponse searchResponse = client().prepareSearch("-test2*").get(); - assertReturnedIndices(searchResponse, "test1", "test3"); - } - - public void testInclusionAndWildcardsExclusion() { - //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded - createIndices("test1", "test10", "test111", "test112", "test2", "index1"); - - SearchResponse searchResponse = client().prepareSearch("test1*", "index*", "-test11*").get(); - assertReturnedIndices(searchResponse, "test1", "test10"); - } - - public void testExplicitAndWildcardsInclusionAndWildcardExclusion() { - //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded - createIndices("test1", "test10", "test111", "test112", "test2", "index1"); - - SearchResponse searchResponse = client().prepareSearch("+test2", "+test11*", "index*", "-test2*").get(); - assertReturnedIndices(searchResponse, "test111", "test112"); - } - - public void testExplicitAndWildcardInclusionAndExplicitExclusions() { - //index1 is not authorized and referred to through wildcard, test111 and test112 are excluded - createIndices("test1", "test10", "test111", "test112", "test2", "index1"); - - SearchResponse searchResponse = client().prepareSearch("+test10", "+test11*", "index*", "-test111", "-test112").get(); - assertReturnedIndices(searchResponse, "test10"); - } - - - public void testMissingDateMath() { - expectThrows(IndexNotFoundException.class, () -> client().prepareSearch("").get()); - } - - public void testIndicesExists() { - createIndices("test1", "test2", "test3"); - - assertEquals(true, client().admin().indices().prepareExists("*").get().isExists()); - - assertEquals(true, client().admin().indices().prepareExists("_all").get().isExists()); - - assertEquals(true, client().admin().indices().prepareExists("test1", "test2").get().isExists()); - - assertEquals(true, client().admin().indices().prepareExists("test*").get().isExists()); - - assertEquals(false, client().admin().indices().prepareExists("does_not_exist").get().isExists()); - - assertEquals(false, client().admin().indices().prepareExists("does_not_exist*").get().isExists()); - } - - public void testMultiSearchUnauthorizedIndex() { - //index1 is not authorized, the whole request fails due to that - createIndices("test1", "test2", "test3", "index1"); - assertThrowsAuthorizationException(client().prepareMultiSearch() - .add(Requests.searchRequest()) - .add(Requests.searchRequest("index1"))); - } - - public void testMultiSearchMissingUnauthorizedIndex() { - //index missing and not authorized, the whole request fails due to that - createIndices("test1", "test2", "test3", "index1"); - assertThrowsAuthorizationException(client().prepareMultiSearch() - .add(Requests.searchRequest()) - .add(Requests.searchRequest("missing"))); - } - - public void testMultiSearchMissingAuthorizedIndex() { - //test4 is missing but authorized, only that specific item fails - createIndices("test1", "test2", "test3", "index1"); - MultiSearchResponse multiSearchResponse = client().prepareMultiSearch() - .add(Requests.searchRequest()) - .add(Requests.searchRequest("test4")).get(); - assertReturnedIndices(multiSearchResponse.getResponses()[0].getResponse(), "test1", "test2", "test3"); - assertThat(multiSearchResponse.getResponses()[1].getFailure().toString(), equalTo("[test4] IndexNotFoundException[no such index]")); - } - - @AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") - public void testMultiSearchWildcard() { - //test4 is missing but authorized, only that specific item fails - createIndices("test1", "test2", "test3", "index1"); - IndexNotFoundException e = expectThrows(IndexNotFoundException.class, - () -> client().prepareMultiSearch().add(Requests.searchRequest()) - .add(Requests.searchRequest("index*")).get()); - assertEquals("no such index", e.getMessage()); - } - - private static void assertReturnedIndices(SearchResponse searchResponse, String... indices) { - List foundIndices = new ArrayList<>(); - for (SearchHit searchHit : searchResponse.getHits().getHits()) { - foundIndices.add(searchHit.index()); - } - assertThat(foundIndices.size(), equalTo(indices.length)); - assertThat(foundIndices, hasItems(indices)); - } - - private static void assertThrowsAuthorizationException(ActionRequestBuilder actionRequestBuilder) { - ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, actionRequestBuilder::get); - assertAuthorizationException(e, containsString("is unauthorized for user [")); - } - - private void createIndices(String... indices) { - if (randomBoolean()) { - //no aliases - createIndex(indices); - } else { - if (randomBoolean()) { - //one alias per index with suffix "-alias" - for (String index : indices) { - client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias(index + "-alias")); - } - } else { - //same alias pointing to all indices - for (String index : indices) { - client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias("alias")); - } - } - } - - for (String index : indices) { - client().prepareIndex(index, "type").setSource("field", "value").get(); - } - refresh(); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java index cabd1c68ec8..3545fc12d9d 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java @@ -80,6 +80,8 @@ public class SuperuserRoleTests extends ESTestCase { authzMap = SuperuserRole.INSTANCE.indices().authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), metaData); assertThat(authzMap.get("aaaaaa").isGranted(), is(true)); assertThat(authzMap.get("b").isGranted(), is(true)); + assertTrue(SuperuserRole.INSTANCE.indices().check(SearchAction.NAME)); + assertFalse(SuperuserRole.INSTANCE.indices().check("unknown")); } public void testRunAs() { diff --git a/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityIT.java b/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityIT.java index 933b170ecab..be65da8d5f8 100644 --- a/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityIT.java +++ b/qa/reindex-tests-with-security/src/test/java/org/elasticsearch/xpack/security/ReindexWithSecurityIT.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security; -import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; @@ -60,7 +59,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase { } public void testDeleteByQuery() { - createIndices("test1", "test2", "test3"); + createIndicesWithRandomAliases("test1", "test2", "test3"); BulkIndexByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get(); assertNotNull(response); @@ -74,7 +73,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase { } public void testUpdateByQuery() { - createIndices("test1", "test2", "test3"); + createIndicesWithRandomAliases("test1", "test2", "test3"); BulkIndexByScrollResponse response = UpdateByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get(); assertNotNull(response); @@ -88,7 +87,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase { } public void testReindex() { - createIndices("test1", "test2", "test3", "dest"); + createIndicesWithRandomAliases("test1", "test2", "test3", "dest"); BulkIndexByScrollResponse response = ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2") .destination("dest").get(); @@ -101,28 +100,4 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase { () -> ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "index1").destination("dest").get()); assertEquals("no such index", e.getMessage()); } - - private void createIndices(String... indices) { - if (randomBoolean()) { - //no aliases - createIndex(indices); - } else { - if (randomBoolean()) { - //one alias per index with suffix "-alias" - for (String index : indices) { - client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias(index + "-alias")); - } - } else { - //same alias pointing to all indices - for (String index : indices) { - client().admin().indices().prepareCreate(index).setSettings(indexSettings()).addAlias(new Alias("alias")); - } - } - } - - for (String index : indices) { - client().prepareIndex(index, "type").setSource("field", "value").get(); - } - refresh(); - } } diff --git a/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/10_reindex.yaml b/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/10_reindex.yaml index 0c74bc19585..632427b54ac 100644 --- a/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/10_reindex.yaml +++ b/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/10_reindex.yaml @@ -167,12 +167,11 @@ search: index: other_dest - # Even the authorized index won't have made it because it was in the same batch as the unauthorized one. - # If there had been lots of documents being copied then some might have made it into the authorized index. - do: - catch: missing search: index: dest + - length: { hits.hits: 1 } + - match: { hits.hits.0._index: dest } --- "Reindex misses hidden docs": diff --git a/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/15_reindex_from_remote.yaml b/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/15_reindex_from_remote.yaml index 9b6f51ed21e..c49323429ab 100644 --- a/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/15_reindex_from_remote.yaml +++ b/qa/reindex-tests-with-security/src/test/resources/rest-api-spec/test/15_reindex_from_remote.yaml @@ -212,12 +212,11 @@ search: index: other_dest - # Even the authorized index won't have made it because it was in the same batch as the unauthorized one. - # If there had been lots of documents being copied then some might have made it into the authorized index. - do: - catch: missing search: index: dest + - length: { hits.hits: 1 } + - match: { hits.hits.0._index: dest } --- "Reindex from remote misses hidden docs":