diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java index 674435815e04d..a85e383f2fe8b 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java @@ -340,10 +340,12 @@ ResolvedIndices resolveIndicesAndAliases( String allIndicesPatternSelector = null; if (indicesRequest.indices() != null && indicesRequest.indices().length > 0) { // Always parse selectors, but do so lazily so that we don't spend a lot of time splitting strings each resolution - isAllIndices = IndexNameExpressionResolver.isAllIndices( - indicesList(indicesRequest.indices()), - (expr) -> IndexNameExpressionResolver.splitSelectorExpression(expr).v1() - ); + isAllIndices = IndexNameExpressionResolver.isAllIndices(indicesList(indicesRequest.indices()), (expr) -> { + var unprefixed = crossProjectModeDecider.resolvesCrossProject(replaceable) + ? RemoteClusterAware.splitIndexName(expr)[1] + : expr; + return IndexNameExpressionResolver.splitSelectorExpression(unprefixed).v1(); + }); if (isAllIndices) { // This parses the single all-indices expression for a second time in this conditional branch, but this is better than // parsing a potentially big list of indices on every request. @@ -394,12 +396,13 @@ ResolvedIndices resolveIndicesAndAliases( replaceable.getProjectRouting(), authorizedProjects ); - remoteIndices = CrossProjectIndexExpressionsRewriter.rewriteIndexExpression( + final var rewritten = CrossProjectIndexExpressionsRewriter.rewriteIndexExpression( indexExpression, resolvedProjects.originProjectAlias(), resolvedProjects.allProjectAliases() - ).remoteExpressions(); - if (resolvedProjects.originProject() == null) { + ); + remoteIndices = rewritten.remoteExpressions(); + if (resolvedProjects.originProject() == null || rewritten.localExpression() == null) { shouldExcludeLocalResolution = true; } } 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 e4bb9664beaee..f875c299cfca6 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 @@ -113,6 +113,7 @@ import static org.elasticsearch.action.ResolvedIndexExpression.LocalIndexResolutionResult.CONCRETE_RESOURCE_NOT_VISIBLE; import static org.elasticsearch.action.ResolvedIndexExpression.LocalIndexResolutionResult.CONCRETE_RESOURCE_UNAUTHORIZED; +import static org.elasticsearch.action.ResolvedIndexExpression.LocalIndexResolutionResult.NONE; import static org.elasticsearch.action.ResolvedIndexExpression.LocalIndexResolutionResult.SUCCESS; import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.newInstance; import static org.elasticsearch.test.ActionListenerUtils.anyActionListener; @@ -2956,6 +2957,118 @@ public void testCrossProjectSearchSelectorsNotAllowed() { assertThat(exception.getMessage(), equalTo("Selectors are not currently supported but was found in the expression [_all::data]")); } + public void testResolveAllWithWildcardRemotePrefix() { + when(crossProjectModeDecider.resolvesCrossProject(any(IndicesRequest.Replaceable.class))).thenReturn(true); + + var request = new SearchRequest().indices("*:_all"); + request.indicesOptions(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, true)); + var resolvedIndices = defaultIndicesResolver.resolveIndicesAndAliases( + "indices:/" + randomAlphaOfLength(8), + request, + projectMetadata, + buildAuthorizedIndices(user, TransportSearchAction.TYPE.name()), + new TargetProjects( + createRandomProjectWithAlias("local"), + List.of(createRandomProjectWithAlias("P1"), createRandomProjectWithAlias("P2"), createRandomProjectWithAlias("P3")) + ) + ); + + var expectedIndices = new String[] { "bar", "foobarfoo", "bar-closed", "foofoobar", "foofoo-closed", "foofoo" }; + + assertThat(resolvedIndices.getLocal(), contains(expectedIndices)); + assertThat(resolvedIndices.getRemote(), containsInAnyOrder("P1:_all", "P2:_all", "P3:_all")); + + final var resolved = request.getResolvedIndexExpressions(); + assertThat(resolved, is(notNullValue())); + assertThat( + resolved.expressions(), + contains(resolvedIndexExpression("*:_all", Set.of(expectedIndices), SUCCESS, Set.of("P1:_all", "P2:_all", "P3:_all"))) + ); + } + + public void testResolveAllWithLocalPrefix() { + when(crossProjectModeDecider.resolvesCrossProject(any(IndicesRequest.Replaceable.class))).thenReturn(true); + + var expression = randomBoolean() ? "local:_all" : "_origin:_all"; + var request = new SearchRequest().indices(expression); + request.indicesOptions(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, true)); + var resolvedIndices = defaultIndicesResolver.resolveIndicesAndAliases( + "indices:/" + randomAlphaOfLength(8), + request, + projectMetadata, + buildAuthorizedIndices(user, TransportSearchAction.TYPE.name()), + new TargetProjects( + createRandomProjectWithAlias("local"), + List.of(createRandomProjectWithAlias("P1"), createRandomProjectWithAlias("P2"), createRandomProjectWithAlias("P3")) + ) + ); + + var expectedIndices = new String[] { "bar", "foobarfoo", "bar-closed", "foofoobar", "foofoo-closed", "foofoo" }; + + assertThat(resolvedIndices.getLocal(), contains(expectedIndices)); + assertThat(resolvedIndices.getRemote(), is(empty())); + + final var resolved = request.getResolvedIndexExpressions(); + assertThat(resolved, is(notNullValue())); + assertThat(resolved.expressions(), contains(resolvedIndexExpression(expression, Set.of(expectedIndices), SUCCESS, Set.of()))); + } + + public void testResolveAllWithRemotePrefix() { + when(crossProjectModeDecider.resolvesCrossProject(any(IndicesRequest.Replaceable.class))).thenReturn(true); + + var request = new SearchRequest().indices("P*:_all"); + request.indicesOptions(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, true)); + var resolvedIndices = defaultIndicesResolver.resolveIndicesAndAliases( + "indices:/" + randomAlphaOfLength(8), + request, + projectMetadata, + buildAuthorizedIndices(user, TransportSearchAction.TYPE.name()), + new TargetProjects( + createRandomProjectWithAlias("local"), + List.of(createRandomProjectWithAlias("P1"), createRandomProjectWithAlias("P2"), createRandomProjectWithAlias("P3")) + ) + ); + + assertThat(resolvedIndices.getLocal(), is(empty())); + assertThat(resolvedIndices.getRemote(), contains("P1:_all", "P2:_all", "P3:_all")); + + final var resolved = request.getResolvedIndexExpressions(); + assertThat(resolved, is(notNullValue())); + assertThat( + resolved.expressions(), + contains(resolvedIndexExpression("P*:_all", Set.of(), NONE, Set.of("P1:_all", "P2:_all", "P3:_all"))) + ); + } + + public void testResolveIndexWithRemotePrefix() { + when(crossProjectModeDecider.resolvesCrossProject(any(IndicesRequest.Replaceable.class))).thenReturn(true); + + var request = new SearchRequest().indices("*:bar"); + request.indicesOptions(IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), true, true)); + var resolvedIndices = defaultIndicesResolver.resolveIndicesAndAliases( + "indices:/" + randomAlphaOfLength(8), + request, + projectMetadata, + buildAuthorizedIndices(user, TransportSearchAction.TYPE.name()), + new TargetProjects( + createRandomProjectWithAlias("local"), + List.of(createRandomProjectWithAlias("P1"), createRandomProjectWithAlias("P2"), createRandomProjectWithAlias("P3")) + ) + ); + + var expectedIndices = new String[] { "bar" }; + + assertThat(resolvedIndices.getLocal(), contains(expectedIndices)); + assertThat(resolvedIndices.getRemote(), containsInAnyOrder("P1:bar", "P2:bar", "P3:bar")); + + final var resolved = request.getResolvedIndexExpressions(); + assertThat(resolved, is(notNullValue())); + assertThat( + resolved.expressions(), + contains(resolvedIndexExpression("*:bar", Set.of(expectedIndices), SUCCESS, Set.of("P1:bar", "P2:bar", "P3:bar"))) + ); + } + private void assertIndicesMatch(IndicesRequest.Replaceable request, String expression, List indices, String[] expectedIndices) { assertThat(indices, hasSize(expectedIndices.length)); assertThat(request.indices().length, equalTo(expectedIndices.length));