diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesRequest.java b/server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesRequest.java index 96b1d0ff901..645bb446e1e 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/GetAliasesRequest.java @@ -33,7 +33,7 @@ public class GetAliasesRequest extends MasterNodeReadRequest private String[] indices = Strings.EMPTY_ARRAY; private String[] aliases = Strings.EMPTY_ARRAY; - private IndicesOptions indicesOptions = IndicesOptions.strictExpand(); + private IndicesOptions indicesOptions = IndicesOptions.strictExpandHidden(); private String[] originalAliases = Strings.EMPTY_ARRAY; public GetAliasesRequest(String... aliases) { diff --git a/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java b/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java index 4a0f53ad119..c6d24670f08 100644 --- a/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java +++ b/server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java @@ -124,6 +124,9 @@ public class IndicesOptions implements ToXContentFragment { EnumSet.of(WildcardStates.OPEN, WildcardStates.CLOSED, WildcardStates.HIDDEN)); public static final IndicesOptions STRICT_EXPAND_OPEN_CLOSED = new IndicesOptions(EnumSet.of(Option.ALLOW_NO_INDICES), EnumSet.of(WildcardStates.OPEN, WildcardStates.CLOSED)); + public static final IndicesOptions STRICT_EXPAND_OPEN_CLOSED_HIDDEN = + new IndicesOptions(EnumSet.of(Option.ALLOW_NO_INDICES), + EnumSet.of(WildcardStates.OPEN, WildcardStates.CLOSED, WildcardStates.HIDDEN)); public static final IndicesOptions STRICT_EXPAND_OPEN_FORBID_CLOSED = new IndicesOptions(EnumSet.of(Option.ALLOW_NO_INDICES, Option.FORBID_CLOSED_INDICES), EnumSet.of(WildcardStates.OPEN)); public static final IndicesOptions STRICT_EXPAND_OPEN_HIDDEN_FORBID_CLOSED = @@ -412,6 +415,14 @@ public class IndicesOptions implements ToXContentFragment { return STRICT_EXPAND_OPEN_CLOSED; } + /** + * @return indices option that requires every specified index to exist, expands wildcards to both open and closed indices, includes + * hidden indices, and allows that no indices are resolved from wildcard expressions (not returning an error). + */ + public static IndicesOptions strictExpandHidden() { + return STRICT_EXPAND_OPEN_CLOSED_HIDDEN; + } + /** * @return indices option that requires each specified index or alias to exist, doesn't expand wildcards and * throws error if any of the aliases resolves to multiple indices diff --git a/server/src/test/java/org/elasticsearch/index/HiddenIndexIT.java b/server/src/test/java/org/elasticsearch/index/HiddenIndexIT.java index 78c8e4bd30a..e8d9327b5db 100644 --- a/server/src/test/java/org/elasticsearch/index/HiddenIndexIT.java +++ b/server/src/test/java/org/elasticsearch/index/HiddenIndexIT.java @@ -19,6 +19,9 @@ package org.elasticsearch.index; +import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequestBuilder; +import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse; import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse; import org.elasticsearch.action.search.SearchResponse; @@ -36,7 +39,10 @@ import java.util.Map; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; public class HiddenIndexIT extends ESIntegTestCase { @@ -135,4 +141,63 @@ public class HiddenIndexIT extends ESIntegTestCase { GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings("my_hidden_pattern1").get(); assertThat(getSettingsResponse.getSetting("my_hidden_pattern1", "index.hidden"), is("true")); } + + public void testAliasesForHiddenIndices() { + final String hiddenIndex = "hidden-index"; + final String visibleAlias = "alias-visible"; + final String hiddenAlias = "alias-hidden"; + final String dotHiddenAlias = ".alias-hidden"; + + assertAcked(client().admin().indices().prepareCreate(hiddenIndex) + .setSettings(Settings.builder().put("index.hidden", true).build()) + .get()); + + assertAcked(admin().indices().prepareAliases() + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(hiddenIndex).alias(visibleAlias))); + + // The index should be returned here when queried by name or by wildcard because the alias is visible + final GetAliasesRequestBuilder req = client().admin().indices().prepareGetAliases(visibleAlias); + GetAliasesResponse response = req.get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), equalTo(visibleAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), nullValue()); + + response = client().admin().indices().prepareGetAliases("alias*").get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), equalTo(visibleAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), nullValue()); + + // Now try with a hidden alias + assertAcked(admin().indices().prepareAliases() + .addAliasAction(IndicesAliasesRequest.AliasActions.remove().index(hiddenIndex).alias(visibleAlias)) + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(hiddenIndex).alias(hiddenAlias).isHidden(true))); + + // Querying by name directly should get the right result + response = client().admin().indices().prepareGetAliases(hiddenAlias).get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), equalTo(hiddenAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), equalTo(true)); + + // querying by wildcard should get the right result because the indices options include hidden by default + response = client().admin().indices().prepareGetAliases("alias*").get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), equalTo(hiddenAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), equalTo(true)); + + // But we should get no results if we specify indices options that don't include hidden + response = client().admin().indices().prepareGetAliases("alias*") + .setIndicesOptions(IndicesOptions.strictExpandOpen()).get(); + assertThat(response.getAliases().get(hiddenIndex), nullValue()); + + // Now try with a hidden alias that starts with a dot + assertAcked(admin().indices().prepareAliases() + .addAliasAction(IndicesAliasesRequest.AliasActions.remove().index(hiddenIndex).alias(hiddenAlias)) + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(hiddenIndex).alias(dotHiddenAlias).isHidden(true))); + + // Check that querying by dot-prefixed pattern returns the alias + response = client().admin().indices().prepareGetAliases(".alias*").get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), equalTo(dotHiddenAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), equalTo(true)); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java index a6216ea2665..58a9b72a330 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.authz; import org.elasticsearch.action.admin.indices.alias.Alias; 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.GetAliasesRequestBuilder; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse; @@ -14,9 +15,11 @@ import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.client.Client; import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.rest.action.admin.indices.AliasesNotFoundException; import org.elasticsearch.test.SecurityIntegTestCase; +import org.hamcrest.Matchers; import org.junit.Before; import java.util.Collections; @@ -28,6 +31,8 @@ import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswo import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.nullValue; public class IndexAliasesTests extends SecurityIntegTestCase { @@ -590,6 +595,61 @@ public class IndexAliasesTests extends SecurityIntegTestCase { assertAliases(client().admin().indices().prepareGetAliases().setAliases("*"), "bogus_index_1", "bogus_alias_1", "bogus_alias_2"); } + public void testAliasesForHiddenIndices() { + final String hiddenIndex = "test_hidden"; + final String visibleAlias = "alias_visible"; + final String hiddenAlias = "alias_hidden"; + + final Map createHeaders = Collections.singletonMap( + BASIC_AUTH_HEADER, basicAuthHeaderValue("all_on_test", new SecureString("test123".toCharArray()))); + final Client createClient = client(createHeaders); + + final Map aliasHeaders = Collections.singletonMap( + BASIC_AUTH_HEADER, basicAuthHeaderValue("aliases_only", new SecureString("test123".toCharArray()))); + final Client aliasesClient = client(aliasHeaders); + + assertAcked(createClient.admin().indices().prepareCreate(hiddenIndex) + .setSettings(Settings.builder().put("index.hidden", true).build()) + .get()); + + assertAcked(aliasesClient.admin().indices().prepareAliases() + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(hiddenIndex).alias(visibleAlias))); + + // The index should be returned here when queried by name or by wildcard because the alias is visible + final GetAliasesRequestBuilder req = aliasesClient.admin().indices().prepareGetAliases(visibleAlias); + GetAliasesResponse response = req.get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), Matchers.equalTo(visibleAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), nullValue()); + + response = client().admin().indices().prepareGetAliases("alias*").get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), Matchers.equalTo(visibleAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), nullValue()); + + // Now try with a hidden alias + assertAcked(aliasesClient.admin().indices().prepareAliases() + .addAliasAction(IndicesAliasesRequest.AliasActions.remove().index(hiddenIndex).alias(visibleAlias)) + .addAliasAction(IndicesAliasesRequest.AliasActions.add().index(hiddenIndex).alias(hiddenAlias).isHidden(true))); + + // Querying by name directly should get the right result + response = aliasesClient.admin().indices().prepareGetAliases(hiddenAlias).get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), Matchers.equalTo(hiddenAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), Matchers.equalTo(true)); + + // querying by wildcard should get the right result because the indices options include hidden by default + response = aliasesClient.admin().indices().prepareGetAliases("alias*").get(); + assertThat(response.getAliases().get(hiddenIndex), hasSize(1)); + assertThat(response.getAliases().get(hiddenIndex).get(0).alias(), Matchers.equalTo(hiddenAlias)); + assertThat(response.getAliases().get(hiddenIndex).get(0).isHidden(), Matchers.equalTo(true)); + + // But we should get no results if we specify indices options that don't include hidden + response = aliasesClient.admin().indices().prepareGetAliases("alias*") + .setIndicesOptions(IndicesOptions.strictExpandOpen()).get(); + assertThat(response.getAliases().get(hiddenIndex), nullValue()); + } + private static Client client(final Map headers) { // it should not matter what client we send the request to, but let's pin all requests to a specific node final Client client; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java index fc1bf779e12..a681a12e8ac 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java @@ -985,11 +985,12 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { request.aliases("alias1"); final List authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - //the union of all resolved indices and aliases gets returned - String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "alias1"}; - assertThat(indices.size(), equalTo(expectedIndices.length)); - assertThat(indices, hasItems(expectedIndices)); - String[] replacedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed"}; + //the union of all resolved indices and aliases gets returned, including hidden indices as Get Aliases includes hidden by default + String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "alias1", + "hidden-open", "hidden-closed", ".hidden-open", ".hidden-closed"}; + assertSameValues(indices, expectedIndices); + String[] replacedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "hidden-open", + "hidden-closed", ".hidden-open", ".hidden-closed"}; //_all gets replaced with all indices that user is authorized for assertThat(request.indices(), arrayContainingInAnyOrder(replacedIndices)); assertThat(request.aliases(), arrayContainingInAnyOrder("alias1")); @@ -1065,8 +1066,9 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { } final List authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - //the union of all resolved indices and aliases gets returned - String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed"}; + //the union of all resolved indices and aliases gets returned, including hidden indices as Get Aliases includes hidden by default + String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "hidden-open", + "hidden-closed", ".hidden-open", ".hidden-closed"}; assertSameValues(indices, expectedIndices); //_all gets replaced with all indices that user is authorized for assertThat(request.indices(), arrayContainingInAnyOrder(expectedIndices)); @@ -1080,11 +1082,14 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { } final List authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - //the union of all resolved indices and aliases gets returned - String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "explicit"}; + //the union of all resolved indices and aliases gets returned, including hidden indices as Get Aliases includes hidden by default + String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "explicit", + "hidden-open", "hidden-closed", ".hidden-open", ".hidden-closed"}; + logger.info("indices: {}", indices); assertSameValues(indices, expectedIndices); //_all gets replaced with all indices that user is authorized for - assertThat(request.indices(), arrayContainingInAnyOrder("bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed")); + assertThat(request.indices(), arrayContainingInAnyOrder("bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", + "hidden-open", "hidden-closed", ".hidden-open", ".hidden-closed")); assertThat(request.aliases(), arrayContainingInAnyOrder("foofoobar", "foobarfoo", "explicit")); } @@ -1095,8 +1100,9 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { } final List authorizedIndices = buildAuthorizedIndices(user, GetAliasesAction.NAME); List indices = resolveIndices(request, authorizedIndices).getLocal(); - //the union of all resolved indices and aliases gets returned - String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed"}; + //the union of all resolved indices and aliases gets returned, including hidden indices as Get Aliases includes hidden by default + String[] expectedIndices = new String[]{"bar", "bar-closed", "foofoobar", "foobarfoo", "foofoo", "foofoo-closed", "hidden-open", + "hidden-closed", ".hidden-open", ".hidden-closed"}; assertSameValues(indices, expectedIndices); //_all gets replaced with all indices that user is authorized for assertThat(request.indices(), arrayContainingInAnyOrder(expectedIndices)); @@ -1136,10 +1142,13 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { //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", "bar-closed", "foobarfoo", "foofoo", "foofoo-closed", "foofoobar"}; + //also includes hidden indices as Get Aliases includes hidden by default + String[] expectedIndices = new String[]{"bar", "bar-closed", "foobarfoo", "foofoo", "foofoo-closed", "foofoobar", "hidden-open", + "hidden-closed", ".hidden-open", ".hidden-closed"}; assertSameValues(indices, expectedIndices); //alias foofoobar on both sides, that's fine, es core would do the same, same as above - assertThat(request.indices(), arrayContainingInAnyOrder("bar", "bar-closed", "foobarfoo", "foofoo", "foofoo-closed", "foofoobar")); + assertThat(request.indices(), arrayContainingInAnyOrder("bar", "bar-closed", "foobarfoo", "foofoo", "foofoo-closed", "foofoobar", + "hidden-open", "hidden-closed", ".hidden-open", ".hidden-closed")); assertThat(request.aliases(), arrayContainingInAnyOrder("foofoobar")); }