Authorize composite actions based on their action name only, subrequests and their indices will be later authorized individually

Eagerly authorizing CompositeIndicesRequests allowed the security plugin to fail fast up until now, but it makes it very hard to reason about each specific item in a multi items request. Either all items fail, or none do. We would rather want to adopt a similar behaviour to es core, where individual items fail without affecting other items that are part of the same request. We can rely on the fact that es core always authorizes both main action and every subaction too, and skip authorization for the main action. By subaction we mean either all sub search requests in msearch, as well as each shard level get in mget or shard level bulk request for bulk.

 BulkRequestInterceptor was converted to intercept BulkShardRequests rather than BulkRequest as that is where bulk is authorized after this change.

 Split IndicesAndAliasesResolverIntegrationTests into ReadActionsTests and WriteActionsTests as they require different set of permissions, lots of tests added.

Explicitly listing the composite actions makes sure that the actions that can bypass security are known, somebody adding a similar action must to add it to the list, so we know it doesn't happen by mistake. At this point the CompositeIndicesRequest can be used as a marker interface only (it is not really needed but can be used to verify that composite actions use a request that implements such interface).

Given that we don't authorize composite actions based on their indices anymore, but only their sub-requests which implement IndicesRequest, printing out the indices names in the audit log for requests like bulk and msearch is confusing. Removed support for that.

Authorize composite indices actions based on their name only, their indices will be authorized at the sub-request/shard level

Rather than simply granting bulk, mget, msearch etc. and relying on authorization at the sub-request/shard level, we check that the current user can at least execute the action. This justifies the grant line that gets written in the audit log, the action is potentially possible without looking at the indices. Each specific item will fail or succeed later and will yield its own specific audit log entry.

Original commit: elastic/x-pack-elasticsearch@4570caf019
This commit is contained in:
javanna 2016-10-05 11:58:27 +02:00 committed by Luca Cavanna
parent c6edec254a
commit 4bb6e856f3
24 changed files with 1066 additions and 684 deletions

View File

@ -9,7 +9,7 @@ import org.elasticsearch.common.inject.multibindings.Multibinder;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter; 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.FieldStatsRequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.SearchRequestInterceptor; 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)) { if (XPackSettings.DLS_FLS_ENABLED.get(settings)) {
multibinder.addBinding().to(SearchRequestInterceptor.class); multibinder.addBinding().to(SearchRequestInterceptor.class);
multibinder.addBinding().to(UpdateRequestInterceptor.class); multibinder.addBinding().to(UpdateRequestInterceptor.class);
multibinder.addBinding().to(BulkRequestInterceptor.class); multibinder.addBinding().to(BulkShardRequestInterceptor.class);
multibinder.addBinding().to(FieldStatsRequestInterceptor.class); multibinder.addBinding().to(FieldStatsRequestInterceptor.class);
} }
} }

View File

