diff --git a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java index baca5bdedffc3..af604454f1698 100644 --- a/server/src/main/java/org/elasticsearch/action/IndicesRequest.java +++ b/server/src/main/java/org/elasticsearch/action/IndicesRequest.java @@ -43,7 +43,27 @@ default boolean includeDataStreams() { return false; } - interface Replaceable extends IndicesRequest { + /** + * 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. 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 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; + } + } + + interface Replaceable extends IndicesRequest, CrossProjectCandidate { /** * Sets the indices that the action relates to. */ @@ -81,15 +101,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 +114,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..e2ed79b71cd7e --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/crossproject/CrossProjectModeDeciderTests.java @@ -0,0 +1,83 @@ +/* + * 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; + +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; } }