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":