@ -6,8 +6,8 @@
package org.elasticsearch.xpack.security.action.interceptor; package org.elasticsearch.xpack.security.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.bulk.BulkItemRequest;
import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkShardRequest;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject; 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.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus; 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.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest; 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. * Similar to {@link UpdateRequestInterceptor}, but checks if there are update requests embedded in a bulk request.
*/ */
public class BulkRequestInterceptor extends AbstractComponent implements RequestInterceptor<BulkRequest> { public class BulkShardRequestInterceptor extends AbstractComponent implements RequestInterceptor<BulkShardRequest> {
private final ThreadContext threadContext; private final ThreadContext threadContext;
private final XPackLicenseState licenseState; private final XPackLicenseState licenseState;
@Inject @Inject
public BulkRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { public BulkShardRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
super(settings); super(settings);
this.threadContext = threadPool.getThreadContext(); this.threadContext = threadPool.getThreadContext();
this.licenseState = licenseState; this.licenseState = licenseState;
} }
public void intercept(BulkRequest request, User user) { public void intercept(BulkShardRequest request, User user) {
if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) {
return; return;
} }
IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY); IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY);
for (IndicesRequest indicesRequest : request.subRequests()) { for (BulkItemRequest bulkItemRequest : request.items()) {
for (String index : indicesRequest.indices()) { IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(bulkItemRequest.index());
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
if (indexAccessControl != null) { if (indexAccessControl != null) {
boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
boolean dls = indexAccessControl.getQueries() != null; boolean dls = indexAccessControl.getQueries() != null;
if (fls || dls) { if (fls || dls) {
if (indicesRequest instanceof UpdateRequest) { if (bulkItemRequest.request() instanceof UpdateRequest) {
throw new ElasticsearchSecurityException("Can't execute a bulk request with update requests embedded if " + throw new ElasticsearchSecurityException("Can't execute a bulk request with update requests embedded if " +
"field or document level security is enabled", RestStatus.BAD_REQUEST); "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 @Override
public boolean supports(TransportRequest request) { public boolean supports(TransportRequest request) {
return request instanceof BulkRequest; return request instanceof BulkShardRequest;
} }
} }

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.xpack.security.audit; package org.elasticsearch.xpack.security.audit;
import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestRequest;
@ -13,7 +12,6 @@ import org.elasticsearch.transport.TransportMessage;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -33,16 +31,6 @@ public class AuditUtil {
public static Set<String> indices(TransportMessage message) { public static Set<String> indices(TransportMessage message) {
if (message instanceof IndicesRequest) { if (message instanceof IndicesRequest) {
return arrayToSetOrNull(((IndicesRequest) message).indices()); return arrayToSetOrNull(((IndicesRequest) message).indices());
} else if (message instanceof CompositeIndicesRequest) {
Set<String> 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; return null;
} }

View File

@ -11,10 +11,14 @@ import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.get.MultiGetAction;
import org.elasticsearch.action.search.ClearScrollAction; import org.elasticsearch.action.search.ClearScrollAction;
import org.elasticsearch.action.search.MultiSearchAction;
import org.elasticsearch.action.search.SearchScrollAction; import org.elasticsearch.action.search.SearchScrollAction;
import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.SearchTransportService;
import org.elasticsearch.action.support.replication.TransportReplicationAction.ConcreteShardRequest; import org.elasticsearch.action.support.replication.TransportReplicationAction.ConcreteShardRequest;
import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
@ -227,14 +231,27 @@ public class AuthorizationService extends AbstractComponent {
throw denial(authentication, action, request); 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, // 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 // 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 // (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). // 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, // 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 // we just grant it if it's a scroll, deny otherwise
if (request instanceof IndicesRequest == false && request instanceof CompositeIndicesRequest == false if (request instanceof IndicesRequest == false && request instanceof IndicesAliasesRequest == false) {
&& request instanceof IndicesAliasesRequest == false) {
if (isScrollRelatedAction(action)) { if (isScrollRelatedAction(action)) {
//note that clear scroll shard level actions can originate from a clear scroll all, which doesn't require any //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 //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<String> indexNames = resolveIndices(authentication, action, request, clusterState); Set<String> 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"; 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. //all wildcard expressions have been resolved and only the security plugin could have set '-*' here.
//'-*' matches no indices, hence we can simply let it go through, it will yield an empty response. //'-*' 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)) { if (indexNames.size() == 1 && indexNames.contains(DefaultIndicesAndAliasesResolver.NO_INDEX)) {
setIndicesAccessControl(IndicesAccessControl.ALLOW_NO_INDICES); setIndicesAccessControl(IndicesAccessControl.ALLOW_NO_INDICES);
grant(authentication, action, request); grant(authentication, action, request);
@ -348,6 +365,16 @@ public class AuthorizationService extends AbstractComponent {
throw denial(authentication, action, request); 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) { private static boolean isScrollRelatedAction(String action) {
return action.equals(SearchScrollAction.NAME) || return action.equals(SearchScrollAction.NAME) ||
action.equals(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME) || action.equals(SearchTransportService.FETCH_ID_SCROLL_ACTION_NAME) ||

View File

@ -6,7 +6,6 @@
package org.elasticsearch.xpack.security.authz.indicesresolver; package org.elasticsearch.xpack.security.authz.indicesresolver;
import org.elasticsearch.action.AliasesRequest; import org.elasticsearch.action.AliasesRequest;
import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
@ -62,21 +61,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
return indices; 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 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."); throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be.");
} }
if (request instanceof CompositeIndicesRequest) {
Set<String> 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); return resolveIndicesAndAliases(user, action, (IndicesRequest) request, metaData);
} }

View File

@ -5,11 +5,12 @@
*/ */
package org.elasticsearch.xpack.security.authz.permission; package org.elasticsearch.xpack.security.authz.permission;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.transport.TransportRequest;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.function.Predicate; import java.util.function.Predicate;
/** /**
@ -19,7 +20,7 @@ public interface ClusterPermission extends Permission {
boolean check(String action, TransportRequest request, Authentication authentication); 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) { public static final Core NONE = new Core(ClusterPrivilege.NONE) {
@Override @Override
@ -56,11 +57,11 @@ public interface ClusterPermission extends Permission {
} }
} }
static class Globals implements ClusterPermission { class Globals implements ClusterPermission {
private final List<GlobalPermission> globals; private final List<GlobalPermission> globals;
public Globals(List<GlobalPermission> globals) { Globals(List<GlobalPermission> globals) {
this.globals = globals; this.globals = globals;
} }
@ -70,9 +71,8 @@ public interface ClusterPermission extends Permission {
return false; return false;
} }
for (GlobalPermission global : globals) { for (GlobalPermission global : globals) {
if (global == null || global.cluster() == null) { Objects.requireNonNull(global, "global must not be null");
throw new RuntimeException(); Objects.requireNonNull(global.indices(), "global.indices() must not be null");
}
if (global.cluster().check(action, request, authentication)) { if (global.cluster().check(action, request, authentication)) {
return true; return true;
} }

View File

@ -72,7 +72,7 @@ public class GlobalPermission implements Permission {
public static class Compound extends GlobalPermission { public static class Compound extends GlobalPermission {
public Compound(List<GlobalPermission> globals) { Compound(List<GlobalPermission> globals) {
super(new ClusterPermission.Globals(globals), new IndicesPermission.Globals(globals), new RunAsPermission.Globals(globals)); super(new ClusterPermission.Globals(globals), new IndicesPermission.Globals(globals), new RunAsPermission.Globals(globals));
} }

View File

@ -40,9 +40,20 @@ import static java.util.Collections.unmodifiableSet;
*/ */
public interface IndicesPermission extends Permission, Iterable<IndicesPermission.Group> { public interface IndicesPermission extends Permission, Iterable<IndicesPermission.Group> {
/**
* Authorizes the provided action against the provided indices, given the current cluster metadata
*/
Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases, MetaData metaData); Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> 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() { public static final Core NONE = new Core() {
@Override @Override
@ -101,6 +112,16 @@ public interface IndicesPermission extends Permission, Iterable<IndicesPermissio
return allowedIndicesMatchersForAction.computeIfAbsent(action, loadingFunction); return allowedIndicesMatchersForAction.computeIfAbsent(action, loadingFunction);
} }
@Override
public boolean check(String action) {
for (Group group : groups) {
if (group.check(action)) {
return true;
}
}
return false;
}
@Override @Override
public Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases, public Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases,
MetaData metaData) { MetaData metaData) {
@ -203,6 +224,21 @@ public interface IndicesPermission extends Permission, Iterable<IndicesPermissio
return true; return true;
} }
@Override
public boolean check(String action) {
if (globals == null) {
return false;
}
for (GlobalPermission global : globals) {
Objects.requireNonNull(global, "global must not be null");
Objects.requireNonNull(global.indices(), "global.indices() must not be null");
if (global.indices().check(action)) {
return true;
}
}
return false;
}
@Override @Override
public Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases, public Map<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases,
MetaData metaData) { MetaData metaData) {
@ -321,13 +357,13 @@ public interface IndicesPermission extends Permission, Iterable<IndicesPermissio
return query; return query;
} }
public boolean indexNameMatch(String index) { private boolean check(String action) {
return indexNameMatcher.test(index); return actionMatcher.test(action);
} }
public boolean check(String action, String index) { private boolean check(String action, String index) {
assert index != null; assert index != null;
return actionMatcher.test(action) && indexNameMatcher.test(index); return check(action) && indexNameMatcher.test(index);
} }
public boolean hasQuery() { public boolean hasQuery() {

View File

@ -63,18 +63,16 @@ public abstract class AbstractPrivilegeTestCase extends SecurityIntegTestCase {
protected void assertAccessIsDenied(String user, String method, String uri, String body, protected void assertAccessIsDenied(String user, String method, String uri, String body,
Map<String, String> params) throws IOException { Map<String, String> params) throws IOException {
try { ResponseException responseException = expectThrows(ResponseException.class,
getRestClient().performRequest(method, uri, params, entityOrNull(body), () -> getRestClient().performRequest(method, uri, params, entityOrNull(body),
new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER, new BasicHeader(UsernamePasswordToken.BASIC_AUTH_HEADER,
UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray())))); UsernamePasswordToken.basicAuthHeaderValue(user, new SecuredString("passwd".toCharArray())))));
fail("request should have failed"); StatusLine statusLine = responseException.getResponse().getStatusLine();
} 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, 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())); statusLine.getStatusCode(), statusLine.getReasonPhrase(),
EntityUtils.toString(responseException.getResponse().getEntity()));
assertThat(message, statusLine.getStatusCode(), is(403)); assertThat(message, statusLine.getStatusCode(), is(403));
} }
}
private static HttpEntity entityOrNull(String body) { private static HttpEntity entityOrNull(String body) {
HttpEntity entity = null; HttpEntity entity = null;

View File

@ -7,6 +7,8 @@ package org.elasticsearch.integration;
import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.ElasticsearchSecurityException; 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.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse; 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.global.Global;
import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.Hasher;
import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.SecuredString;
import org.elasticsearch.test.SecurityIntegTestCase;
import java.util.Collections; 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.hasParentQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery; 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.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchHits; 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.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
public class DocumentLevelSecurityTests extends SecurityIntegTestCase { 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")); 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: // With document level security enabled the update in bulk is not allowed:
try { BulkResponse bulkResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue
client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) ("user1", USERS_PASSWD)))
.prepareBulk() .prepareBulk()
.add(new UpdateRequest("test", "type", "1").doc("field1", "value3")) .add(new UpdateRequest("test", "type", "1").doc("field1", "value3"))
.get(); .get();
fail("failed, because bulk request with updates shouldn't be allowed if field or document level security is enabled"); assertEquals(1, bulkResponse.getItems().length);
} catch (ElasticsearchSecurityException e) { BulkItemResponse bulkItem = bulkResponse.getItems()[0];
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST)); assertTrue(bulkItem.isFailed());
assertThat(e.getMessage(), 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")); 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")); assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value2"));
client().prepareBulk() client().prepareBulk()

View File

@ -7,6 +7,8 @@ package org.elasticsearch.integration;
import org.apache.lucene.search.join.ScoreMode; import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.ElasticsearchSecurityException; 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.fieldstats.FieldStatsResponse;
import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse; 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.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; 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")); 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: // With field level security enabled the update in bulk is not allowed:
try { BulkResponse bulkResponse = client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue
client().filterWithHeader(Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))) ("user1", USERS_PASSWD)))
.prepareBulk() .prepareBulk()
.add(new UpdateRequest("test", "type", "1").doc("field2", "value3")) .add(new UpdateRequest("test", "type", "1").doc("field2", "value3"))
.get(); .get();
fail("failed, because bulk request with updates shouldn't be allowed if field level security is enabled"); assertEquals(1, bulkResponse.getItems().length);
} catch (ElasticsearchSecurityException e) { BulkItemResponse bulkItem = bulkResponse.getItems()[0];
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST)); assertTrue(bulkItem.isFailed());
assertThat(e.getMessage(), 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")); 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")); assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value2"));
client().prepareBulk() client().prepareBulk()

