From b4a3f9c79ead8a148e537cc3300d8d86d29f5de4 Mon Sep 17 00:00:00 2001 From: Pat Whelan Date: Tue, 28 Oct 2025 15:51:59 -0400 Subject: [PATCH 1/2] [CPS] current thoughts on project routing resolution --- .../security/authz/AuthorizationService.java | 29 +++++++++++++++++++ .../authz/IndicesAndAliasesResolver.java | 1 + 2 files changed, 30 insertions(+) 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..e11011a7e196b 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,7 +49,9 @@ 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.ProjectRoutingInfo; import org.elasticsearch.search.crossproject.TargetProjects; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.LinkedProjectConfigService; @@ -106,6 +108,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 +507,38 @@ 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(), From a48bbc7945f15c6f4af8c32692b3183ee0093a30 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Tue, 28 Oct 2025 20:06:08 +0000 Subject: [PATCH 2/2] [CI] Auto commit changes from spotless --- .../xpack/security/authz/AuthorizationService.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 e11011a7e196b..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 @@ -51,7 +51,6 @@ import org.elasticsearch.search.crossproject.CrossProjectModeDecider; import org.elasticsearch.search.crossproject.CrossProjectRoutingResolver; import org.elasticsearch.search.crossproject.NoMatchingProjectException; -import org.elasticsearch.search.crossproject.ProjectRoutingInfo; import org.elasticsearch.search.crossproject.TargetProjects; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.LinkedProjectConfigService; @@ -522,10 +521,7 @@ private void authorizeAction( targetProjects.originProject(), targetProjects.linkedProjects() ); - var originOrNull = projectRoutingInfos.stream() - .filter(targetProjects.originProject()::equals) - .findAny() - .orElse(null); + var originOrNull = projectRoutingInfos.stream().filter(targetProjects.originProject()::equals).findAny().orElse(null); var linkedProjects = projectRoutingInfos.stream() .filter(Predicate.not(targetProjects.originProject()::equals)) .toList();