From 5ddf6386b4f06f97c07ed50918a5b417d074f653 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 27 Oct 2025 15:50:26 +0000 Subject: [PATCH 1/3] ES-12969 Prohibit _reindex, _delete_by_query and _update_by_query from issuing cross-project calls --- .../AbstractBaseReindexRestHandler.java | 4 ++++ .../metadata/IndexNameExpressionResolver.java | 20 +++++++++++++------ .../reindex/AbstractBulkByScrollRequest.java | 7 +++++++ .../index/reindex/ReindexRequest.java | 5 +++++ .../IndexNameExpressionResolverTests.java | 14 +++++++++++++ 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/modules/reindex/src/main/java/org/elasticsearch/reindex/AbstractBaseReindexRestHandler.java b/modules/reindex/src/main/java/org/elasticsearch/reindex/AbstractBaseReindexRestHandler.java index f9721fd0bcc13..814e2c6841349 100644 --- a/modules/reindex/src/main/java/org/elasticsearch/reindex/AbstractBaseReindexRestHandler.java +++ b/modules/reindex/src/main/java/org/elasticsearch/reindex/AbstractBaseReindexRestHandler.java @@ -44,6 +44,10 @@ protected RestChannelConsumer doPrepareRequest(RestRequest request, NodeClient c // Build the internal request Request internal = setCommonOptions(request, buildRequest(request)); + // Only requests supporting remote indices can have IndicesOptions allowing cross-project index expressions + assert internal.supportsRemoteIndicesSearch() + || internal.getSearchRequest().indicesOptions().resolveCrossProjectIndexExpression() == false; + // Executes the request and waits for completion if (request.paramAsBoolean("wait_for_completion", true)) { Map params = new HashMap<>(); diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index fc7a63a475a75..6ae541841e204 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -479,18 +479,26 @@ private static void ensureRemoteExpressionRequireIgnoreUnavailable(IndicesOption return; } if (RemoteClusterAware.isRemoteIndexName(current)) { - List crossClusterIndices = new ArrayList<>(); - for (int i = 0; i < expressions.length; i++) { - if (RemoteClusterAware.isRemoteIndexName(expressions[i])) { - crossClusterIndices.add(expressions[i]); - } - } + List crossClusterIndices = getRemoteIndexExpressions(expressions); throw new IllegalArgumentException( "Cross-cluster calls are not supported in this context but remote indices were requested: " + crossClusterIndices ); } } + /** + * Extracts the list of remote index expressions from the given array of index expressions + */ + public static List getRemoteIndexExpressions(String... expressions) { + List crossClusterIndices = new ArrayList<>(); + for (int i = 0; i < expressions.length; i++) { + if (RemoteClusterAware.isRemoteIndexName(expressions[i])) { + crossClusterIndices.add(expressions[i]); + } + } + return crossClusterIndices; + } + /** * Translates the provided index expression into actual concrete indices, properly deduplicated. * diff --git a/server/src/main/java/org/elasticsearch/index/reindex/AbstractBulkByScrollRequest.java b/server/src/main/java/org/elasticsearch/index/reindex/AbstractBulkByScrollRequest.java index 2f63776d7f1c5..b7a188d3da1ca 100644 --- a/server/src/main/java/org/elasticsearch/index/reindex/AbstractBulkByScrollRequest.java +++ b/server/src/main/java/org/elasticsearch/index/reindex/AbstractBulkByScrollRequest.java @@ -137,6 +137,13 @@ public AbstractBulkByScrollRequest(SearchRequest searchRequest, boolean setDefau */ protected abstract Self self(); + /** + * Whether the request supports remote indices in the search request. + */ + public boolean supportsRemoteIndicesSearch() { + return false; + } + @Override public ActionRequestValidationException validate() { ActionRequestValidationException e = searchRequest.validate(); diff --git a/server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java b/server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java index c2fb9171fc709..035aefd96c8cc 100644 --- a/server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java +++ b/server/src/main/java/org/elasticsearch/index/reindex/ReindexRequest.java @@ -83,6 +83,11 @@ protected ReindexRequest self() { return this; } + @Override + public boolean supportsRemoteIndicesSearch() { + return true; + } + @Override public ActionRequestValidationException validate() { ActionRequestValidationException e = super.validate(); diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java index 51d5e48f8c1bb..1182c8eebfca7 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -78,6 +78,8 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasLength; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -3486,6 +3488,18 @@ public void testResolveWriteIndexAbstractionMultipleMatches() { ); } + public void testGetRemoteIndexExpressions() { + { + List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions("index-1"); + assertThat(remoteIndexExpressions, empty()); + } + { + List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions("index-1", "remote:index-1", "idx-*", "remote-2:idx-5"); + assertThat(remoteIndexExpressions, hasSize(2)); + assertThat(remoteIndexExpressions, contains("remote:index-1", "remote-2:idx-5")); + } + } + public static IndexMetadata.Builder indexBuilder(String index) { return indexBuilder(index, Settings.EMPTY); } From ed101ee549568467243fcf076d5bb847a5d7a501 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Mon, 27 Oct 2025 16:01:14 +0000 Subject: [PATCH 2/3] [CI] Auto commit changes from spotless --- .../metadata/IndexNameExpressionResolverTests.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java index 1182c8eebfca7..c8b46e8402c04 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -78,7 +78,6 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasLength; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -3494,7 +3493,12 @@ public void testGetRemoteIndexExpressions() { assertThat(remoteIndexExpressions, empty()); } { - List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions("index-1", "remote:index-1", "idx-*", "remote-2:idx-5"); + List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions( + "index-1", + "remote:index-1", + "idx-*", + "remote-2:idx-5" + ); assertThat(remoteIndexExpressions, hasSize(2)); assertThat(remoteIndexExpressions, contains("remote:index-1", "remote-2:idx-5")); } From 313bed5e995d5ee3ac682322e3abd0a13eb1c5f3 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 5 Nov 2025 14:46:57 +0000 Subject: [PATCH 3/3] Address review comments --- .../metadata/IndexNameExpressionResolver.java | 15 +------------- .../transport/RemoteClusterAware.java | 13 ++++++++++++ .../IndexNameExpressionResolverTests.java | 18 ----------------- .../transport/RemoteClusterAwareTests.java | 20 +++++++++++++++++++ 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index 6ae541841e204..8861cd73fd4fc 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -479,26 +479,13 @@ private static void ensureRemoteExpressionRequireIgnoreUnavailable(IndicesOption return; } if (RemoteClusterAware.isRemoteIndexName(current)) { - List crossClusterIndices = getRemoteIndexExpressions(expressions); + List crossClusterIndices = RemoteClusterAware.getRemoteIndexExpressions(expressions); throw new IllegalArgumentException( "Cross-cluster calls are not supported in this context but remote indices were requested: " + crossClusterIndices ); } } - /** - * Extracts the list of remote index expressions from the given array of index expressions - */ - public static List getRemoteIndexExpressions(String... expressions) { - List crossClusterIndices = new ArrayList<>(); - for (int i = 0; i < expressions.length; i++) { - if (RemoteClusterAware.isRemoteIndexName(expressions[i])) { - crossClusterIndices.add(expressions[i]); - } - } - return crossClusterIndices; - } - /** * Translates the provided index expression into actual concrete indices, properly deduplicated. * diff --git a/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java b/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java index 50e1be5fdba48..86f15184ece21 100644 --- a/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java +++ b/server/src/main/java/org/elasticsearch/transport/RemoteClusterAware.java @@ -69,6 +69,19 @@ public static boolean isRemoteIndexName(String indexExpression) { return idx > 0 && isSelector == false; } + /** + * Extracts the list of remote index expressions from the given array of index expressions + */ + public static List getRemoteIndexExpressions(String... expressions) { + List crossClusterIndices = new ArrayList<>(); + for (int i = 0; i < expressions.length; i++) { + if (isRemoteIndexName(expressions[i])) { + crossClusterIndices.add(expressions[i]); + } + } + return crossClusterIndices; + } + /** * @param indexExpression expects a single index expression at a time (not a csv list of expression) * @return cluster alias in the index expression. If none is present, returns RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java index c8b46e8402c04..51d5e48f8c1bb 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java @@ -78,7 +78,6 @@ import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -3487,23 +3486,6 @@ public void testResolveWriteIndexAbstractionMultipleMatches() { ); } - public void testGetRemoteIndexExpressions() { - { - List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions("index-1"); - assertThat(remoteIndexExpressions, empty()); - } - { - List remoteIndexExpressions = IndexNameExpressionResolver.getRemoteIndexExpressions( - "index-1", - "remote:index-1", - "idx-*", - "remote-2:idx-5" - ); - assertThat(remoteIndexExpressions, hasSize(2)); - assertThat(remoteIndexExpressions, contains("remote:index-1", "remote-2:idx-5")); - } - } - public static IndexMetadata.Builder indexBuilder(String index) { return indexBuilder(index, Settings.EMPTY); } diff --git a/server/src/test/java/org/elasticsearch/transport/RemoteClusterAwareTests.java b/server/src/test/java/org/elasticsearch/transport/RemoteClusterAwareTests.java index 073b5bf0b4b52..c5d33fa3d38f3 100644 --- a/server/src/test/java/org/elasticsearch/transport/RemoteClusterAwareTests.java +++ b/server/src/test/java/org/elasticsearch/transport/RemoteClusterAwareTests.java @@ -16,10 +16,13 @@ import java.util.Map; import java.util.Set; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; public class RemoteClusterAwareTests extends ESTestCase { @@ -158,6 +161,23 @@ public void testGroupClusterIndicesFail() { } + public void testGetRemoteIndexExpressions() { + { + List remoteIndexExpressions = RemoteClusterAware.getRemoteIndexExpressions("index-1"); + assertThat(remoteIndexExpressions, empty()); + } + { + List remoteIndexExpressions = RemoteClusterAware.getRemoteIndexExpressions( + "index-1", + "remote:index-1", + "idx-*", + "remote-2:idx-5" + ); + assertThat(remoteIndexExpressions, hasSize(2)); + assertThat(remoteIndexExpressions, contains("remote:index-1", "remote-2:idx-5")); + } + } + private static class RemoteClusterAwareTest extends RemoteClusterAware { RemoteClusterAwareTest() { super(Settings.EMPTY);