diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index b9d73a6c47106..d1e97561decb5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -49,6 +49,7 @@ import org.elasticsearch.indices.InvalidIndexNameException; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.search.crossproject.CrossProjectModeDecider; +import org.elasticsearch.search.crossproject.CrossProjectRoutingResolver; import org.elasticsearch.search.crossproject.NoMatchingProjectException; import org.elasticsearch.search.crossproject.TargetProjects; import org.elasticsearch.threadpool.ThreadPool; @@ -106,6 +107,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.Supplier; import static org.elasticsearch.action.support.ContextPreservingActionListener.wrapPreservingContext; @@ -504,12 +506,35 @@ private void authorizeAction( final SubscribableListener targetProjectListener; if (indicesAndAliasesResolver.resolvesCrossProject(request)) { targetProjectListener = new SubscribableListener<>(); + // Get list of projects from cluster state + // Remove projects from list that user lacks permissions to access authorizedProjectsResolver.resolveAuthorizedProjects(targetProjectListener); + + // project routing resolution could go here + // regardless of location, it would look something like... + targetProjectListener.andThenApply(targetProjects -> { + // we'd inject the CrossProjectRoutingResolver + // we somehow pipe project_routing through to here, + // perhaps setting it in the SearchRequest so we can access it via RequestInfo.getRequest() + var projectRoutingInfos = new CrossProjectRoutingResolver().resolve( + "*", + targetProjects.originProject(), + targetProjects.linkedProjects() + ); + var originOrNull = projectRoutingInfos.stream().filter(targetProjects.originProject()::equals).findAny().orElse(null); + var linkedProjects = projectRoutingInfos.stream() + .filter(Predicate.not(targetProjects.originProject()::equals)) + .toList(); + // perhaps we change the CrossProjectRoutingResolver API to accept and return TargetProjects? + return new TargetProjects(originOrNull, linkedProjects); + }); } else { targetProjectListener = SubscribableListener.newSucceeded(TargetProjects.LOCAL_ONLY_FOR_CPS_DISABLED); } targetProjectListener.addListener(ActionListener.wrap(targetProjects -> { + // this will eventually rewrite the index expression based on the targetProjects, + // so filtering by project_routing should go before here final AsyncSupplier resolvedIndicesAsyncSupplier = makeResolvedIndicesAsyncSupplier( targetProjects, requestInfo, 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 17d932db9f0dd..9aba35219ec33 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 @@ -383,6 +383,7 @@ ResolvedIndices resolveIndicesAndAliases( Set remoteIndices = Collections.emptySet(); if (crossProjectModeDecider.resolvesCrossProject(replaceable)) { + // project routing resolution could go here, before the index expression is generated remoteIndices = CrossProjectIndexExpressionsRewriter.rewriteIndexExpression( indexExpression, authorizedProjects.originProjectAlias(),