security: resolve date match expressions for authorization
Elasticsearch supports the concept of date match expressions for index names and the authorization service was trying to authorize the names without resolving them to their concrete index names. This change now resolves these names Closes elastic/elasticsearch#1983 Original commit: elastic/x-pack-elasticsearch@3c6baa8e83
This commit is contained in:
parent
2b00967b01
commit
98a308352a
|
@ -12,6 +12,7 @@ import org.elasticsearch.action.admin.indices.alias.Alias;
|
|||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.search.ClearScrollAction;
|
||||
import org.elasticsearch.action.search.SearchScrollAction;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.AliasOrIndex;
|
||||
|
@ -79,13 +80,14 @@ public class InternalAuthorizationService extends AbstractComponent implements A
|
|||
|
||||
@Inject
|
||||
public InternalAuthorizationService(Settings settings, RolesStore rolesStore, ClusterService clusterService,
|
||||
AuditTrail auditTrail, AuthenticationFailureHandler authcFailureHandler, ThreadPool threadPool) {
|
||||
AuditTrail auditTrail, AuthenticationFailureHandler authcFailureHandler,
|
||||
ThreadPool threadPool, IndexNameExpressionResolver nameExpressionResolver) {
|
||||
super(settings);
|
||||
this.rolesStore = rolesStore;
|
||||
this.clusterService = clusterService;
|
||||
this.auditTrail = auditTrail;
|
||||
this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[]{
|
||||
new DefaultIndicesAndAliasesResolver(this)
|
||||
this.indicesAndAliasesResolvers = new IndicesAndAliasesResolver[] {
|
||||
new DefaultIndicesAndAliasesResolver(this, nameExpressionResolver)
|
||||
};
|
||||
this.authcFailureHandler = authcFailureHandler;
|
||||
this.threadContext = threadPool.getThreadContext();
|
||||
|
|
|
@ -7,7 +7,9 @@ package org.elasticsearch.shield.authz.indicesresolver;
|
|||
|
||||
import org.elasticsearch.action.AliasesRequest;
|
||||
import org.elasticsearch.action.CompositeIndicesRequest;
|
||||
import org.elasticsearch.action.DocumentRequest;
|
||||
import org.elasticsearch.action.IndicesRequest;
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
|
||||
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
|
||||
import org.elasticsearch.action.support.IndicesOptions;
|
||||
import org.elasticsearch.cluster.metadata.AliasOrIndex;
|
||||
|
@ -35,9 +37,11 @@ import java.util.SortedMap;
|
|||
public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolver<TransportRequest> {
|
||||
|
||||
private final AuthorizationService authzService;
|
||||
private final IndexNameExpressionResolver nameExpressionResolver;
|
||||
|
||||
public DefaultIndicesAndAliasesResolver(AuthorizationService authzService) {
|
||||
public DefaultIndicesAndAliasesResolver(AuthorizationService authzService, IndexNameExpressionResolver nameExpressionResolver) {
|
||||
this.authzService = authzService;
|
||||
this.nameExpressionResolver = nameExpressionResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -80,25 +84,30 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
indices = Collections.singleton(((PutMappingRequest) indicesRequest).getConcreteIndex().getName());
|
||||
assert indicesRequest.indices() == null || indicesRequest.indices().length == 0
|
||||
: "indices are: " + Arrays.toString(indicesRequest.indices()); // Arrays.toString() can handle null values - all good
|
||||
} else {
|
||||
if (indicesRequest.indicesOptions().expandWildcardsOpen() || indicesRequest.indicesOptions().expandWildcardsClosed()) {
|
||||
if (indicesRequest instanceof IndicesRequest.Replaceable) {
|
||||
List<String> authorizedIndices = replaceWildcardsWithAuthorizedIndices(indicesRequest.indices(),
|
||||
indicesRequest.indicesOptions(),
|
||||
metaData, authzService.authorizedIndicesAndAliases(user, action));
|
||||
((IndicesRequest.Replaceable) indicesRequest).indices(authorizedIndices.toArray(new String[authorizedIndices.size()]));
|
||||
} else {
|
||||
assert !containsWildcards(indicesRequest) :
|
||||
"There are no external requests known to support wildcards that don't support replacing their indices";
|
||||
|
||||
//NOTE: shard level requests do support wildcards (as they hold the original indices options) but don't support
|
||||
// replacing their indices.
|
||||
//That is fine though because they never contain wildcards, as they get replaced as part of the authorization of their
|
||||
//corresponding parent request on the coordinating node. Hence wildcards don't need to get replaced nor exploded for
|
||||
// shard level requests.
|
||||
}
|
||||
}
|
||||
} else if (indicesRequest instanceof IndicesRequest.Replaceable) {
|
||||
IndicesRequest.Replaceable replaceable = (IndicesRequest.Replaceable) indicesRequest;
|
||||
final boolean replaceWildcards = indicesRequest.indicesOptions().expandWildcardsOpen()
|
||||
|| indicesRequest.indicesOptions().expandWildcardsClosed();
|
||||
List<String> authorizedIndices = replaceWildcardsWithAuthorizedIndices(indicesRequest.indices(),
|
||||
indicesRequest.indicesOptions(),
|
||||
metaData,
|
||||
authzService.authorizedIndicesAndAliases(user, action),
|
||||
replaceWildcards);
|
||||
replaceable.indices(authorizedIndices.toArray(new String[authorizedIndices.size()]));
|
||||
indices = Sets.newHashSet(indicesRequest.indices());
|
||||
} else {
|
||||
assert !containsWildcards(indicesRequest) :
|
||||
"There are no external requests known to support wildcards that don't support replacing their indices";
|
||||
//NOTE: shard level requests do support wildcards (as they hold the original indices options) but don't support
|
||||
// replacing their indices.
|
||||
//That is fine though because they never contain wildcards, as they get replaced as part of the authorization of their
|
||||
//corresponding parent request on the coordinating node. Hence wildcards don't need to get replaced nor exploded for
|
||||
// shard level requests.
|
||||
List<String> resolvedNames = new ArrayList<>();
|
||||
for (String name : indicesRequest.indices()) {
|
||||
resolvedNames.add(nameExpressionResolver.resolveDateMathExpression(name));
|
||||
}
|
||||
indices = Sets.newHashSet(resolvedNames);
|
||||
}
|
||||
|
||||
if (indicesRequest instanceof AliasesRequest) {
|
||||
|
@ -177,10 +186,14 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
}
|
||||
|
||||
private List<String> replaceWildcardsWithAuthorizedIndices(String[] indices, IndicesOptions indicesOptions, MetaData metaData,
|
||||
List<String> authorizedIndices) {
|
||||
List<String> authorizedIndices, boolean replaceWildcards) {
|
||||
|
||||
// check for all and return list of authorized indices
|
||||
if (IndexNameExpressionResolver.isAllIndices(indicesList(indices))) {
|
||||
if (replaceWildcards == false) {
|
||||
// if we cannot replace wildcards, then we should not set all indices
|
||||
return throwExceptionIfNoIndicesWereResolved(indices, null);
|
||||
}
|
||||
List<String> visibleIndices = new ArrayList<>();
|
||||
for (String authorizedIndex : authorizedIndices) {
|
||||
if (isIndexVisible(authorizedIndex, indicesOptions, metaData)) {
|
||||
|
@ -214,7 +227,7 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
aliasOrIndex = index;
|
||||
}
|
||||
|
||||
if (Regex.isSimpleMatchPattern(aliasOrIndex)) {
|
||||
if (replaceWildcards && Regex.isSimpleMatchPattern(aliasOrIndex)) {
|
||||
for (String authorizedIndex : authorizedIndices) {
|
||||
if (Regex.simpleMatch(aliasOrIndex, authorizedIndex)) {
|
||||
if (minus) {
|
||||
|
@ -227,15 +240,32 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
}
|
||||
}
|
||||
} else {
|
||||
//MetaData#convertFromWildcards checks if the index exists here and throws IndexNotFoundException 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 IndexNotFoundException 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);
|
||||
// we always need to check for date math expressions
|
||||
String dateMathName = nameExpressionResolver.resolveDateMathExpression(aliasOrIndex);
|
||||
// we can use != here to compare strings since the name expression resolver returns the same instance, but add an assert
|
||||
// to ensure we catch this if it changes
|
||||
if (dateMathName != aliasOrIndex) {
|
||||
assert dateMathName.equals(aliasOrIndex) == false;
|
||||
if (authorizedIndices.contains(dateMathName)) {
|
||||
if (minus) {
|
||||
finalIndices.remove(dateMathName);
|
||||
} else {
|
||||
if (isIndexVisible(dateMathName, indicesOptions, metaData, true)) {
|
||||
finalIndices.add(dateMathName);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalIndices.add(aliasOrIndex);
|
||||
//MetaData#convertFromWildcards checks if the index exists here and throws IndexNotFoundException 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 IndexNotFoundException 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +288,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
}
|
||||
|
||||
private static boolean isIndexVisible(String index, IndicesOptions indicesOptions, MetaData metaData) {
|
||||
return isIndexVisible(index, indicesOptions, metaData, false);
|
||||
}
|
||||
|
||||
private static boolean isIndexVisible(String index, IndicesOptions indicesOptions, MetaData metaData, boolean dateMathExpression) {
|
||||
if (metaData.hasConcreteIndex(index)) {
|
||||
IndexMetaData indexMetaData = metaData.index(index);
|
||||
if (indexMetaData == null) {
|
||||
|
@ -265,10 +299,10 @@ public class DefaultIndicesAndAliasesResolver implements IndicesAndAliasesResolv
|
|||
//complicated to support those options with aliases pointing to multiple indices...
|
||||
return true;
|
||||
}
|
||||
if (indexMetaData.getState() == IndexMetaData.State.CLOSE && indicesOptions.expandWildcardsClosed()) {
|
||||
if (indexMetaData.getState() == IndexMetaData.State.CLOSE && (indicesOptions.expandWildcardsClosed() || dateMathExpression)) {
|
||||
return true;
|
||||
}
|
||||
if (indexMetaData.getState() == IndexMetaData.State.OPEN && indicesOptions.expandWildcardsOpen()) {
|
||||
if (indexMetaData.getState() == IndexMetaData.State.OPEN && (indicesOptions.expandWildcardsOpen() || dateMathExpression)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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.integration;
|
||||
|
||||
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
|
||||
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
|
||||
import org.elasticsearch.action.get.GetResponse;
|
||||
import org.elasticsearch.action.get.MultiGetResponse;
|
||||
import org.elasticsearch.action.index.IndexResponse;
|
||||
import org.elasticsearch.action.search.MultiSearchResponse;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.action.update.UpdateResponse;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.shield.authc.support.Hasher;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.test.ShieldIntegTestCase;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class DateMathExpressionIntegTests extends ShieldIntegTestCase {
|
||||
|
||||
protected static final SecuredString USERS_PASSWD = new SecuredString("change_me".toCharArray());
|
||||
protected static final String USERS_PASSWD_HASHED = new String(Hasher.BCRYPT.hash(USERS_PASSWD));
|
||||
|
||||
@Override
|
||||
protected String configUsers() {
|
||||
return super.configUsers() +
|
||||
"user1:" + USERS_PASSWD_HASHED + "\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configUsersRoles() {
|
||||
return super.configUsersRoles() +
|
||||
"role1:user1\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String configRoles() {
|
||||
return super.configRoles() +
|
||||
"\nrole1:\n" +
|
||||
" cluster: [ none ]\n" +
|
||||
" indices:\n" +
|
||||
" - names: 'datemath-*'\n" +
|
||||
" privileges: [ ALL ]\n";
|
||||
}
|
||||
|
||||
public void testDateMathExpressionsCanBeAuthorized() throws Exception {
|
||||
final String expression = "<datemath-{now/M}>";
|
||||
final String expectedIndexName = new IndexNameExpressionResolver(Settings.EMPTY).resolveDateMathExpression(expression);
|
||||
final boolean refeshOnOperation = randomBoolean();
|
||||
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", basicAuthHeaderValue("user1", USERS_PASSWD)));
|
||||
|
||||
if (randomBoolean()) {
|
||||
CreateIndexResponse response = client.admin().indices().prepareCreate(expression).get();
|
||||
assertThat(response.isAcknowledged(), is(true));
|
||||
}
|
||||
IndexResponse response = client.prepareIndex(expression, "type").setSource("foo", "bar").setRefresh(refeshOnOperation).get();
|
||||
|
||||
assertThat(response.isCreated(), is(true));
|
||||
assertThat(response.getIndex(), containsString(expectedIndexName));
|
||||
|
||||
if (refeshOnOperation == false) {
|
||||
client.admin().indices().prepareRefresh(expression).get();
|
||||
}
|
||||
SearchResponse searchResponse = client.prepareSearch(expression)
|
||||
.setQuery(QueryBuilders.matchAllQuery())
|
||||
.get();
|
||||
assertThat(searchResponse.getHits().getTotalHits(), is(1L));
|
||||
|
||||
MultiSearchResponse multiSearchResponse = client.prepareMultiSearch()
|
||||
.add(client.prepareSearch(expression).setQuery(QueryBuilders.matchAllQuery()).request())
|
||||
.get();
|
||||
assertThat(multiSearchResponse.getResponses()[0].getResponse().getHits().getTotalHits(), is(1L));
|
||||
|
||||
UpdateResponse updateResponse = client.prepareUpdate(expression, "type", response.getId())
|
||||
.setDoc("new", "field")
|
||||
.setRefresh(refeshOnOperation)
|
||||
.get();
|
||||
assertThat(updateResponse.isCreated(), is(false));
|
||||
|
||||
if (refeshOnOperation == false) {
|
||||
client.admin().indices().prepareRefresh(expression).get();
|
||||
}
|
||||
GetResponse getResponse = client.prepareGet(expression, "type", response.getId()).setFetchSource(true).get();
|
||||
assertThat(getResponse.isExists(), is(true));
|
||||
assertThat(getResponse.getSourceAsMap().get("foo").toString(), is("bar"));
|
||||
assertThat(getResponse.getSourceAsMap().get("new").toString(), is("field"));
|
||||
|
||||
// multi get doesn't support expressions - this is probably a bug
|
||||
MultiGetResponse multiGetResponse = client.prepareMultiGet()
|
||||
.add(expression, "type", response.getId())
|
||||
.get();
|
||||
assertThat(multiGetResponse.getResponses()[0].getFailure().getMessage(), is("no such index"));
|
||||
|
||||
DeleteIndexResponse deleteIndexResponse = client.admin().indices().prepareDelete(expression).get();
|
||||
assertThat(deleteIndexResponse.isAcknowledged(), is(true));
|
||||
}
|
||||
|
||||
}
|
|
@ -45,6 +45,7 @@ import org.elasticsearch.action.termvectors.TermVectorsAction;
|
|||
import org.elasticsearch.action.termvectors.TermVectorsRequest;
|
||||
import org.elasticsearch.action.update.UpdateAction;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.AliasMetaData;
|
||||
|
@ -82,6 +83,8 @@ import static org.hamcrest.Matchers.containsInAnyOrder;
|
|||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.mockito.AdditionalAnswers.returnsFirstArg;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -105,8 +108,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
threadPool = mock(ThreadPool.class);
|
||||
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||
|
||||
IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class);
|
||||
when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg());
|
||||
internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService,
|
||||
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver);
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -349,8 +354,10 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
TransportRequest request = new IndicesExistsRequest("b");
|
||||
ClusterState state = mock(ClusterState.class);
|
||||
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build());
|
||||
IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class);
|
||||
when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg());
|
||||
internalAuthorizationService = new InternalAuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail,
|
||||
new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver);
|
||||
|
||||
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
|
@ -377,9 +384,11 @@ public class InternalAuthorizationServiceTests extends ESTestCase {
|
|||
.put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false)
|
||||
.build());
|
||||
User anonymousUser = AnonymousUser.INSTANCE;
|
||||
IndexNameExpressionResolver nameExpressionResolver = mock(IndexNameExpressionResolver.class);
|
||||
when(nameExpressionResolver.resolveDateMathExpression(any(String.class))).thenAnswer(returnsFirstArg());
|
||||
internalAuthorizationService = new InternalAuthorizationService(
|
||||
Settings.builder().put(InternalAuthorizationService.ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.getKey(), false).build(),
|
||||
rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool);
|
||||
rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, nameExpressionResolver);
|
||||
|
||||
when(rolesStore.role("a_all")).thenReturn(Role.builder("a_all").add(IndexPrivilege.ALL, "a").build());
|
||||
when(clusterService.state()).thenReturn(state);
|
||||
|
|
|
@ -20,6 +20,7 @@ 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.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.cluster.service.ClusterService;
|
||||
import org.elasticsearch.cluster.ClusterState;
|
||||
import org.elasticsearch.cluster.metadata.AliasAction;
|
||||
|
@ -45,6 +46,7 @@ import org.junit.Before;
|
|||
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
|
@ -61,6 +63,7 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
private RolesStore rolesStore;
|
||||
private MetaData metaData;
|
||||
private DefaultIndicesAndAliasesResolver defaultIndicesResolver;
|
||||
private IndexNameExpressionResolver indexNameExpressionResolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
@ -70,6 +73,7 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, randomIntBetween(0, 1))
|
||||
.build();
|
||||
|
||||
indexNameExpressionResolver = new IndexNameExpressionResolver(Settings.EMPTY);
|
||||
MetaData.Builder mdBuilder = MetaData.builder()
|
||||
.put(indexBuilder("foo").putAlias(AliasMetaData.builder("foofoobar")).settings(settings))
|
||||
.put(indexBuilder("foobar").putAlias(AliasMetaData.builder("foofoobar")).settings(settings))
|
||||
|
@ -81,6 +85,7 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
.put(indexBuilder("bar").settings(settings))
|
||||
.put(indexBuilder("bar-closed").state(IndexMetaData.State.CLOSE).settings(settings))
|
||||
.put(indexBuilder("bar2").settings(settings))
|
||||
.put(indexBuilder(indexNameExpressionResolver.resolveDateMathExpression("<datetime-{now/M}>")).settings(settings))
|
||||
.put(indexBuilder(ShieldTemplateService.SECURITY_INDEX_NAME).settings(settings));
|
||||
metaData = mdBuilder.build();
|
||||
|
||||
|
@ -97,8 +102,8 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
when(state.metaData()).thenReturn(metaData);
|
||||
|
||||
InternalAuthorizationService authzService = new InternalAuthorizationService(settings, rolesStore, clusterService,
|
||||
mock(AuditTrail.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class));
|
||||
defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService);
|
||||
mock(AuditTrail.class), new DefaultAuthenticationFailureHandler(), mock(ThreadPool.class), indexNameExpressionResolver);
|
||||
defaultIndicesResolver = new DefaultIndicesAndAliasesResolver(authzService, indexNameExpressionResolver);
|
||||
}
|
||||
|
||||
public void testResolveEmptyIndicesExpandWilcardsOpenAndClosed() {
|
||||
|
@ -840,6 +845,82 @@ public class DefaultIndicesResolverTests extends ESTestCase {
|
|||
assertThat(indices, not(hasItem(ShieldTemplateService.SECURITY_INDEX_NAME)));
|
||||
}
|
||||
|
||||
public void testResolvingDateExpression() {
|
||||
// the user isn't authorized so resolution should fail
|
||||
SearchRequest request = new SearchRequest("<datetime-{now/M}>");
|
||||
if (randomBoolean()) {
|
||||
request.indicesOptions(IndicesOptions.strictSingleIndexNoExpandForbidClosed());
|
||||
}
|
||||
try {
|
||||
defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
fail("user is not authorized to see this index");
|
||||
} catch (IndexNotFoundException e) {
|
||||
assertThat(e.getMessage(), is("no such index"));
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
assertThat(indices.size(), equalTo(1));
|
||||
assertThat(request.indices()[0], equalTo(indexNameExpressionResolver.resolveDateMathExpression("<datetime-{now/M}>")));
|
||||
}
|
||||
|
||||
public void testMissingDateExpression() {
|
||||
SearchRequest request = new SearchRequest("<foobar-{now/M}>");
|
||||
try {
|
||||
defaultIndicesResolver.resolve(user, SearchAction.NAME, request, metaData);
|
||||
fail("index should not exist");
|
||||
} catch (IndexNotFoundException e) {
|
||||
assertThat(e.getMessage(), is("no such index"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testAliasDateMathExpressionNotSupported() {
|
||||
// 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());
|
||||
GetAliasesRequest request = new GetAliasesRequest("<datetime-{now/M}>").indices("foo", "foofoo");
|
||||
Set<String> indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData);
|
||||
//the union of all indices and aliases gets returned
|
||||
String[] expectedIndices = new String[]{"<datetime-{now/M}>", "foo", "foofoo"};
|
||||
assertThat(indices.size(), equalTo(expectedIndices.length));
|
||||
assertThat(indices, hasItems(expectedIndices));
|
||||
assertThat(request.indices(), arrayContainingInAnyOrder("foo", "foofoo"));
|
||||
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?
|
||||
|
||||
private static IndexMetaData.Builder indexBuilder(String index) {
|
||||
|
|
Loading…
Reference in New Issue