View File

@ -34,7 +34,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
private String jsonDoc = "{ \"name\" : \"elasticsearch\", \"body\": \"foo bar\" }"; private String jsonDoc = "{ \"name\" : \"elasticsearch\", \"body\": \"foo bar\" }";
public static final String ROLES = private static final String ROLES =
"all_cluster_role:\n" + "all_cluster_role:\n" +
" cluster: [ all ]\n" + " cluster: [ all ]\n" +
"all_indices_role:\n" + "all_indices_role:\n" +
@ -95,7 +95,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
" privileges: [ index ]\n" + " privileges: [ index ]\n" +
"\n"; "\n";
public static final String USERS = private static final String USERS =
"admin:" + USERS_PASSWD_HASHED + "\n" + "admin:" + USERS_PASSWD_HASHED + "\n" +
"u1:" + USERS_PASSWD_HASHED + "\n" + "u1:" + USERS_PASSWD_HASHED + "\n" +
"u2:" + USERS_PASSWD_HASHED + "\n" + "u2:" + USERS_PASSWD_HASHED + "\n" +
@ -111,7 +111,7 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
"u13:" + USERS_PASSWD_HASHED + "\n" + "u13:" + USERS_PASSWD_HASHED + "\n" +
"u14:" + 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_indices_role:admin,u8\n" +
"all_cluster_role:admin\n" + "all_cluster_role:admin\n" +
"all_a_role:u1,u2,u6\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); assertAccessIsAllowed("admin", "PUT", "/abc/foo/1", jsonDoc, params);
} }
private static String randomIndex() {
return randomFrom("a", "b", "c", "abc");
}
public void testUserU1() throws Exception { public void testUserU1() throws Exception {
// u1 has all_a_role and read_a_role // u1 has all_a_role and read_a_role
assertUserIsAllowed("u1", "all", "a"); assertUserIsAllowed("u1", "all", "a");
assertUserIsDenied("u1", "all", "b"); assertUserIsDenied("u1", "all", "b");
assertUserIsDenied("u1", "all", "c"); 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 { public void testUserU2() throws Exception {
@ -194,6 +205,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsDenied("u2", "monitor", "b"); assertUserIsDenied("u2", "monitor", "b");
assertUserIsDenied("u2", "create_index", "b"); assertUserIsDenied("u2", "create_index", "b");
assertUserIsDenied("u2", "all", "c"); 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 { public void testUserU3() throws Exception {
@ -201,6 +219,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u3", "all", "a"); assertUserIsAllowed("u3", "all", "a");
assertUserIsAllowed("u3", "all", "b"); assertUserIsAllowed("u3", "all", "b");
assertUserIsDenied("u3", "all", "c"); 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 { public void testUserU4() throws Exception {
@ -217,6 +242,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u4", "create_index", "an_index"); assertUserIsAllowed("u4", "create_index", "an_index");
assertUserIsAllowed("u4", "manage", "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 { public void testUserU5() throws Exception {
@ -228,6 +261,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u5", "read", "b"); assertUserIsAllowed("u5", "read", "b");
assertUserIsDenied("u5", "manage", "b"); assertUserIsDenied("u5", "manage", "b");
assertUserIsDenied("u5", "write", "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 { public void testUserU6() throws Exception {
@ -237,6 +278,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsDenied("u6", "manage", "b"); assertUserIsDenied("u6", "manage", "b");
assertUserIsDenied("u6", "write", "b"); assertUserIsDenied("u6", "write", "b");
assertUserIsDenied("u6", "all", "c"); 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 { public void testUserU7() throws Exception {
@ -244,6 +292,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsDenied("u7", "all", "a"); assertUserIsDenied("u7", "all", "a");
assertUserIsDenied("u7", "all", "b"); assertUserIsDenied("u7", "all", "b");
assertUserIsDenied("u7", "all", "c"); 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 { public void testUserU8() throws Exception {
@ -251,6 +306,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u8", "all", "a"); assertUserIsAllowed("u8", "all", "a");
assertUserIsAllowed("u8", "all", "b"); assertUserIsAllowed("u8", "all", "b");
assertUserIsAllowed("u8", "all", "c"); 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 { public void testUserU9() throws Exception {
@ -261,6 +323,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsDenied("u9", "manage", "b"); assertUserIsDenied("u9", "manage", "b");
assertUserIsDenied("u9", "write", "b"); assertUserIsDenied("u9", "write", "b");
assertUserIsDenied("u9", "all", "c"); 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 { public void testUserU11() throws Exception {
@ -276,6 +345,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u11", "create_index", "c"); assertUserIsAllowed("u11", "create_index", "c");
assertUserIsDenied("u11", "data_access", "c"); assertUserIsDenied("u11", "data_access", "c");
assertUserIsDenied("u11", "monitor", "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 { public void testUserU12() throws Exception {
@ -286,6 +363,13 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u12", "data_access", "b"); assertUserIsAllowed("u12", "data_access", "b");
assertUserIsDenied("u12", "manage", "c"); assertUserIsDenied("u12", "manage", "c");
assertUserIsAllowed("u12", "data_access", "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 { public void testUserU13() throws Exception {
@ -300,6 +384,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsAllowed("u13", "read", "b"); assertUserIsAllowed("u13", "read", "b");
assertUserIsDenied("u13", "all", "c"); 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 { public void testUserU14() throws Exception {
@ -314,6 +406,14 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertUserIsDenied("u14", "data_access", "b"); assertUserIsDenied("u14", "data_access", "b");
assertUserIsDenied("u14", "all", "c"); 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 { 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 + "/foo/1/_termvector");
assertAccessIsAllowed(user, "GET", assertAccessIsAllowed(user, "GET",
"/" + index + "/_suggest", "{ \"sgs\" : { \"text\" : \"foo\", \"term\" : { \"field\" : \"body\" } } }"); "/" + 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); assertUserIsAllowed(user, "search", index);
} else { } else {
assertAccessIsDenied(user, "GET", "/" + index + "/_count"); 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 + "/foo/1/_termvector");
assertAccessIsDenied(user, assertAccessIsDenied(user,
"GET", "/" + index + "/_suggest", "{ \"sgs\" : { \"text\" : \"foo\", \"term\" : { \"field\" : \"body\" } } }"); "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); assertUserIsDenied(user, "search", index);
} }
break; break;
@ -452,24 +544,18 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
assertAccessIsAllowed(user, "GET", "/" + index + "/_search"); assertAccessIsAllowed(user, "GET", "/" + index + "/_search");
assertAccessIsAllowed(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " + assertAccessIsAllowed(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " +
"\"term\" : { \"field\" : \"name\" } } }"); "\"term\" : { \"field\" : \"name\" } } }");
assertAccessIsAllowed(user,
"GET", "/" + index + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n");
} else { } else {
assertAccessIsDenied(user, "GET", "/" + index + "/_search"); assertAccessIsDenied(user, "GET", "/" + index + "/_search");
assertAccessIsDenied(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " + assertAccessIsDenied(user, "GET", "/" + index + "/_suggest", "{ \"my-suggestion\" : { \"text\":\"elasticsearch\", " +
"\"term\" : { \"field\" : \"name\" } } }"); "\"term\" : { \"field\" : \"name\" } } }");
assertAccessIsDenied(user,
"GET", "/" + index + "/foo/_msearch", "{}\n{ \"query\" : { \"match_all\" : {} } }\n");
} }
break; break;
case "get" : case "get" :
if (userIsAllowed) { if (userIsAllowed) {
assertAccessIsAllowed(user, "GET", "/" + index + "/foo/1"); assertAccessIsAllowed(user, "GET", "/" + index + "/foo/1");
assertAccessIsAllowed(user, "POST", "/" + index + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } ");
} else { } else {
assertAccessIsDenied(user, "GET", "/" + index + "/foo/1"); assertAccessIsDenied(user, "GET", "/" + index + "/foo/1");
assertAccessIsDenied(user, "POST", "/" + index + "/foo/_mget", "{ \"ids\" : [ \"1\", \"2\" ] } ");
} }
break; break;
@ -498,13 +584,9 @@ public class IndexPrivilegeTests extends AbstractPrivilegeTestCase {
if (userIsAllowed) { if (userIsAllowed) {
assertUserIsAllowed(user, "index", index); assertUserIsAllowed(user, "index", index);
assertUserIsAllowed(user, "delete", index); assertUserIsAllowed(user, "delete", index);
assertAccessIsAllowed(user, "PUT",
"/" + index + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n");
} else { } else {
assertUserIsDenied(user, "index", index); assertUserIsDenied(user, "index", index);
assertUserIsDenied(user, "delete", index); assertUserIsDenied(user, "delete", index);
assertAccessIsDenied(user,
"PUT", "/" + index + "/foo/_bulk", "{ \"index\" : { \"_id\" : \"123\" } }\n{ \"foo\" : \"bar\" }\n");
} }
break; break;

View File

@ -6,9 +6,12 @@
package org.elasticsearch.test; package org.elasticsearch.test;
import org.elasticsearch.AbstractOldXPackIndicesBackwardsCompatibilityTestCase; 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.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; 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.Client;
import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.health.ClusterHealthStatus; 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.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.xpack.XPackClient;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.XPackSettings;
import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.InternalClient;
import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authc.support.SecuredString;
import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.xpack.security.client.SecurityClient;
import org.elasticsearch.xpack.XPackClient;
import org.elasticsearch.xpack.XPackPlugin;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -38,8 +41,9 @@ import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; 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.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.Matchers.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem; 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(); ClusterHealthResponse clusterHealthResponse = client.admin().cluster().prepareHealth().get();
assertNoTimeout(clusterHealthResponse); assertNoTimeout(clusterHealthResponse);
assertThat(clusterHealthResponse.getStatus(), is(ClusterHealthStatus.GREEN)); 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 @Override
protected Function<Client,Client> getClientWrapper() { protected Function<Client,Client> getClientWrapper() {
Map<String, String> headers = Collections.singletonMap("Authorization", Map<String, String> headers = Collections.singletonMap("Authorization",

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.xpack.security.audit; package org.elasticsearch.xpack.security.audit;
import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -13,7 +12,6 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportMessage; import org.elasticsearch.transport.TransportMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -44,34 +42,6 @@ public class AuditUtilTests extends ESTestCase {
assertThat(result, hasItems(uniqueExpectedIndices.toArray(Strings.EMPTY_ARRAY))); 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<String> expectedIndices = new ArrayList<>();
List<IndicesRequest> indicesRequests = new ArrayList<>(numberOfIndicesRequests);
for (int i = 0; i < numberOfIndicesRequests; i++) {
final int numberOfIndices = randomIntBetween(1, 12);
List<String> 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<String> uniqueExpectedIndices = new HashSet<>(expectedIndices);
final Set<String> 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 static class MockIndicesRequest extends TransportMessage implements IndicesRequest {
private final String[] indices; private final String[] indices;
@ -90,18 +60,4 @@ public class AuditUtilTests extends ESTestCase {
return null; return null;
} }
} }
private static class MockCompositeIndicesRequest extends TransportMessage implements CompositeIndicesRequest {
private final List<? extends IndicesRequest> requests;
private MockCompositeIndicesRequest(List<? extends IndicesRequest> requests) {
this.requests = requests;
}
@Override
public List<? extends IndicesRequest> subRequests() {
return requests;
}
}
} }

View File

@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.authz;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.Version; 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.ClusterHealthAction;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.indices.alias.Alias; 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.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusAction; import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusAction;
import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusRequest; import org.elasticsearch.action.admin.indices.upgrade.get.UpgradeStatusRequest;
import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.delete.DeleteAction;
import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.get.GetAction;
import org.elasticsearch.action.get.GetRequest; 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.IndexAction;
import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.ClearScrollAction; import org.elasticsearch.action.search.ClearScrollAction;
import org.elasticsearch.action.search.ClearScrollRequest; 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.SearchAction;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchScrollAction; import org.elasticsearch.action.search.SearchScrollAction;
import org.elasticsearch.action.search.SearchScrollRequest; import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.action.search.SearchTransportService; import org.elasticsearch.action.search.SearchTransportService;
import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.termvectors.MultiTermVectorsAction;
import org.elasticsearch.action.termvectors.MultiTermVectorsRequest;
import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsAction;
import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.termvectors.TermVectorsRequest;
import org.elasticsearch.action.update.UpdateAction; 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.collect.Tuple;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
@ -83,6 +93,7 @@ import org.junit.Before;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException;
@ -587,9 +598,9 @@ public class AuthorizationServiceTests extends ESTestCase {
verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request); verify(auditTrail).accessGranted(user, ClusterHealthAction.NAME, request);
SearchRequest searchRequest = new SearchRequest("_all"); SearchRequest searchRequest = new SearchRequest("_all");
IndexNotFoundException e = expectThrows(IndexNotFoundException.class, authorizationService.authorize(createAuthentication(user), SearchAction.NAME, searchRequest);
() -> authorizationService.authorize(createAuthentication(user), SearchAction.NAME, searchRequest)); assertEquals(1, searchRequest.indices().length);
assertThat(e.getMessage(), containsString("no such index")); assertEquals(DefaultIndicesAndAliasesResolver.NO_INDEX, searchRequest.indices()[0]);
} }
public void testGrantedNonXPackUserCanExecuteMonitoringOperationsAgainstSecurityIndex() { 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() { public void testAnonymousRolesAreAppliedToOtherUsers() {
TransportRequest request = new ClusterHealthRequest(); TransportRequest request = new ClusterHealthRequest();
ClusterState state = mock(ClusterState.class); ClusterState state = mock(ClusterState.class);
@ -688,31 +722,136 @@ public class AuthorizationServiceTests extends ESTestCase {
authorizationService.authorize(createAuthentication(userWithNoRoles), IndicesExistsAction.NAME, new IndicesExistsRequest("a")); authorizationService.authorize(createAuthentication(userWithNoRoles), IndicesExistsAction.NAME, new IndicesExistsRequest("a"));
} }
public void testXPackUserAndSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() { public void testCompositeActionsAreImmediatelyRejected() {
final User superuser = new User("custom_admin", SuperuserRole.NAME); //if the user has no permission for composite actions against any index, the request fails straight-away in the main action
when(rolesStore.role(SuperuserRole.NAME)).thenReturn(Role.builder(SuperuserRole.DESCRIPTOR).build()); Tuple<String, TransportRequest> compositeRequest = randomCompositeRequest();
ClusterState state = mock(ClusterState.class); String action = compositeRequest.v1();
when(clusterService.state()).thenReturn(state); TransportRequest request = compositeRequest.v2();
when(state.metaData()).thenReturn(MetaData.builder() User user = new User("test user", "no_indices");
.put(new IndexMetaData.Builder(SecurityTemplateService.SECURITY_INDEX_NAME) when(rolesStore.role("no_indices")).thenReturn(Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build());
.settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
.numberOfShards(1).numberOfReplicas(0).build(), true) () -> authorizationService.authorize(createAuthentication(user), action, request));
.build()); assertThat(securityException.status(), is(RestStatus.FORBIDDEN));
assertThat(securityException.getMessage(), containsString("[" + action + "] is unauthorized for user [" + user.principal() + "]"));
String action = SearchAction.NAME; verify(auditTrail).accessDenied(user, action, request);
SearchRequest request = new SearchRequest("_all"); verifyNoMoreInteractions(auditTrail);
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"));
} }
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<String, TransportRequest> 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<String, TransportRequest> 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<? extends IndicesRequest> subRequests() {
return Collections.singletonList(new MockIndicesRequest());
}
}
private static Authentication createAuthentication(User user) {
RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by"); RealmRef lookedUpBy = user.runAs() == null ? null : new RealmRef("looked", "up", "by");
return new Authentication(user, new RealmRef("test", "test", "foo"), lookedUpBy); 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;
}
} }

View File

@ -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("<logstash-{now/M}>").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<String> foundIndices = new ArrayList<>();
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
foundIndices.add(searchHit.index());
}
assertThat(foundIndices.size(), equalTo(indices.length));
assertThat(foundIndices, hasItems(indices));
}
}

View File

@ -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"));
}
}

View File

@ -126,6 +126,9 @@ public class IndicesPermissionTests extends ESTestCase {
// did not define anything for ba so we allow all // did not define anything for ba so we allow all
assertFalse(authzMap.get("ba").getFieldPermissions().hasFieldLevelSecurity()); assertFalse(authzMap.get("ba").getFieldPermissions().hasFieldLevelSecurity());
assertTrue(core.check(SearchAction.NAME));
assertFalse(core.check("unknown"));
// test with two indices // test with two indices
group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1"); group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1");
group2 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(null, new 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) + "_field"));
assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field2")); assertTrue(authzMap.get("a2").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5) + "_field2"));
assertTrue(authzMap.get("a2").getFieldPermissions().hasFieldLevelSecurity()); assertTrue(authzMap.get("a2").getFieldPermissions().hasFieldLevelSecurity());
assertTrue(core.check(SearchAction.NAME));
assertFalse(core.check("unknown"));
} }
} }

View File

@ -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.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsAction; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsAction;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; 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.get.MultiGetRequest;
import org.elasticsearch.action.search.MultiSearchAction; import org.elasticsearch.action.search.MultiSearchAction;
import org.elasticsearch.action.search.MultiSearchRequest; import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.support.IndicesOptions; 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.ClusterState;
import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -34,11 +34,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.SecurityTemplateService; 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.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler;
import org.elasticsearch.xpack.security.authz.AuthorizationService; 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.permission.SuperuserRole;
import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege;
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; 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 org.junit.Before;
import java.util.Set; import java.util.Set;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.arrayContainingInAnyOrder; import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
@ -880,137 +880,10 @@ public class DefaultIndicesResolverTests extends ESTestCase {
assertEquals("no such index", e.getMessage()); assertEquals("no such index", e.getMessage());
} }
//msearch is a CompositeIndicesRequest whose items (SearchRequests) implement IndicesRequest.Replaceable, wildcards will get replaced public void testCompositeIndicesRequestIsNotSupported() {
@AwaitsFix(bugUrl = "multi requests endpoints need fixing, we shouldn't merge all the indices in one collection") TransportRequest request = randomFrom(new MultiSearchRequest(), new MultiGetRequest(),
public void testResolveMultiSearchNoWildcards() { new MultiTermVectorsRequest(), new BulkRequest());
MultiSearchRequest request = new MultiSearchRequest(); expectThrows(IllegalStateException.class, () -> defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData));
request.add(Requests.searchRequest("foo", "bar"));
request.add(Requests.searchRequest("bar2"));
Set<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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 testResolveAdminAction() { public void testResolveAdminAction() {
@ -1139,34 +1012,6 @@ public class DefaultIndicesResolverTests extends ESTestCase {
assertThat(request.aliases(), arrayContainingInAnyOrder("<datetime-{now/M}>")); assertThat(request.aliases(), arrayContainingInAnyOrder("<datetime-{now/M}>"));
} }
public void testCompositeRequestsCanResolveExpressions() {
// make the user authorized
String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed",
indexNameExpressionResolver.resolveDateMathExpression("<datetime-{now/M}>")};
when(rolesStore.role("role")).thenReturn(Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build());
MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
multiSearchRequest.add(new SearchRequest("<datetime-{now/M}>"));
multiSearchRequest.add(new SearchRequest("bar"));
Set<String> indices = defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, multiSearchRequest, metaData);
String resolvedName = indexNameExpressionResolver.resolveDateMathExpression("<datetime-{now/M}>");
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("<datetime-{now/M}>", "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("<datetime-{now/M}>"));
assertThat(multiGetRequest.getItems().get(1).indices(), arrayContaining("bar"));
}
// TODO with the removal of DeleteByQuery is there another way to test resolving a write action? // TODO with the removal of DeleteByQuery is there another way to test resolving a write action?
private static IndexMetaData.Builder indexBuilder(String index) { private static IndexMetaData.Builder indexBuilder(String index) {

View File

@ -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("<logstash-{now/M}>").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<String> 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();
}
}

View File

@ -80,6 +80,8 @@ public class SuperuserRoleTests extends ESTestCase {
authzMap = SuperuserRole.INSTANCE.indices().authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), metaData); authzMap = SuperuserRole.INSTANCE.indices().authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), metaData);
assertThat(authzMap.get("aaaaaa").isGranted(), is(true)); assertThat(authzMap.get("aaaaaa").isGranted(), is(true));
assertThat(authzMap.get("b").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() { public void testRunAs() {

View File

@ -5,7 +5,6 @@
*/ */
package org.elasticsearch.xpack.security; package org.elasticsearch.xpack.security;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.common.network.NetworkModule; import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
@ -60,7 +59,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase {
} }
public void testDeleteByQuery() { public void testDeleteByQuery() {
createIndices("test1", "test2", "test3"); createIndicesWithRandomAliases("test1", "test2", "test3");
BulkIndexByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get(); BulkIndexByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get();
assertNotNull(response); assertNotNull(response);
@ -74,7 +73,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase {
} }
public void testUpdateByQuery() { public void testUpdateByQuery() {
createIndices("test1", "test2", "test3"); createIndicesWithRandomAliases("test1", "test2", "test3");
BulkIndexByScrollResponse response = UpdateByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get(); BulkIndexByScrollResponse response = UpdateByQueryAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2").get();
assertNotNull(response); assertNotNull(response);
@ -88,7 +87,7 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase {
} }
public void testReindex() { public void testReindex() {
createIndices("test1", "test2", "test3", "dest"); createIndicesWithRandomAliases("test1", "test2", "test3", "dest");
BulkIndexByScrollResponse response = ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2") BulkIndexByScrollResponse response = ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "test2")
.destination("dest").get(); .destination("dest").get();
@ -101,28 +100,4 @@ public class ReindexWithSecurityIT extends SecurityIntegTestCase {
() -> ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "index1").destination("dest").get()); () -> ReindexAction.INSTANCE.newRequestBuilder(client()).source("test1", "index1").destination("dest").get());
assertEquals("no such index", e.getMessage()); 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();
}
} }

View File

@ -167,12 +167,11 @@
search: search:
index: other_dest 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: - do:
catch: missing
search: search:
index: dest index: dest
- length: { hits.hits: 1 }
- match: { hits.hits.0._index: dest }
--- ---
"Reindex misses hidden docs": "Reindex misses hidden docs":

View File

@ -212,12 +212,11 @@
search: search:
index: other_dest 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: - do:
catch: missing
search: search:
index: dest index: dest
- length: { hits.hits: 1 }
- match: { hits.hits.0._index: dest }
--- ---
"Reindex from remote misses hidden docs": "Reindex from remote misses hidden docs":