diff --git a/src/main/java/org/elasticsearch/shield/authz/Privilege.java b/src/main/java/org/elasticsearch/shield/authz/Privilege.java index 7e85130199e..e107865030b 100644 --- a/src/main/java/org/elasticsearch/shield/authz/Privilege.java +++ b/src/main/java/org/elasticsearch/shield/authz/Privilege.java @@ -10,6 +10,7 @@ import org.apache.lucene.util.automaton.Automaton; import org.apache.lucene.util.automaton.Operations; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchIllegalArgumentException; +import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.get.GetAction; import org.elasticsearch.action.search.SearchAction; import org.elasticsearch.common.Strings; @@ -107,8 +108,8 @@ public abstract class Privilege

> { public static final Index NONE = new Index(Name.NONE, Automata.makeEmpty()); public static final Index ALL = new Index(Name.ALL, "indices:*"); public static final Index MANAGE = new Index("manage", "indices:monitor/*", "indices:admin/*"); - public static final Index CREATE_INDEX = new Index("create_index", "indices:admin/create"); - public static final Index MANAGE_ALIASES = new Index("manage_aliases", "indices:admin/aliases"); + public static final Index CREATE_INDEX = new Index("create_index", CreateIndexAction.NAME); + public static final Index MANAGE_ALIASES = new Index("manage_aliases", "indices:admin/aliases*"); public static final Index MONITOR = new Index("monitor", "indices:monitor/*"); public static final Index DATA_ACCESS = new Index("data_access", "indices:data/*"); public static final Index CRUD = new Index("crud", "indices:data/write/*", "indices:data/read/*"); diff --git a/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java b/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java index 73534f967d0..b39aa3db52a 100644 --- a/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java +++ b/src/main/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolver.java @@ -8,11 +8,14 @@ package org.elasticsearch.shield.authz.indicesresolver; import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.metadata.AliasAction; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.collect.*; +import org.elasticsearch.common.collect.ImmutableList; +import org.elasticsearch.common.collect.Lists; +import org.elasticsearch.common.collect.Sets; import org.elasticsearch.common.hppc.ObjectLookupContainer; import org.elasticsearch.common.regex.Regex; import org.elasticsearch.index.Index; @@ -68,84 +71,110 @@ public class DefaultIndicesResolver implements IndicesResolver ImmutableList authorizedIndices = authzService.authorizedIndicesAndAliases(user, action); List indices = replaceWildcardsWithAuthorizedIndices(indicesRequest.indices(), indicesRequest.indicesOptions(), metaData, authorizedIndices); ((IndicesRequest.Replaceable) indicesRequest).indices(indices.toArray(new String[indices.size()])); - return Sets.newHashSet(indices); + } else { + assert indicesRequest instanceof IndicesAliasesRequest || !containsWildcards(indicesRequest) : + "IndicesAliasesRequest is the only external request known to support wildcards that doesn't support replacing its 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. } - - assert indicesRequest instanceof IndicesAliasesRequest || !containsWildcards(indicesRequest) : - "IndicesAliasesRequest is the only external request known to support wildcards that doesn't support replacing its 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. } if (indicesRequest instanceof IndicesAliasesRequest) { //special treatment for IndicesAliasesRequest since we need to extract indices from indices() as well as aliases() //Also, we need to replace wildcards in both with authorized indices and/or aliases (IndicesAliasesRequest doesn't implement Replaceable) - IndicesAliasesRequest request = (IndicesAliasesRequest) indicesRequest; + return resolveIndicesAliasesRequest(user, action, (IndicesAliasesRequest) indicesRequest, metaData); + } - ImmutableList authorizedIndices = authzService.authorizedIndicesAndAliases(user, action); - Set finalIndices = Sets.newHashSet(); - - List authorizedAliases = null; - - for (IndicesAliasesRequest.AliasActions aliasActions : request.getAliasActions()) { - //replace indices with authorized ones if needed - if (indicesRequest.indicesOptions().expandWildcardsOpen() || indicesRequest.indicesOptions().expandWildcardsClosed()) { - //Note: the indices that the alias operation maps to might end up containing aliases, since authorized indices can also be aliases. - //This is fine as es core resolves them to concrete indices anyway before executing the actual operation. - //Also es core already allows to specify aliases among indices, they will just be resolved (alias to alias is not supported). - //e.g. index: foo* gets resolved in core to anything that matches the expression, aliases included, hence their corresponding indices. - List indices = replaceWildcardsWithAuthorizedIndices(aliasActions.indices(), indicesRequest.indicesOptions(), metaData, authorizedIndices); - aliasActions.indices(indices.toArray(new String[indices.size()])); - } - Collections.addAll(finalIndices, aliasActions.indices()); - - //replace aliases with authorized ones if needed - if (aliasActions.actionType() == AliasAction.Type.REMOVE) { - //lazily initialize a list of all the authorized aliases (filtering concrete indices out) - if (authorizedAliases == null) { - authorizedAliases = Lists.newArrayList(); - ObjectLookupContainer existingAliases = metaData.aliases().keys(); - for (String authorizedIndex : authorizedIndices) { - if (existingAliases.contains(authorizedIndex)) { - authorizedAliases.add(authorizedIndex); - } - } - } - - List finalAliases = Lists.newArrayList(); - for (String aliasPattern : aliasActions.aliases()) { - if (aliasPattern.equals(MetaData.ALL)) { - finalAliases.addAll(authorizedAliases); - } else if (Regex.isSimpleMatchPattern(aliasPattern)) { - for (String authorizedAlias : authorizedAliases) { - if (Regex.simpleMatch(aliasPattern, authorizedAlias)) { - finalAliases.add(authorizedAlias); - } - } - } else { - finalAliases.add(aliasPattern); - } - } - - //throw exception if the wildcards expansion to authorized aliases 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 aliases that we authorized it to execute on. - //If we can't replace because we got an empty set, we can only throw exception. - if (finalAliases.isEmpty()) { - throw new IndexMissingException(new Index(Arrays.toString(aliasActions.aliases()))); - } - aliasActions.aliases(finalAliases.toArray(new String[finalAliases.size()])); - } - Collections.addAll(finalIndices, aliasActions.aliases()); - } - return finalIndices; + if (indicesRequest instanceof GetAliasesRequest) { + //special treatment for GetAliasesRequest since we need to replace wildcards among the specified aliases. + //GetAliasesRequest implements IndicesRequest.Replaceable, hence its indices have already been properly replaced. + return resolveGetAliasesRequest(user, action, (GetAliasesRequest) indicesRequest, metaData); } return Sets.newHashSet(indicesRequest.indices()); } + private Set resolveGetAliasesRequest(User user, String action, GetAliasesRequest request, MetaData metaData) { + ImmutableList authorizedIndices = authzService.authorizedIndicesAndAliases(user, action); + List aliases = replaceWildcardsWithAuthorizedAliases(request.aliases(), loadAuthorizedAliases(authorizedIndices, metaData)); + request.aliases(aliases.toArray(new String[aliases.size()])); + Set indices = Sets.newHashSet(request.indices()); + indices.addAll(aliases); + return indices; + } + + private Set resolveIndicesAliasesRequest(User user, String action, IndicesAliasesRequest request, MetaData metaData) { + ImmutableList authorizedIndices = authzService.authorizedIndicesAndAliases(user, action); + Set finalIndices = Sets.newHashSet(); + + List authorizedAliases = null; + + for (IndicesAliasesRequest.AliasActions aliasActions : request.getAliasActions()) { + //replace indices with authorized ones if needed + if (request.indicesOptions().expandWildcardsOpen() || request.indicesOptions().expandWildcardsClosed()) { + //Note: the indices that the alias operation maps to might end up containing aliases, since authorized indices can also be aliases. + //This is fine as es core resolves them to concrete indices anyway before executing the actual operation. + //Also es core already allows to specify aliases among indices, they will just be resolved (alias to alias is not supported). + //e.g. index: foo* gets resolved in core to anything that matches the expression, aliases included, hence their corresponding indices. + List indices = replaceWildcardsWithAuthorizedIndices(aliasActions.indices(), request.indicesOptions(), metaData, authorizedIndices); + aliasActions.indices(indices.toArray(new String[indices.size()])); + } + Collections.addAll(finalIndices, aliasActions.indices()); + + //replace aliases with authorized ones if needed + if (aliasActions.actionType() == AliasAction.Type.REMOVE) { + //lazily initialize a list of all the authorized aliases (filtering concrete indices out) + if (authorizedAliases == null) { + authorizedAliases = loadAuthorizedAliases(authorizedIndices, metaData); + } + + List aliases = replaceWildcardsWithAuthorizedAliases(aliasActions.aliases(), authorizedAliases); + aliasActions.aliases(aliases.toArray(new String[aliases.size()])); + } + Collections.addAll(finalIndices, aliasActions.aliases()); + } + return finalIndices; + } + + private List loadAuthorizedAliases(List authorizedIndices, MetaData metaData) { + List authorizedAliases = Lists.newArrayList(); + ObjectLookupContainer existingAliases = metaData.aliases().keys(); + for (String authorizedIndex : authorizedIndices) { + if (existingAliases.contains(authorizedIndex)) { + authorizedAliases.add(authorizedIndex); + } + } + return authorizedAliases; + } + + private List replaceWildcardsWithAuthorizedAliases(String[] aliases, List authorizedAliases) { + List finalAliases = Lists.newArrayList(); + for (String aliasPattern : aliases) { + if (aliasPattern.equals(MetaData.ALL)) { + finalAliases.addAll(authorizedAliases); + } else if (Regex.isSimpleMatchPattern(aliasPattern)) { + for (String authorizedAlias : authorizedAliases) { + if (Regex.simpleMatch(aliasPattern, authorizedAlias)) { + finalAliases.add(authorizedAlias); + } + } + } else { + finalAliases.add(aliasPattern); + } + } + + //throw exception if the wildcards expansion to authorized aliases 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 aliases that we authorized it to execute on. + //If we can't replace because we got an empty set, we can only throw exception. + if (finalAliases.isEmpty()) { + throw new IndexMissingException(new Index(Arrays.toString(aliases))); + } + return finalAliases; + } + private boolean containsWildcards(IndicesRequest indicesRequest) { if (MetaData.isAllIndices(indicesRequest.indices())) { return true; diff --git a/src/test/java/org/elasticsearch/shield/authz/IndexAliasesTests.java b/src/test/java/org/elasticsearch/shield/authz/IndexAliasesTests.java index 2e3a1fc2800..31d96eea542 100644 --- a/src/test/java/org/elasticsearch/shield/authz/IndexAliasesTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/IndexAliasesTests.java @@ -6,9 +6,13 @@ package org.elasticsearch.shield.authz; import org.elasticsearch.action.admin.indices.alias.Alias; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequestBuilder; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse; +import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.indices.IndexMissingException; import org.elasticsearch.shield.authc.support.SecuredString; import org.elasticsearch.test.ShieldIntegrationTest; +import org.junit.Before; import org.junit.Test; import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.BASIC_AUTH_HEADER; @@ -17,6 +21,8 @@ import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; @ClusterScope(scope = Scope.SUITE) public class IndexAliasesTests extends ShieldIntegrationTest { @@ -68,6 +74,14 @@ public class IndexAliasesTests extends ShieldIntegrationTest { " 'alias_*,test_*': manage_aliases\n"; } + @Before + public void createBogusIndex() { + if (randomBoolean()) { + //randomly create an index with two aliases from user admin, to make sure it doesn't affect any of the test results + assertAcked(client().admin().indices().prepareCreate("index1").addAlias(new Alias("alias1")).addAlias(new Alias("alias2"))); + } + } + @Test public void testCreateIndexThenAliasesCreateOnlyPermission() { //user has create permission only: allows to create indices, manage_aliases is required to add/remove aliases @@ -131,6 +145,34 @@ public class IndexAliasesTests extends ShieldIntegrationTest { } } + @Test + public void testGetAliasesCreateOnlyPermission() { + //user has create permission only: allows to create indices, manage_aliases is required to retrieve aliases though + try { + client().admin().indices().prepareGetAliases("test_1").setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_only", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges"); + } catch(AuthorizationException e) { + assertThat(e.getMessage(), containsString("action [indices:admin/aliases/get] is unauthorized for user [create_only]")); + } + + try { + client().admin().indices().prepareGetAliases("_all").setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_only", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[_all]")); + } + + try { + client().admin().indices().prepareGetAliases("test_alias").setIndices("test_*").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_only", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[test_*]")); + } + } + @Test public void testCreateIndexThenAliasesCreateAndAliasesPermission() { //user has create and manage_aliases permission on test_*. manage_aliases is required to add/remove aliases on both aliases and indices @@ -217,6 +259,53 @@ public class IndexAliasesTests extends ShieldIntegrationTest { } } + @Test + public void testGetAliasesCreateAndAliasesPermission() { + //user has create and manage_aliases permission on test_*. manage_aliases is required to retrieve aliases on both aliases and indices + + assertAcked(client().admin().indices().prepareCreate("test_1").addAlias(new Alias("test_alias")) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray())))); + + //ok: user has manage_aliases on test_* + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_alias").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + //ok: user has manage_aliases on test_*, test_* gets resolved to test_1 + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_alias").setIndices("test_*") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + //ok: user has manage_aliases on test_*, empty indices gets resolved to _all indices (thus test_1) + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_alias") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + //ok: user has manage_aliases on test_*, _all aliases gets resolved to test_alias and empty indices gets resolved to _all indices (thus test_1) + assertAliases(client().admin().indices().prepareGetAliases().setAliases("_all").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + //ok: user has manage_aliases on test_*, test_* aliases gets resolved to test_alias and empty indices gets resolved to _all indices (thus test_1) + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_*").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + //ok: user has manage_aliases on test_*, _all aliases gets resolved to test_alias and _all indices becomes test_1 + assertAliases(client().admin().indices().prepareGetAliases().setAliases("_all").setIndices("_all") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + try { + //fails: user doesn't have manage_aliases on alias_1 + client().admin().indices().prepareGetAliases().setAliases("alias_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on alias_1"); + } catch(AuthorizationException e) { + assertThat(e.getMessage(), containsString("action [indices:admin/aliases/get] is unauthorized for user [create_test_aliases_test]")); + } + } + @Test public void testCreateIndexThenAliasesCreateAndAliasesPermission2() { //user has create permission on test_* and manage_aliases permission on alias_*. manage_aliases is required to add/remove aliases on both aliases and indices @@ -304,6 +393,58 @@ public class IndexAliasesTests extends ShieldIntegrationTest { } } + @Test + public void testGetAliasesCreateAndAliasesPermission2() { + //user has create permission on test_* and manage_aliases permission on alias_*. manage_aliases is required to retrieve aliases on both aliases and indices + assertAcked(client().admin().indices().prepareCreate("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray())))); + + try { + //fails: user doesn't have manage aliases on test_1, nor test_alias + client().admin().indices().prepareGetAliases().setAliases("test_alias").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on test_alias and test_1"); + } catch(AuthorizationException e) { + assertThat(e.getMessage(), containsString("action [indices:admin/aliases/get] is unauthorized for user [create_test_aliases_alias]")); + } + + try { + //fails: user doesn't have manage aliases on test_*, no matching indices to replace wildcards + client().admin().indices().prepareGetAliases().setIndices("test_*").setAliases("test_alias") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on test_*"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[test_*]")); + } + + try { + //fails: no existing indices to replace empty indices (thus _all) + client().admin().indices().prepareGetAliases().setAliases("test_alias") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on any index"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[_all]")); + } + + try { + //fails: no existing aliases to replace wildcards + client().admin().indices().prepareGetAliases().setIndices("test_1").setAliases("test_*") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on test_1"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[test_*]")); + } + + try { + //fails: no existing aliases to replace _all + client().admin().indices().prepareGetAliases().setIndices("test_1").setAliases("_all") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_alias", new SecuredString("test123".toCharArray()))).get(); + fail("get alias should have failed due to missing manage_aliases privileges on test_1"); + } catch(IndexMissingException e) { + assertThat(e.getMessage(), containsString("[_all]")); + } + } + @Test public void testCreateIndexThenAliasesCreateAndAliasesPermission3() { //user has create permission on test_* and manage_aliases permission on test_*,alias_*. All good. @@ -363,9 +504,79 @@ public class IndexAliasesTests extends ShieldIntegrationTest { } } + @Test + public void testGetAliasesCreateAndAliasesPermission3() { + //user has create permission on test_* and manage_aliases permission on test_*,alias_*. All good. + assertAcked(client().admin().indices().prepareCreate("test_1").addAlias(new Alias("test_alias")).addAlias(new Alias("alias_1")) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray())))); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_alias").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("alias_1").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "alias_1"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("alias_1").setIndices("test_*") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "alias_1"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("test_*").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "test_alias"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("_all").setIndices("test_1") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "alias_1", "test_alias"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("_all") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "alias_1", "test_alias"); + + assertAliases(client().admin().indices().prepareGetAliases().setAliases("alias_*").setIndices("test_*") + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_test_aliases_test_alias", new SecuredString("test123".toCharArray()))), + "test_1", "alias_1"); + } + @Test(expected = AuthorizationException.class) public void testCreateIndexAliasesOnlyPermission() { client().admin().indices().prepareCreate("test_1") .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("aliases_only", new SecuredString("test123".toCharArray()))).get(); } + + @Test + public void testGetAliasesAliasesOnlyPermission() { + //user has manage_aliases only permissions on both alias_* and test_* + + //ok: manage_aliases on both test_* and alias_* + GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases("alias_1").addIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("aliases_only", new SecuredString("test123".toCharArray()))).get(); + assertThat(getAliasesResponse.getAliases().isEmpty(), is(true)); + + try { + //fails: no manage_aliases privilege on non_authorized alias + client().admin().indices().prepareGetAliases("non_authorized").addIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("aliases_only", new SecuredString("test123".toCharArray()))).get(); + } catch(AuthorizationException e) { + assertThat(e.getMessage(), containsString("action [indices:admin/aliases/get] is unauthorized for user [aliases_only]")); + } + + try { + //fails: no manage_aliases privilege on non_authorized index + client().admin().indices().prepareGetAliases("alias_1").addIndices("non_authorized").setIndicesOptions(IndicesOptions.lenientExpandOpen()) + .putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("aliases_only", new SecuredString("test123".toCharArray()))).get(); + } catch(AuthorizationException e) { + assertThat(e.getMessage(), containsString("action [indices:admin/aliases/get] is unauthorized for user [aliases_only]")); + } + } + + private static void assertAliases(GetAliasesRequestBuilder getAliasesRequestBuilder, String index, String... aliases) { + GetAliasesResponse getAliasesResponse = getAliasesRequestBuilder.get(); + assertThat(getAliasesResponse.getAliases().size(), equalTo(1)); + assertThat(getAliasesResponse.getAliases().get(index).size(), equalTo(aliases.length)); + for (int i = 0; i < aliases.length; i++) { + assertThat(getAliasesResponse.getAliases().get(index).get(i).alias(), equalTo(aliases[i])); + } + } } diff --git a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java index 9413b8cc207..9128ca274d2 100644 --- a/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java +++ b/src/test/java/org/elasticsearch/shield/authz/indicesresolver/DefaultIndicesResolverTests.java @@ -7,6 +7,8 @@ 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.alias.get.GetAliasesAction; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.deletebyquery.DeleteByQueryAction; @@ -67,10 +69,12 @@ public class DefaultIndicesResolverTests extends ElasticsearchTestCase { 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, GetAliasesAction.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, IndicesAliasesAction.NAME)).thenReturn(ImmutableList.of()); + when(authzService.authorizedIndicesAndAliases(userNoIndices, GetAliasesAction.NAME)).thenReturn(ImmutableList.of()); when(authzService.authorizedIndicesAndAliases(userNoIndices, SearchAction.NAME)).thenReturn(ImmutableList.of()); when(authzService.authorizedIndicesAndAliases(userNoIndices, MultiSearchAction.NAME)).thenReturn(ImmutableList.of()); @@ -431,6 +435,157 @@ public class DefaultIndicesResolverTests extends ElasticsearchTestCase { assertThat(request.getAliasActions().get(1).aliases(), arrayContaining("foofoobar")); } + @Test + public void testResolveGetAliasesRequest() { + GetAliasesRequest request = new GetAliasesRequest("alias1").indices("foo", "foofoo"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all indices and aliases gets returned + String[] expectedIndices = new String[]{"alias1", "foo", "foofoo"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + assertThat(request.indices(), arrayContaining("foo", "foofoo")); + assertThat(request.aliases(), arrayContaining("alias1")); + } + + @Test + public void testResolveGetAliasesRequestMissingIndex() { + GetAliasesRequest request = new GetAliasesRequest(); + request.indices("missing"); + request.aliases("alias2"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all indices and aliases gets returned, missing is not an existing index/alias but that doesn't make any difference + String[] expectedIndices = new String[]{"alias2", "missing"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + assertThat(request.indices(), arrayContaining("missing")); + assertThat(request.aliases(), arrayContaining("alias2")); + } + + @Test + public void testResolveWildcardsGetAliasesRequest() { + GetAliasesRequest request = new GetAliasesRequest(); + request.aliases("alias1"); + request.indices("foo*"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all resolved indices and aliases gets returned, based on indices and aliases that user is authorized for + String[] expectedIndices = new String[]{"alias1", "foofoo", "foofoobar"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + //wildcards get replaced on each single action + assertThat(request.indices(), arrayContaining("foofoobar", "foofoo")); + assertThat(request.aliases(), arrayContaining("alias1")); + } + + @Test(expected = IndexMissingException.class) + public void testResolveWildcardsGetAliasesRequestNoMatchingIndices() { + GetAliasesRequest request = new GetAliasesRequest(); + request.aliases("alias3"); + request.indices("non_matching_*"); + //indices get resolved to no indices, request gets rejected + defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + } + + @Test + public void testResolveAllGetAliasesRequest() { + GetAliasesRequest request = new GetAliasesRequest(); + //even if not set, empty means _all + if (randomBoolean()) { + request.indices("_all"); + } + request.aliases("alias1"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all resolved indices and aliases gets returned + String[] expectedIndices = new String[]{"bar", "foofoobar", "foofoo", "alias1"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + String[] replacedIndices = new String[]{"bar", "foofoobar", "foofoo"}; + //_all gets replaced with all indices that user is authorized for + assertThat(request.indices(), arrayContaining(replacedIndices)); + assertThat(request.aliases(), arrayContaining("alias1")); + } + + @Test + public void testResolveAllGetAliasesRequestExpandWildcardsClosed() { + GetAliasesRequest request = new GetAliasesRequest(); + //set indices options to have wildcards resolved to open and closed indices (default is open only) + request.indicesOptions(IndicesOptions.fromOptions(true, false, true, true)); + //even if not set, empty means _all + if (randomBoolean()) { + request.indices("_all"); + } + request.aliases("alias1"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all resolved indices and aliases gets returned + String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "foofoo-closed", "alias1"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + String[] replacedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foofoo", "foofoo-closed"}; + //_all gets replaced with all indices that user is authorized for + assertThat(request.indices(), arrayContaining(replacedIndices)); + assertThat(request.aliases(), arrayContaining("alias1")); + } + + @Test(expected = IndexMissingException.class) + public void testResolveAllGetAliasesRequestNoAuthorizedIndices() { + GetAliasesRequest request = new GetAliasesRequest(); + request.aliases("alias1"); + request.indices("_all"); + //current user is not authorized for any index, _all resolves to no indices, the request fails + defaultIndicesResolver.resolve(userNoIndices, GetAliasesAction.NAME, request, metaData); + } + + @Test(expected = IndexMissingException.class) + public void testResolveWildcardsGetAliasesRequestNoAuthorizedIndices() { + GetAliasesRequest request = new GetAliasesRequest(); + request.aliases("alias1"); + request.indices("foo*"); + //current user is not authorized for any index, foo* resolves to no indices, the request fails + defaultIndicesResolver.resolve(userNoIndices, GetAliasesAction.NAME, request, metaData); + } + + @Test + public void testResolveAllAliasesGetAliasesRequest() { + GetAliasesRequest request = new GetAliasesRequest(); + request.aliases("_all"); + if (randomBoolean()) { + request.indices("_all"); + } + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //the union of all resolved indices and aliases gets returned + String[] expectedIndices = new String[]{"bar", "foofoobar", "foofoo"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + //_all gets replaced with all indices that user is authorized for + assertThat(request.indices(), arrayContaining(expectedIndices)); + assertThat(request.aliases(), arrayContaining("foofoobar")); + } + + @Test + public void testResolveAliasesWildcardsGetAliasesRequest() { + GetAliasesRequest request = new GetAliasesRequest(); + request.indices("*bar"); + request.aliases("foo*"); + Set indices = defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + //union of all resolved indices and aliases gets returned, based on what user is authorized for + //note that the index side will end up containing matching aliases too, which is fine, as es core would do + //the same and resolve those aliases to their corresponding concrete indices (which we let core do) + String[] expectedIndices = new String[]{"bar", "foofoobar"}; + assertThat(indices.size(), equalTo(expectedIndices.length)); + assertThat(indices, hasItems(expectedIndices)); + //alias foofoobar on both sides, that's fine, es core would do the same, same as above + assertThat(request.indices(), arrayContaining("bar", "foofoobar")); + assertThat(request.aliases(), arrayContaining("foofoobar")); + } + + @Test(expected = IndexMissingException.class) + public void testResolveAliasesWildcardsGetAliasesRequestNoAuthorizedIndices() { + GetAliasesRequest request = new GetAliasesRequest(); + //no authorized aliases match bar*, hence the request fails + request.aliases("bar*"); + request.indices("*bar"); + defaultIndicesResolver.resolve(user, GetAliasesAction.NAME, request, metaData); + } + //msearch is a CompositeIndicesRequest whose items (SearchRequests) implement IndicesRequest.Replaceable, wildcards will get replaced @Test public void testResolveMultiSearchNoWildcards() {