Internal: replace wildcard expressions and _all with matching indices that the current user is authorized for
Two reasons for this: 1) automatically convert the _all to its matching indices, in the context of the current user is authorized for, instead of resolving wildcards and then throwing authorization exception because the wildcard exp matches indices that the user is not authorized for 2) this makes the wildcards resolution secure, meaning that there is a single place that resolve wildcards. If it happened in shield while authorizing and in core while actually executing the operation, there would be mismatches which would allow to execute operation on indices that the user is not authorized for, if they get created with the "right" timing. Closes elastic/elasticsearch#54 Closes elastic/elasticsearch#105 Original commit: elastic/x-pack-elasticsearch@a02c6fbccf
This commit is contained in:
parent
c02277283c
commit
11ff005dc3
|
@ -72,7 +72,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
|
||||
if (user.isSystem()) {
|
||||
MetaData metaData = clusterService.state().metaData();
|
||||
if (SystemRole.INSTANCE.check(action, request, metaData)) {
|
||||
if (SystemRole.INSTANCE.check(user, action, request, metaData)) {
|
||||
grant(user, action, request);
|
||||
} else {
|
||||
deny(user, action, request);
|
||||
|
@ -88,7 +88,7 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
MetaData metaData = clusterService.state().metaData();
|
||||
for (String role : roles) {
|
||||
Permission permission = rolesStore.permission(role);
|
||||
if (permission != null && permission.check(action, request, metaData)) {
|
||||
if (permission != null && permission.check(user, action, request, metaData)) {
|
||||
grant(user, action, request);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import org.elasticsearch.action.IndicesRequest;
|
|||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.base.Predicate;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authz.indicesresolver.DefaultIndicesResolver;
|
||||
import org.elasticsearch.shield.authz.indicesresolver.IndicesResolver;
|
||||
import org.elasticsearch.shield.support.AutomatonPredicate;
|
||||
|
@ -38,7 +39,7 @@ import java.util.Set;
|
|||
*/
|
||||
public interface Permission {
|
||||
|
||||
boolean check(String action, TransportRequest request, MetaData metaData);
|
||||
boolean check(User user, String action, TransportRequest request, MetaData metaData);
|
||||
|
||||
static class Global implements Permission {
|
||||
|
||||
|
@ -62,26 +63,29 @@ public interface Permission {
|
|||
return indices;
|
||||
}
|
||||
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
if (cluster != null && cluster.check(action, request, metaData)) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
if (cluster != null && cluster.check(user, action, request, metaData)) {
|
||||
return true;
|
||||
}
|
||||
if (indices != null && indices.check(action, request, metaData)) {
|
||||
if (indices != null && indices.check(user, action, request, metaData)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
public static Builder builder(AuthorizationService authzService) {
|
||||
return new Builder(authzService);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final AuthorizationService authzService;
|
||||
|
||||
private Cluster cluster = Cluster.NONE;
|
||||
private ImmutableList.Builder<Indices.Group> groups;
|
||||
|
||||
private Builder() {
|
||||
private Builder(AuthorizationService authzService) {
|
||||
this.authzService = authzService;
|
||||
}
|
||||
|
||||
public Builder set(Privilege.Cluster privilege) {
|
||||
|
@ -98,7 +102,7 @@ public interface Permission {
|
|||
}
|
||||
|
||||
public Global build() {
|
||||
Indices indices = groups != null ? new Indices(groups.build()) : Indices.NONE;
|
||||
Indices indices = groups != null ? new Indices(authzService, groups.build()) : Indices.NONE;
|
||||
return new Global(cluster, indices);
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +112,7 @@ public interface Permission {
|
|||
|
||||
public static final Cluster NONE = new Cluster(Privilege.Cluster.NONE) {
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -126,7 +130,7 @@ public interface Permission {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
return predicate.apply(action);
|
||||
}
|
||||
}
|
||||
|
@ -135,24 +139,26 @@ public interface Permission {
|
|||
|
||||
public static final Indices NONE = new Indices() {
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
static final IndicesResolver[] indicesResolvers = new IndicesResolver[] {
|
||||
// add special resolvers here
|
||||
new DefaultIndicesResolver()
|
||||
};
|
||||
private final IndicesResolver[] indicesResolvers;
|
||||
private final Group[] groups;
|
||||
|
||||
private Group[] groups;
|
||||
|
||||
public Indices(Collection<Group> groups) {
|
||||
this(groups.toArray(new Group[groups.size()]));
|
||||
private Indices() {
|
||||
this.indicesResolvers = new IndicesResolver[0];
|
||||
this.groups = new Group[0];
|
||||
}
|
||||
|
||||
public Indices(Group... groups) {
|
||||
this.groups = groups;
|
||||
|
||||
public Indices(AuthorizationService authzService, Collection<Group> groups) {
|
||||
this.groups = groups.toArray(new Group[groups.size()]);
|
||||
this.indicesResolvers = new IndicesResolver[] {
|
||||
// add special resolvers here
|
||||
new DefaultIndicesResolver(authzService)
|
||||
};
|
||||
}
|
||||
|
||||
public Group[] groups() {
|
||||
|
@ -188,7 +194,7 @@ public interface Permission {
|
|||
}
|
||||
|
||||
@Override @SuppressWarnings("unchecked")
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
|
||||
// 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
|
||||
|
@ -202,14 +208,14 @@ public interface Permission {
|
|||
indices = Collections.emptySet();
|
||||
for (IndicesResolver resolver : indicesResolvers) {
|
||||
if (resolver.requestType().isInstance(request)) {
|
||||
indices = resolver.resolve(request, metaData);
|
||||
indices = resolver.resolve(user, action, request, metaData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
if (groups[i].check(action, indices)) {
|
||||
for (Group group : groups) {
|
||||
if (group.check(action, indices)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.shield.authz;
|
|||
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.base.Predicate;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
/**
|
||||
|
@ -27,7 +28,7 @@ public class SystemRole extends Permission.Global {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean check(String action, TransportRequest request, MetaData metaData) {
|
||||
public boolean check(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
return PREDICATE.apply(action);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,25 +7,38 @@ package org.elasticsearch.shield.authz.indicesresolver;
|
|||
|
||||
import org.elasticsearch.action.CompositeIndicesRequest;
|
||||
import org.elasticsearch.action.IndicesRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.collect.Sets;
|
||||
import org.elasticsearch.common.regex.Regex;
|
||||
import org.elasticsearch.index.Index;
|
||||
import org.elasticsearch.indices.IndexMissingException;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class DefaultIndicesResolver implements IndicesResolver<TransportRequest> {
|
||||
|
||||
private final AuthorizationService authzService;
|
||||
|
||||
public DefaultIndicesResolver(AuthorizationService authzService) {
|
||||
this.authzService = authzService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<TransportRequest> requestType() {
|
||||
return TransportRequest.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> resolve(TransportRequest request, MetaData metaData) {
|
||||
public Set<String> resolve(User user, String action, TransportRequest request, MetaData metaData) {
|
||||
|
||||
boolean isIndicesRequest = request instanceof CompositeIndicesRequest || request instanceof IndicesRequest;
|
||||
assert isIndicesRequest : "Request [" + request + "] is not an Indices request. The only requests passing the action matcher should be IndicesRequests";
|
||||
|
@ -39,28 +52,130 @@ public class DefaultIndicesResolver implements IndicesResolver<TransportRequest>
|
|||
Set<String> indices = Sets.newHashSet();
|
||||
CompositeIndicesRequest compositeIndicesRequest = (CompositeIndicesRequest) request;
|
||||
for (IndicesRequest indicesRequest : compositeIndicesRequest.subRequests()) {
|
||||
Collections.addAll(indices, explodeWildcards(indicesRequest, metaData));
|
||||
indices.addAll(resolveIndices(user, action, indicesRequest, metaData));
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
|
||||
return Sets.newHashSet(explodeWildcards((IndicesRequest) request, metaData));
|
||||
return resolveIndices(user, action, (IndicesRequest) request, metaData);
|
||||
}
|
||||
|
||||
private Set<String> resolveIndices(User user, String action, IndicesRequest indicesRequest, MetaData metaData) {
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen() || indicesRequest.indicesOptions().expandWildcardsClosed()) {
|
||||
if (indicesRequest instanceof IndicesRequest.Replaceable) {
|
||||
ImmutableList<String> authorizedIndices = authzService.authorizedIndicesAndAliases(user, action);
|
||||
List<String> indices = replaceWildcardsWithAuthorizedIndices(indicesRequest, metaData, authorizedIndices);
|
||||
//ignore the IndicesOptions#allowNoIndices and just throw exception if the wildcards expansion to authorized
|
||||
//indices resulted in no indices. This is important as we always need to replace wildcards for security reason,
|
||||
//to make sure that the operation is executed on the indices that we authorized it to execute on.
|
||||
//If we can't replace because we got an empty set, we can only throw exception.
|
||||
//Downside of this is that a single item exception is going to its composite requests fail as a whole.
|
||||
if (indices == null || indices.isEmpty()) {
|
||||
throw new IndexMissingException(new Index(Arrays.toString(indicesRequest.indices())));
|
||||
}
|
||||
((IndicesRequest.Replaceable)indicesRequest).indices(indices.toArray(new String[indices.size()]));
|
||||
return Sets.newHashSet(indices);
|
||||
}
|
||||
return Sets.newHashSet(explodeWildcards(indicesRequest, metaData));
|
||||
}
|
||||
return Sets.newHashSet(indicesRequest.indices());
|
||||
}
|
||||
|
||||
private String[] explodeWildcards(IndicesRequest indicesRequest, MetaData metaData) {
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen() || indicesRequest.indicesOptions().expandWildcardsClosed()) {
|
||||
if (MetaData.isAllIndices(indicesRequest.indices())) {
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen() && indicesRequest.indicesOptions().expandWildcardsClosed()) {
|
||||
return metaData.concreteAllIndices();
|
||||
}
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen()) {
|
||||
return metaData.concreteAllOpenIndices();
|
||||
}
|
||||
return metaData.concreteAllClosedIndices();
|
||||
|
||||
//note that "_all" will map to concrete indices only, as the same happens in core
|
||||
//which is different from "*" as the latter expands to all indices and aliases
|
||||
if (MetaData.isAllIndices(indicesRequest.indices())) {
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen() && indicesRequest.indicesOptions().expandWildcardsClosed()) {
|
||||
return metaData.concreteAllIndices();
|
||||
}
|
||||
return metaData.convertFromWildcards(indicesRequest.indices(), indicesRequest.indicesOptions());
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen()) {
|
||||
return metaData.concreteAllOpenIndices();
|
||||
}
|
||||
return metaData.concreteAllClosedIndices();
|
||||
|
||||
}
|
||||
return indicesRequest.indices();
|
||||
return metaData.convertFromWildcards(indicesRequest.indices(), indicesRequest.indicesOptions());
|
||||
}
|
||||
|
||||
private List<String> replaceWildcardsWithAuthorizedIndices(IndicesRequest indicesRequest, MetaData metaData, List<String> authorizedIndices) {
|
||||
|
||||
if (MetaData.isAllIndices(indicesRequest.indices())) {
|
||||
List<String> visibleIndices = new ArrayList<>();
|
||||
for (String authorizedIndex : authorizedIndices) {
|
||||
if (isIndexVisible(authorizedIndex, indicesRequest.indicesOptions(), metaData)) {
|
||||
visibleIndices.add(authorizedIndex);
|
||||
}
|
||||
}
|
||||
return visibleIndices;
|
||||
}
|
||||
|
||||
//the order matters when it comes to + and - (see MetaData#convertFromWildcards)
|
||||
List<String> finalIndices = new ArrayList<>();
|
||||
for (int i = 0; i < indicesRequest.indices().length; i++) {
|
||||
String index = indicesRequest.indices()[i];
|
||||
String aliasOrIndex;
|
||||
boolean minus = false;
|
||||
if (index.charAt(0) == '+') {
|
||||
aliasOrIndex = index.substring(1);
|
||||
} else if (index.charAt(0) == '-') {
|
||||
if (i == 0) {
|
||||
//mimic the MetaData#convertFromWilcards behaviour with "-index" syntax
|
||||
//but instead of adding all the indices, add only the ones that the user is authorized for
|
||||
for (String authorizedIndex : authorizedIndices) {
|
||||
if (isIndexVisible(authorizedIndex, indicesRequest.indicesOptions(), metaData)) {
|
||||
finalIndices.add(authorizedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
aliasOrIndex = index.substring(1);
|
||||
minus = true;
|
||||
} else {
|
||||
aliasOrIndex = index;
|
||||
}
|
||||
|
||||
if (Regex.isSimpleMatchPattern(aliasOrIndex)) {
|
||||
for (String authorizedIndex : authorizedIndices) {
|
||||
if (Regex.simpleMatch(aliasOrIndex, authorizedIndex)) {
|
||||
if (minus) {
|
||||
finalIndices.remove(authorizedIndex);
|
||||
} else {
|
||||
if (isIndexVisible(authorizedIndex, indicesRequest.indicesOptions(), metaData)) {
|
||||
finalIndices.add(authorizedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//MetaData#convertFromWildcards checks if the index exists here and throws IndexMissingException if not (based on ignore_unavailable).
|
||||
//Do nothing as if the index is missing but the user is not authorized to it an AuthorizationException will be thrown.
|
||||
//If the index is missing and the user is authorized to it, core will throw IndexMissingException later on.
|
||||
//There is no problem with deferring this as we are dealing with an explicit name, not with wildcards.
|
||||
if (minus) {
|
||||
finalIndices.remove(aliasOrIndex);
|
||||
} else {
|
||||
finalIndices.add(aliasOrIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return finalIndices;
|
||||
}
|
||||
|
||||
private static boolean isIndexVisible(String index, IndicesOptions indicesOptions, MetaData metaData) {
|
||||
if (metaData.hasConcreteIndex(index)) {
|
||||
IndexMetaData indexMetaData = metaData.index(index);
|
||||
if (indexMetaData == null) {
|
||||
//it's an alias, ignore expandWildcardsOpen and expandWildcardsClosed.
|
||||
//complicated to support those options with aliases pointing to multiple indices...
|
||||
return true;
|
||||
}
|
||||
if (indexMetaData.state() == IndexMetaData.State.CLOSE && indicesOptions.expandWildcardsClosed()) {
|
||||
return true;
|
||||
}
|
||||
if (indexMetaData.state() == IndexMetaData.State.OPEN && indicesOptions.expandWildcardsOpen()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.shield.authz.indicesresolver;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
|
||||
import java.util.Set;
|
||||
|
@ -17,6 +18,6 @@ public interface IndicesResolver<Request extends TransportRequest> {
|
|||
|
||||
Class<Request> requestType();
|
||||
|
||||
Set<String> resolve(Request request, MetaData metaData);
|
||||
Set<String> resolve(User user, String action, Request request, MetaData metaData);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
|||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.shield.authz.Permission;
|
||||
import org.elasticsearch.shield.authz.Privilege;
|
||||
import org.elasticsearch.shield.plugin.ShieldPlugin;
|
||||
|
@ -52,16 +53,16 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
private volatile ImmutableMap<String, Permission.Global> permissions;
|
||||
|
||||
@Inject
|
||||
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService) {
|
||||
this(settings, env, watcherService, Listener.NOOP);
|
||||
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, AuthorizationService authzService) {
|
||||
this(settings, env, watcherService, authzService, Listener.NOOP);
|
||||
}
|
||||
|
||||
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Listener listener) {
|
||||
public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, AuthorizationService authzService, Listener listener) {
|
||||
super(settings);
|
||||
file = resolveFile(componentSettings, env);
|
||||
permissions = parseFile(file, logger);
|
||||
permissions = parseFile(file, logger, authzService);
|
||||
FileWatcher watcher = new FileWatcher(file.getParent().toFile());
|
||||
watcher.addListener(new FileListener());
|
||||
watcher.addListener(new FileListener(authzService));
|
||||
watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
@ -80,7 +81,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
return Paths.get(location);
|
||||
}
|
||||
|
||||
public static ImmutableMap<String, Permission.Global> parseFile(Path path, ESLogger logger) {
|
||||
public static ImmutableMap<String, Permission.Global> parseFile(Path path, ESLogger logger, AuthorizationService authzService) {
|
||||
if (logger != null) {
|
||||
logger.trace("Reading roles file located at [{}]", path);
|
||||
}
|
||||
|
@ -99,7 +100,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
currentFieldName = parser.currentName();
|
||||
} else if (token == XContentParser.Token.START_OBJECT && currentFieldName != null) {
|
||||
String roleName = currentFieldName;
|
||||
Permission.Global.Builder permission = Permission.Global.builder();
|
||||
Permission.Global.Builder permission = Permission.Global.builder(authzService);
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
currentFieldName = parser.currentName();
|
||||
|
@ -188,8 +189,8 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
Permission.Global.Indices.Group[] groups = indices.groups();
|
||||
if (groups != null && groups.length > 0) {
|
||||
builder.startObject("indices");
|
||||
for (int i = 0; i < groups.length; i++) {
|
||||
builder.field(Strings.arrayToCommaDelimitedString(groups[i].indices())).value(groups[i].privilege().name());
|
||||
for (Permission.Indices.Group group : groups) {
|
||||
builder.field(Strings.arrayToCommaDelimitedString(group.indices())).value(group.privilege().name());
|
||||
}
|
||||
builder.endObject();
|
||||
}
|
||||
|
@ -212,6 +213,13 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
}
|
||||
|
||||
private class FileListener extends FileChangesListener {
|
||||
|
||||
private final AuthorizationService authzService;
|
||||
|
||||
private FileListener(AuthorizationService authzService) {
|
||||
this.authzService = authzService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileCreated(File file) {
|
||||
onFileChanged(file);
|
||||
|
@ -225,7 +233,7 @@ public class FileRolesStore extends AbstractComponent implements RolesStore {
|
|||
@Override
|
||||
public void onFileChanged(File file) {
|
||||
if (file.equals(FileRolesStore.this.file.toFile())) {
|
||||
permissions = parseFile(file.toPath(), logger);
|
||||
permissions = parseFile(file.toPath(), logger, authzService);
|
||||
listener.onRefresh();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import static org.elasticsearch.shield.authz.Privilege.Index.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -23,7 +24,7 @@ public class PermissionTests extends ElasticsearchTestCase {
|
|||
|
||||
@Before
|
||||
public void init() {
|
||||
Permission.Global.Builder builder = Permission.Global.builder();
|
||||
Permission.Global.Builder builder = Permission.Global.builder(mock(AuthorizationService.class));
|
||||
builder.add(union(SEARCH, MONITOR), "test_.*", "foo.*");
|
||||
builder.add(union(READ), "baz_.*foo", "fool.*bar");
|
||||
builder.add(union(MONITOR), "bar.*");
|
||||
|
|
|
@ -0,0 +1,400 @@
|
|||
/*
|
||||
* 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.shield.authz.indicesresolver;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
|
||||
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
|
||||
import org.elasticsearch.action.deletebyquery.DeleteByQueryAction;
|
||||
import org.elasticsearch.action.deletebyquery.DeleteByQueryRequest;
|
||||
import org.elasticsearch.action.get.MultiGetAction;
|
||||
import org.elasticsearch.action.get.MultiGetRequest;
|
||||
import org.elasticsearch.action.search.MultiSearchAction;
|
||||
import org.elasticsearch.action.search.MultiSearchRequest;
|
||||
import org.elasticsearch.action.search.SearchAction;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.client.Requests;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.collect.ImmutableList;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.indices.IndexMissingException;
|
||||
import org.elasticsearch.shield.User;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DefaultIndicesResolverTests extends ElasticsearchTestCase {
|
||||
|
||||
private User user;
|
||||
private User userNoIndices;
|
||||
private MetaData metaData;
|
||||
private DefaultIndicesResolver defaultIndicesResolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MetaData.Builder mdBuilder = MetaData.builder()
|
||||
.put(indexBuilder("foo").putAlias(AliasMetaData.builder("foofoobar")))
|
||||
.put(indexBuilder("foobar").putAlias(AliasMetaData.builder("foofoobar")))
|
||||
.put(indexBuilder("closed").state(IndexMetaData.State.CLOSE).putAlias(AliasMetaData.builder("foofoobar")))
|
||||
.put(indexBuilder("foofoo-closed").state(IndexMetaData.State.CLOSE))
|
||||
.put(indexBuilder("foobar-closed").state(IndexMetaData.State.CLOSE))
|
||||
.put(indexBuilder("foofoo").putAlias(AliasMetaData.builder("barbaz")))
|
||||
.put(indexBuilder("bar"))
|
||||
.put(indexBuilder("bar-closed").state(IndexMetaData.State.CLOSE))
|
||||
.put(indexBuilder("bar2"));
|
||||
metaData = mdBuilder.build();
|
||||
|
||||
AuthorizationService authzService = mock(AuthorizationService.class);
|
||||
user = new User.Simple("user", "role");
|
||||
|
||||
String[] authorizedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed"};
|
||||
when(authzService.authorizedIndicesAndAliases(user, SearchAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
when(authzService.authorizedIndicesAndAliases(user, MultiSearchAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
when(authzService.authorizedIndicesAndAliases(user, MultiGetAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
when(authzService.authorizedIndicesAndAliases(user, IndicesAliasesAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
when(authzService.authorizedIndicesAndAliases(user, DeleteIndexAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
when(authzService.authorizedIndicesAndAliases(user, DeleteByQueryAction.NAME)).thenReturn(ImmutableList.copyOf(authorizedIndices));
|
||||
userNoIndices = new User.Simple("test", "test");
|
||||
when(authzService.authorizedIndicesAndAliases(userNoIndices, SearchAction.NAME)).thenReturn(ImmutableList.<String>of());
|
||||
when(authzService.authorizedIndicesAndAliases(userNoIndices, MultiSearchAction.NAME)).thenReturn(ImmutableList.<String>of());
|
||||
|
||||
defaultIndicesResolver = new DefaultIndicesResolver(authzService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveEmptyIndicesExpandWilcardsOpenAndClosed() {
|
||||
SearchRequest request = new SearchRequest();
|
||||
request.indicesOptions(IndicesOptions.strictExpand());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "foofoo-closed"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveEmptyIndicesExpandWilcardsOpen() {
|
||||
SearchRequest request = new SearchRequest();
|
||||
request.indicesOptions(randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen()));
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "foofoobar", "foofoo"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAllExpandWilcardsOpenAndClosed() {
|
||||
SearchRequest request = new SearchRequest("_all");
|
||||
request.indicesOptions(IndicesOptions.strictExpand());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "foofoo-closed"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAllExpandWilcardsOpen() {
|
||||
SearchRequest request = new SearchRequest("_all");
|
||||
request.indicesOptions(randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen()));
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "foofoobar", "foofoo"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsExpandWilcardsOpenAndClosed() {
|
||||
SearchRequest request = new SearchRequest("barbaz", "foofoo*");
|
||||
request.indicesOptions(IndicesOptions.strictExpand());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"barbaz", "foofoobar", "foofoo", "foofoo-closed"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsExpandWilcardsOpen() {
|
||||
SearchRequest request = new SearchRequest("barbaz", "foofoo*");
|
||||
request.indicesOptions(randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen()));
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"barbaz", "foofoobar", "foofoo"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsMinusExpandWilcardsOpen() {
|
||||
SearchRequest request = new SearchRequest("-foofoo*");
|
||||
request.indicesOptions(randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen()));
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsMinusExpandWilcardsOpenAndClosed() {
|
||||
SearchRequest request = new SearchRequest("-foofoo*");
|
||||
request.indicesOptions(IndicesOptions.strictExpand());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "bar-closed"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsPlusAndMinusExpandWilcardsOpen() {
|
||||
SearchRequest request = new SearchRequest("-foofoo*", "+barbaz", "+foob*");
|
||||
request.indicesOptions(randomFrom(IndicesOptions.strictExpandOpen(), IndicesOptions.lenientExpandOpen()));
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "barbaz"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsPlusAndMinusExpandWilcardsOpenAndClosed() {
|
||||
SearchRequest request = new SearchRequest("-foofoo*", "+barbaz");
|
||||
request.indicesOptions(IndicesOptions.strictExpand());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] replacedIndices = new String[]{"bar", "bar-closed", "barbaz"};
|
||||
assertThat(indices.size(), equalTo(replacedIndices.length));
|
||||
assertThat(indices, hasItems(replacedIndices));
|
||||
assertThat(request.indices().length, equalTo(replacedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(replacedIndices));
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testResolveNonMatchingIndices() {
|
||||
SearchRequest request = new SearchRequest("missing*");
|
||||
defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testResolveNoAuthorizedIndices() {
|
||||
SearchRequest request = new SearchRequest();
|
||||
defaultIndicesResolver.resolve(userNoIndices, SearchAction.NAME, request, metaData);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveMissingIndex() {
|
||||
SearchRequest request = new SearchRequest("bar*", "missing");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] expectedIndices = new String[]{"bar", "missing"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices(), equalTo(expectedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveNonMatchingIndicesAndExplicit() {
|
||||
SearchRequest request = new SearchRequest("missing*", "bar");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] expectedIndices = new String[]{"bar"};
|
||||
assertThat(indices.toArray(new String[indices.size()]), equalTo(expectedIndices));
|
||||
assertThat(request.indices(), equalTo(expectedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveNoExpand() {
|
||||
SearchRequest request = new SearchRequest("missing*");
|
||||
request.indicesOptions(IndicesOptions.strictSingleIndexNoExpandForbidClosed());
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
String[] expectedIndices = new String[]{"missing*"};
|
||||
assertThat(indices.toArray(new String[indices.size()]), equalTo(expectedIndices));
|
||||
assertThat(request.indices(), equalTo(expectedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAllIndicesRequestNonReplaceable() {
|
||||
IndicesAliasesRequest request = new IndicesAliasesRequest();
|
||||
request.addAlias("alias", "_all");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, IndicesAliasesAction.NAME, request, metaData);
|
||||
//we do expand _all (to concrete indices only!) but we don't replace them with authorized indices
|
||||
String[] expectedIndices = new String[]{"foofoo", "foo", "foobar", "bar", "bar2"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices(), equalTo(new String[]{"_all"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWildcardsIndicesRequestNonReplaceable() {
|
||||
IndicesAliasesRequest request = new IndicesAliasesRequest();
|
||||
request.addAlias("alias", "*");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, IndicesAliasesAction.NAME, request, metaData);
|
||||
//we do explode wildcards but we don't replace them with authorized indices
|
||||
String[] expectedIndices = new String[]{"foofoobar", "foobar", "barbaz", "foo", "foofoo", "bar", "bar2"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices(), equalTo(new String[]{"*"}));
|
||||
}
|
||||
|
||||
//msearch is a CompositeIndicesRequest whose items (SearchRequests) implement IndicesRequest.Replaceable, wildcards will get replaced
|
||||
@Test
|
||||
public void testResolveMultiSearchNoWildcards() {
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testResolveMultiSearchWildcardsNoMatchingIndices() {
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
request.add(Requests.searchRequest("missing*"));
|
||||
request.add(Requests.searchRequest("foobar"));
|
||||
defaultIndicesResolver.resolve(user, MultiSearchAction.NAME, request, metaData);
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testResolveMultiSearchWildcardsNoAuthorizedIndices() {
|
||||
MultiSearchRequest request = new MultiSearchRequest();
|
||||
request.add(Requests.searchRequest("foofoo*"));
|
||||
request.add(Requests.searchRequest("foobar"));
|
||||
defaultIndicesResolver.resolve(userNoIndices, MultiSearchAction.NAME, request, metaData);
|
||||
}
|
||||
|
||||
//mget is a CompositeIndicesRequest whose items don't support expanding wildcards
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
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"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveAdminAction() {
|
||||
DeleteIndexRequest request = new DeleteIndexRequest("*");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, DeleteIndexAction.NAME, request, metaData);
|
||||
String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "foofoo-closed"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices().length, equalTo(expectedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(expectedIndices));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResolveWriteAction() {
|
||||
DeleteByQueryRequest request = new DeleteByQueryRequest("*");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, DeleteByQueryAction.NAME, request, metaData);
|
||||
String[] expectedIndices = new String[]{"bar", "foofoobar", "foofoo"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices().length, equalTo(expectedIndices.length));
|
||||
assertThat(request.indices(), arrayContaining(expectedIndices));
|
||||
}
|
||||
|
||||
private static IndexMetaData.Builder indexBuilder(String index) {
|
||||
return IndexMetaData.builder(index).settings(ImmutableSettings.settingsBuilder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1).put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* 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.shield.authz.indicesresolver;
|
||||
|
||||
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.indices.IndexMissingException;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.shield.authz.AuthorizationException;
|
||||
import org.elasticsearch.shield.test.ShieldIntegrationTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
|
||||
public class IndicesResolverIntegrationTests extends ShieldIntegrationTest {
|
||||
|
||||
@Override
|
||||
protected String configRole() {
|
||||
return DEFAULT_ROLE + ":\n" +
|
||||
" cluster: ALL\n" +
|
||||
" indices:\n" +
|
||||
" '.*': manage,write\n" +
|
||||
" 'test.*': read\n";
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testSearchNonAuthorizedWildcard() {
|
||||
//wildcard doesn't match any authorized index
|
||||
createIndices("test1", "test2", "index1", "index2");
|
||||
client().prepareSearch("index*").get();
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testEmptyClusterSearchForAll() {
|
||||
client().prepareSearch().get();
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testEmptyClusterSearchForWildcard() {
|
||||
client().prepareSearch("*").get();
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testEmptyAuthorizedIndicesSearchForAll() {
|
||||
createIndices("index1", "index2");
|
||||
client().prepareSearch().get();
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testEmptyAuthorizedIndicesSearchForWildcard() {
|
||||
createIndices("index1", "index2");
|
||||
client().prepareSearch("*").get();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExplicitNonAuthorizedIndex() {
|
||||
createIndices("test1", "test2", "index1");
|
||||
assertThrowsAuthorizationException(client().prepareSearch("test*", "index1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexMissing() {
|
||||
createIndices("test1", "test2", "index1");
|
||||
assertThrowsAuthorizationException(client().prepareSearch("missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexMissingIgnoreUnavailable() {
|
||||
createIndices("test1", "test2", "index1");
|
||||
assertThrowsAuthorizationException(client().prepareSearch("missing").setIndicesOptions(IndicesOptions.lenientExpandOpen()));
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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")));
|
||||
}
|
||||
|
||||
@Test
|
||||
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")));
|
||||
}
|
||||
|
||||
@Test
|
||||
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].getFailureMessage(), equalTo("IndexMissingException[[test4] missing]"));
|
||||
}
|
||||
|
||||
@Test(expected = IndexMissingException.class)
|
||||
public void testMultiSearchWildcard() {
|
||||
//test4 is missing but authorized, only that specific item fails
|
||||
createIndices("test1", "test2", "test3", "index1");
|
||||
client().prepareMultiSearch()
|
||||
.add(Requests.searchRequest())
|
||||
.add(Requests.searchRequest("index*")).get();
|
||||
}
|
||||
|
||||
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) {
|
||||
try {
|
||||
actionRequestBuilder.get();
|
||||
fail("search should fail due to attempt to access non authorized indices");
|
||||
} catch(AuthorizationException e) {
|
||||
assertThat(e.getMessage(), containsString("is unauthorized for user [test_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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensureGreen();
|
||||
for (String index : indices) {
|
||||
client().prepareIndex(index, "type").setSource("field", "value").get();
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.elasticsearch.common.base.Charsets;
|
|||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.shield.authz.AuthorizationService;
|
||||
import org.elasticsearch.shield.authz.Permission;
|
||||
import org.elasticsearch.shield.authz.Privilege;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
|
@ -30,6 +31,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -42,7 +44,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
|||
@Test
|
||||
public void testParseFile() throws Exception {
|
||||
Path path = Paths.get(getClass().getResource("roles.yml").toURI());
|
||||
Map<String, Permission.Global> roles = FileRolesStore.parseFile(path, logger);
|
||||
Map<String, Permission.Global> roles = FileRolesStore.parseFile(path, logger, mock(AuthorizationService.class));
|
||||
assertThat(roles, notNullValue());
|
||||
assertThat(roles.size(), is(3));
|
||||
|
||||
|
@ -110,7 +112,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
|||
threadPool = new ThreadPool("test");
|
||||
watcherService = new ResourceWatcherService(settings, threadPool);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
FileRolesStore store = new FileRolesStore(settings, env, watcherService, new FileRolesStore.Listener() {
|
||||
FileRolesStore store = new FileRolesStore(settings, env, watcherService, mock(AuthorizationService.class), new FileRolesStore.Listener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
latch.countDown();
|
||||
|
@ -138,8 +140,8 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
|||
|
||||
permission = store.permission("role4");
|
||||
assertThat(permission, notNullValue());
|
||||
assertThat(permission.check("cluster:monitor/foo/bar", null, null), is(true));
|
||||
assertThat(permission.check("cluster:admin/foo/bar", null, null), is(false));
|
||||
assertThat(permission.check(null, "cluster:monitor/foo/bar", null, null), is(true));
|
||||
assertThat(permission.check(null, "cluster:admin/foo/bar", null, null), is(false));
|
||||
|
||||
} finally {
|
||||
if (watcherService != null) {
|
||||
|
@ -155,7 +157,7 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
|||
public void testThatEmptyFileDoesNotResultInLoop() throws Exception {
|
||||
File file = tempFolder.newFile();
|
||||
com.google.common.io.Files.write("#".getBytes(Charsets.UTF_8), file);
|
||||
Map<String, Permission.Global> roles = FileRolesStore.parseFile(file.toPath(), logger);
|
||||
Map<String, Permission.Global> roles = FileRolesStore.parseFile(file.toPath(), logger, mock(AuthorizationService.class));
|
||||
assertThat(roles.keySet(), is(empty()));
|
||||
}
|
||||
|
||||
|
@ -163,6 +165,6 @@ public class FileRolesStoreTests extends ElasticsearchTestCase {
|
|||
public void testThatInvalidYAMLThrowsElasticsearchException() throws Exception {
|
||||
File file = tempFolder.newFile();
|
||||
com.google.common.io.Files.write("user: cluster: ALL indices: '.*': ALL".getBytes(Charsets.UTF_8), file);
|
||||
FileRolesStore.parseFile(file.toPath(), logger);
|
||||
FileRolesStore.parseFile(file.toPath(), logger, mock(AuthorizationService.class));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ import static org.hamcrest.Matchers.is;
|
|||
|
||||
@Ignore
|
||||
@AbstractRandomizedTest.Integration
|
||||
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0, maxNumDataNodes = 1)
|
||||
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0)
|
||||
public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest {
|
||||
|
||||
protected static final String DEFAULT_USER_NAME = "test_user";
|
||||
|
@ -50,7 +50,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
|||
public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n";
|
||||
public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n";
|
||||
public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_USER_NAME + ":" + DEFAULT_ROLE + "\n";
|
||||
public static final String CONFIG_ROLE_ALLOW_ALL = "user:\n" +
|
||||
public static final String CONFIG_ROLE_ALLOW_ALL = DEFAULT_ROLE + ":\n" +
|
||||
" cluster: ALL\n" +
|
||||
" indices:\n" +
|
||||
" '.*': ALL\n";
|
||||
|
@ -70,7 +70,7 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
|||
.put("plugin.types", ShieldPlugin.class.getName())
|
||||
.put("shield.authc.esusers.files.users", writeFile(folder, "users", CONFIG_STANDARD_USER))
|
||||
.put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES))
|
||||
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", CONFIG_ROLE_ALLOW_ALL))
|
||||
.put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", configRole()))
|
||||
.put("shield.transport.n2n.ip_filter.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL))
|
||||
.put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode"))
|
||||
.put("shield.audit.enabled", true)
|
||||
|
@ -84,6 +84,10 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
protected String configRole() {
|
||||
return CONFIG_ROLE_ALLOW_ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Settings transportClientSettings() {
|
||||
return ImmutableSettings.builder()
|
||||
|
|
Loading…
Reference in New Issue