From 435a0803e176d57c062ecc3a7089ffd7bf297c99 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 26 Nov 2025 17:56:43 +1100 Subject: [PATCH 1/2] Add seperate interface for UIAM authentication This PR separate allowCrossProject from IndicesRequest.Replaceable into its own interface so that it can be implemented by requests that are not replaceable, such as ES|QL query request. Relates: ES-13662 --- .../elasticsearch/action/IndicesRequest.java | 36 +++++--- .../crossproject/CrossProjectModeDecider.java | 11 ++- .../CrossProjectModeDeciderTests.java | 91 +++++++++++++++++++ .../authz/IndicesAndAliasesResolver.java | 4 +- 4 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java diff --git a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java index baca5bdedffc3..c069ca9c02ae2 100644 --- a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java @@ -43,7 +43,30 @@ default boolean includeDataStreams() { return false; } - interface Replaceable extends IndicesRequest { + /** + * Close PIT, Clear Scroll are LegacyActionRequest + * Reindex is essentially a LegacyActionRequest as well as CompositeIndicesRequest + * EsqlQueryRequest also a LegacyActionRequest and CompositeIndicesRequest + * SQL query request is similar to ESQL + * Painless execute request is SingleIndexNoWildcards which is an IndicesRequest + * Project tags is a LegacyActionRequest. This one does not need action filter work + */ + interface CrossProjectCandidate { + + /** + * Determines whether the request type can support cross-project processing. Cross-project processing entails + * 1. UIAM authentication and authorization projects resolution. + * 2. Cross-project flat-world index resolution and error handling if applicable. + * Note: this method only determines in the request _supports_ cross-project. Whether cross-project processing + * is actually performed depends on other factors such is whether CPS is enabled and {@link IndicesOptions} if + * the request is an {@link IndicesRequest}, see also {@link org.elasticsearch.search.crossproject.CrossProjectModeDecider}. + */ + default boolean allowsCrossProject() { + return false; + } + } + + interface Replaceable extends IndicesRequest, CrossProjectCandidate { /** * Sets the indices that the action relates to. */ @@ -81,15 +104,6 @@ default boolean allowsRemoteIndices() { return false; } - /** - * Determines whether the request type allows cross-project processing. Cross-project processing entails cross-project search - * index resolution and error handling. Note: this method only determines in the request _supports_ cross-project. - * Whether cross-project processing is actually performed is determined by {@link IndicesOptions}. - */ - default boolean allowsCrossProject() { - return false; - } - @Nullable // if no routing is specified default String getProjectRouting() { return null; @@ -103,7 +117,7 @@ default String getProjectRouting() { * * This may change with https://github.com/elastic/elasticsearch/issues/105598 */ - interface SingleIndexNoWildcards extends IndicesRequest { + interface SingleIndexNoWildcards extends IndicesRequest, CrossProjectCandidate { default boolean allowsRemoteIndices() { return true; } diff --git a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java index ef47446bca54b..cc191b23f7de7 100644 --- a/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java +++ b/server/src/main/java/org/elasticsearch/search/crossproject/CrossProjectModeDecider.java @@ -44,11 +44,18 @@ public boolean crossProjectEnabled() { return crossProjectEnabled; } - public boolean resolvesCrossProject(IndicesRequest.Replaceable request) { + public boolean resolvesCrossProject(IndicesRequest.CrossProjectCandidate request) { if (crossProjectEnabled == false) { return false; } + // TODO: The following check can be an method on the request itself - return request.allowsCrossProject() && request.indicesOptions().resolveCrossProjectIndexExpression(); + if (request.allowsCrossProject() == false) { + return false; + } + if (request instanceof IndicesRequest indicesRequest) { + return indicesRequest.indicesOptions().resolveCrossProjectIndexExpression(); + } + return true; } } diff --git a/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java b/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java new file mode 100644 index 0000000000000..84822d0b8645f --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java @@ -0,0 +1,91 @@ +/* + * ELASTICSEARCH CONFIDENTIAL + * __________________ + * + * Copyright Elasticsearch B.V. All rights reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of Elasticsearch B.V. and its suppliers, if any. + * The intellectual and technical concepts contained herein + * are proprietary to Elasticsearch B.V. and its suppliers and + * may be covered by U.S. and Foreign Patents, patents in + * process, and are protected by trade secret or copyright + * law. Dissemination of this information or reproduction of + * this material is strictly forbidden unless prior written + * permission is obtained from Elasticsearch B.V. + */ + +package org.elasticsearch.search.crossproject; + +import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.is; + +public class CrossProjectModeDeciderTests extends ESTestCase { + + public void testResolvesCrossProject() { + doTestResolvesCrossProject(new CrossProjectModeDecider(Settings.builder().build()), false); + doTestResolvesCrossProject( + new CrossProjectModeDecider(Settings.builder().put("serverless.cross_project.enabled", true).build()), + true + ); + } + + private void doTestResolvesCrossProject(CrossProjectModeDecider crossProjectModeDecider, boolean expected) { + final var cpsIndicesOptions = IndicesOptions.builder(org.elasticsearch.action.support.IndicesOptions.DEFAULT) + .crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true)) + .build(); + + final var candidateButNotAllowed = randomFrom( + new CrossProjectCandidateImpl(false), + new IndicesRequestImpl(false, randomFrom(IndicesOptions.DEFAULT, cpsIndicesOptions)) + ); + final var candidateAndAllowed = new CrossProjectCandidateImpl(true); + + final var indicesRequestNoCpsOption = new IndicesRequestImpl(true, IndicesOptions.DEFAULT); + final var indicesRequestWithCpsOption = new IndicesRequestImpl(true, cpsIndicesOptions); + + assertFalse(crossProjectModeDecider.resolvesCrossProject(candidateButNotAllowed)); + assertFalse(crossProjectModeDecider.resolvesCrossProject(indicesRequestNoCpsOption)); + + assertThat(crossProjectModeDecider.resolvesCrossProject(candidateAndAllowed), is(expected)); + assertThat(crossProjectModeDecider.resolvesCrossProject(indicesRequestWithCpsOption), is(expected)); + } + + private static class CrossProjectCandidateImpl implements IndicesRequest.CrossProjectCandidate { + + private boolean allowsCrossProject; + + CrossProjectCandidateImpl(boolean allowsCrossProject) { + this.allowsCrossProject = allowsCrossProject; + } + + @Override + public boolean allowsCrossProject() { + return allowsCrossProject; + } + } + + private static class IndicesRequestImpl extends CrossProjectCandidateImpl implements IndicesRequest { + + private IndicesOptions indicesOptions; + + IndicesRequestImpl(boolean allowsCrossProject, IndicesOptions indicesOptions) { + super(allowsCrossProject); + this.indicesOptions = indicesOptions; + } + + @Override + public String[] indices() { + return new String[0]; + } + + @Override + public IndicesOptions indicesOptions() { + return indicesOptions; + } + } +} 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 d00922f4577f1..c63857770a7b2 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 @@ -167,7 +167,8 @@ ResolvedIndices tryResolveWithoutWildcards(String action, TransportRequest trans } boolean resolvesCrossProject(TransportRequest request) { - return request instanceof IndicesRequest.Replaceable replaceable && crossProjectModeDecider.resolvesCrossProject(replaceable); + return request instanceof IndicesRequest.CrossProjectCandidate crossProjectCandidate + && crossProjectModeDecider.resolvesCrossProject(crossProjectCandidate); } private static boolean requiresWildcardExpansion(IndicesRequest indicesRequest) { @@ -560,6 +561,7 @@ private static void setResolvedIndexExpressionsIfUnset(IndicesRequest.Replaceabl + "]"; logger.debug(message); // we are excepting `*,-*` below since we've observed this already -- keeping this assertion to catch other cases + // If more exceptions are found, we can add a comment to above linked issue and relax this check further assert replaceable.indices() == null || isNoneExpression(replaceable.indices()) : message; } } From 139eec40b0ea9ac7e6bea79337e18a218287a1ab Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Wed, 26 Nov 2025 18:26:54 +1100 Subject: [PATCH 2/2] comments --- .../elasticsearch/action/IndicesRequest.java | 15 ++++++-------- .../CrossProjectModeDeciderTests.java | 20 ++++++------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java index c069ca9c02ae2..af604454f1698 100644 --- a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java @@ -44,22 +44,19 @@ default boolean includeDataStreams() { } /** - * Close PIT, Clear Scroll are LegacyActionRequest - * Reindex is essentially a LegacyActionRequest as well as CompositeIndicesRequest - * EsqlQueryRequest also a LegacyActionRequest and CompositeIndicesRequest - * SQL query request is similar to ESQL - * Painless execute request is SingleIndexNoWildcards which is an IndicesRequest - * Project tags is a LegacyActionRequest. This one does not need action filter work + * Interface for indicating potential work related to cross-project authentication and authorization. */ interface CrossProjectCandidate { /** * Determines whether the request type can support cross-project processing. Cross-project processing entails * 1. UIAM authentication and authorization projects resolution. - * 2. Cross-project flat-world index resolution and error handling if applicable. + * 2. If applicable, cross-project flat-world index resolution and error handling * Note: this method only determines in the request _supports_ cross-project. Whether cross-project processing - * is actually performed depends on other factors such is whether CPS is enabled and {@link IndicesOptions} if - * the request is an {@link IndicesRequest}, see also {@link org.elasticsearch.search.crossproject.CrossProjectModeDecider}. + * is actually performed depends on other factors such as: + * - Whether CPS is enabled which impacts both 1 and 2. + * - Whether {@link IndicesOptions} supports it when the request is an {@link IndicesRequest}. This only impacts 2. + * See also {@link org.elasticsearch.search.crossproject.CrossProjectModeDecider}. */ default boolean allowsCrossProject() { return false; diff --git a/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java b/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java index 84822d0b8645f..e2ed79b71cd7e 100644 --- a/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java +++ b/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java @@ -1,18 +1,10 @@ /* - * ELASTICSEARCH CONFIDENTIAL - * __________________ - * - * Copyright Elasticsearch B.V. All rights reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of Elasticsearch B.V. and its suppliers, if any. - * The intellectual and technical concepts contained herein - * are proprietary to Elasticsearch B.V. and its suppliers and - * may be covered by U.S. and Foreign Patents, patents in - * process, and are protected by trade secret or copyright - * law. Dissemination of this information or reproduction of - * this material is strictly forbidden unless prior written - * permission is obtained from Elasticsearch B.V. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ package org.elasticsearch.search.crossproject;