From 980723ef83556263ff2fe00fab0ae0d6af24e94a Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Tue, 24 Jun 2025 22:49:43 +0200 Subject: [PATCH 1/4] poc --- .../org/elasticsearch/FlatIndicesRequest.java | 18 +++ .../action/search/SearchRequest.java | 12 +- .../security/authz/AuthorizationEngine.java | 5 + .../core/security/SerializationDemoTests.java | 136 ++++++++++++++++++ .../authz/IndicesAndAliasesResolver.java | 53 ++++++- .../xpack/security/authz/RBACEngine.java | 14 +- 6 files changed, 235 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/FlatIndicesRequest.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java diff --git a/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java new file mode 100644 index 0000000000000..8e9785d3f82fa --- /dev/null +++ b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java @@ -0,0 +1,18 @@ +/* + * 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; + +import org.elasticsearch.action.IndicesRequest; + +import java.util.List; + +public interface FlatIndicesRequest extends IndicesRequest { + void indices(List indices); +} diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java index fda2df81d3f94..60b01bda53b28 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.search; +import org.elasticsearch.FlatIndicesRequest; import org.elasticsearch.TransportVersions; import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequestValidationException; @@ -53,7 +54,11 @@ * @see Client#search(SearchRequest) * @see SearchResponse */ -public class SearchRequest extends LegacyActionRequest implements IndicesRequest.Replaceable, Rewriteable { +public class SearchRequest extends LegacyActionRequest + implements + FlatIndicesRequest, + IndicesRequest.Replaceable, + Rewriteable { public static final ToXContent.Params FORMAT_PARAMS = new ToXContent.MapParams(Collections.singletonMap("pretty", "false")); @@ -853,4 +858,9 @@ public String toString() { + source + '}'; } + + @Override + public void indices(List indices) { + indices(indices.toArray(Strings.EMPTY_ARRAY)); + } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java index 2c831645d0e69..e2ffcf7480381 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java @@ -299,6 +299,11 @@ interface AuthorizedIndices { * Checks if an index-like resource name is authorized, for an action by a user. The resource might or might not exist. */ boolean check(String name, IndexComponentSelector selector); + + // Does not belong here + default boolean checkProject(String projectId) { + return false; + } } /** diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java new file mode 100644 index 0000000000000..caabd1d1ee0da --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java @@ -0,0 +1,136 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.core.security; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.NamedXContentRegistry; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentType; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.TransportVersions.PARTIAL_DATA_DEMO; +import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; + +public class SerializationDemoTests extends ESTestCase { + + record SearchResult(boolean success, @Nullable List results, @Nullable List failures) + implements + Writeable, + // ToXContentFragment also exists + ToXContentObject { + + private static final ConstructingObjectParser PARSER = buildParser(); + + @SuppressWarnings("unchecked") + private static ConstructingObjectParser buildParser() { + final ConstructingObjectParser parser = new ConstructingObjectParser<>( + "search_result", + true, + a -> new SearchResult((boolean) a[0], (List) a[1], (List) a[2]) + ); + parser.declareBoolean(constructorArg(), new ParseField("success")); + parser.declareStringArray(optionalConstructorArg(), new ParseField("results")); + parser.declareStringArray(optionalConstructorArg(), new ParseField("failures")); + return parser; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeBoolean(success); + out.writeOptionalCollection(results, StreamOutput::writeString); + // Elasticsearch supports rolling upgrades across 1 major version and within major versions. + // For example 7.17 needs to be able to communicate with 8.4 nodes, and 8.1 nodes need to be able to talk with 8.4 nodes. + // Serverless removed the notion of transport versions being tied cleanly to ES versions since we release to serverless + // every week and have rolling upgrades + if (out.getTransportVersion().onOrAfter(PARTIAL_DATA_DEMO)) { + out.writeOptionalCollection(failures, StreamOutput::writeString); + } + } + + SearchResult(StreamInput input) throws IOException { + this( + input.readBoolean(), + input.readOptionalCollectionAsList(StreamInput::readString), + input.getTransportVersion().onOrAfter(PARTIAL_DATA_DEMO) + ? input.readOptionalCollectionAsList(StreamInput::readString) + : List.of() + ); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + var xcb = builder.startObject().field("success", success); + if (results != null) { + xcb = xcb.field("results", results); + } + if (failures != null) { + xcb = xcb.field("failures", failures); + } + return xcb.endObject(); + } + + public SearchResult fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + } + + public void testRoundTripTransportSerialization() throws IOException { + var result = new SearchResult(true, List.of("hit1"), List.of()); + + try (var out = new BytesStreamOutput()) { + result.writeTo(out); + var received = new SearchResult(out.bytes().streamInput()); + + System.out.println("Original: " + result); + System.out.println("Received: " + received); + } + } + + public void testToXContent() { + var result = new SearchResult(true, List.of("hit1", "hit2"), List.of("failure1")); + + try (var builder = XContentBuilder.builder(XContentType.JSON.xContent())) { + result.toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.flush(); + String json = Strings.toString(builder); + System.out.println("JSON Output: " + json); + // test from XContent + try ( + var parser = XContentType.JSON.xContent() + .createParser( + NamedXContentRegistry.EMPTY, + LoggingDeprecationHandler.INSTANCE, + new ByteArrayInputStream(json.getBytes()) + ) + ) { + var parsedResult = result.fromXContent(parser); + System.out.println("Parsed Result: " + parsedResult); + } + } catch (IOException e) { + fail("Failed to convert to XContent: " + e.getMessage()); + } + } + +} 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 ff39fd587dc3a..f3573d8fb6518 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 @@ -6,6 +6,9 @@ */ package org.elasticsearch.xpack.security.authz; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.FlatIndicesRequest; import org.elasticsearch.action.AliasesRequest; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -55,6 +58,8 @@ class IndicesAndAliasesResolver { + private static final Logger logger = LogManager.getLogger(IndicesAndAliasesResolver.class); + private final IndexNameExpressionResolver nameExpressionResolver; private final IndexAbstractionResolver indexAbstractionResolver; private final RemoteClusterResolver remoteClusterResolver; @@ -103,7 +108,6 @@ class IndicesAndAliasesResolver { * resolving wildcards. *

*/ - ResolvedIndices resolve( String action, TransportRequest request, @@ -124,9 +128,52 @@ ResolvedIndices resolve( if (request instanceof IndicesRequest == false) { throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be."); } + + if (request instanceof FlatIndicesRequest flatIndicesRequest) { + rewrite(flatIndicesRequest, authorizedIndices); + } + return resolveIndicesAndAliases(action, (IndicesRequest) request, projectMetadata, authorizedIndices); } + void rewrite(FlatIndicesRequest request, AuthorizationEngine.AuthorizedIndices authorizedIndices) { + var clusters = remoteClusterResolver.clusters(); + logger.info("Clusters available for remote indices: {}", clusters); + // no remotes, nothing to rewrite... + if (clusters.isEmpty()) { + logger.info("Skipping..."); + return; + } + + var indices = request.indices(); + // empty indices actually means search everything so would need to also rewrite that + + var authorizedClusters = new HashSet(); + for (var cluster : clusters) { + if (authorizedIndices.checkProject(cluster)) { + logger.info("Remote cluster [{}] authorized", cluster); + authorizedClusters.add(cluster); + } + } + + // TODO do not rewrite twice + List rewrittenIndices = new ArrayList<>(indices.length); + ResolvedIndices resolved = remoteClusterResolver.splitLocalAndRemoteIndexNames(indices); + for (var local : resolved.getLocal()) { + String rewritten = local; + for (var cluster : authorizedClusters) { + rewritten += "," + RemoteClusterAware.buildRemoteIndexName(cluster, local); + rewrittenIndices.add(rewritten); + } + logger.info("Rewrote [{}] to [{}]", local, rewritten); + } + if (resolved.getRemote().isEmpty() == false) { + rewrittenIndices.addAll(resolved.getRemote()); + } + request.indices(rewrittenIndices); + // skipping mixed expressions, _local expressions and all that jazz + } + /** * Attempt to resolve requested indices without expanding any wildcards. * @return The {@link ResolvedIndices} or null if wildcard expansion must be performed. @@ -569,5 +616,9 @@ ResolvedIndices splitLocalAndRemoteIndexNames(String... indices) { .toList(); return new ResolvedIndices(local == null ? List.of() : local, remote); } + + Set clusters() { + return Collections.unmodifiableSet(clusters); + } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index 1b99bd6888c4f..2ef0319e4729a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -998,6 +998,9 @@ static AuthorizedIndices resolveAuthorizedIndicesFromRole( } // we don't support granting access to a backing index with a failure selector via the parent data stream } return predicate.test(indexAbstraction, selector); + }, name -> { + // just some bogus predicate that lets us differentiate between roles + return Arrays.asList(role.names()).contains("remote_searcher"); }); } @@ -1125,15 +1128,18 @@ static final class AuthorizedIndices implements AuthorizationEngine.AuthorizedIn private final CachedSupplier> authorizedAndAvailableDataResources; private final CachedSupplier> authorizedAndAvailableFailuresResources; private final BiPredicate isAuthorizedPredicate; + private final Predicate projectPredicate; AuthorizedIndices( Supplier> authorizedAndAvailableDataResources, Supplier> authorizedAndAvailableFailuresResources, - BiPredicate isAuthorizedPredicate + BiPredicate isAuthorizedPredicate, + Predicate projectPredicate ) { this.authorizedAndAvailableDataResources = CachedSupplier.wrap(authorizedAndAvailableDataResources); this.authorizedAndAvailableFailuresResources = CachedSupplier.wrap(authorizedAndAvailableFailuresResources); this.isAuthorizedPredicate = Objects.requireNonNull(isAuthorizedPredicate); + this.projectPredicate = projectPredicate; } @Override @@ -1149,5 +1155,11 @@ public boolean check(String name, IndexComponentSelector selector) { Objects.requireNonNull(selector, "must specify a selector for authorization check"); return isAuthorizedPredicate.test(name, selector); } + + @Override + public boolean checkProject(String name) { + Objects.requireNonNull(name, "must specify a project name for authorization check"); + return projectPredicate.test(name); + } } } From 4ff5e6eb8cbacedda35dc0a916a604624b823437 Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Tue, 24 Jun 2025 22:50:15 +0200 Subject: [PATCH 2/4] poc --- .../core/security/SerializationDemoTests.java | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java deleted file mode 100644 index caabd1d1ee0da..0000000000000 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/SerializationDemoTests.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -package org.elasticsearch.xpack.core.security; - -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.io.stream.BytesStreamOutput; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; -import org.elasticsearch.core.Nullable; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xcontent.ConstructingObjectParser; -import org.elasticsearch.xcontent.NamedXContentRegistry; -import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.ToXContent; -import org.elasticsearch.xcontent.ToXContentObject; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.XContentParser; -import org.elasticsearch.xcontent.XContentType; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.List; - -import static org.elasticsearch.TransportVersions.PARTIAL_DATA_DEMO; -import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; -import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; - -public class SerializationDemoTests extends ESTestCase { - - record SearchResult(boolean success, @Nullable List results, @Nullable List failures) - implements - Writeable, - // ToXContentFragment also exists - ToXContentObject { - - private static final ConstructingObjectParser PARSER = buildParser(); - - @SuppressWarnings("unchecked") - private static ConstructingObjectParser buildParser() { - final ConstructingObjectParser parser = new ConstructingObjectParser<>( - "search_result", - true, - a -> new SearchResult((boolean) a[0], (List) a[1], (List) a[2]) - ); - parser.declareBoolean(constructorArg(), new ParseField("success")); - parser.declareStringArray(optionalConstructorArg(), new ParseField("results")); - parser.declareStringArray(optionalConstructorArg(), new ParseField("failures")); - return parser; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeBoolean(success); - out.writeOptionalCollection(results, StreamOutput::writeString); - // Elasticsearch supports rolling upgrades across 1 major version and within major versions. - // For example 7.17 needs to be able to communicate with 8.4 nodes, and 8.1 nodes need to be able to talk with 8.4 nodes. - // Serverless removed the notion of transport versions being tied cleanly to ES versions since we release to serverless - // every week and have rolling upgrades - if (out.getTransportVersion().onOrAfter(PARTIAL_DATA_DEMO)) { - out.writeOptionalCollection(failures, StreamOutput::writeString); - } - } - - SearchResult(StreamInput input) throws IOException { - this( - input.readBoolean(), - input.readOptionalCollectionAsList(StreamInput::readString), - input.getTransportVersion().onOrAfter(PARTIAL_DATA_DEMO) - ? input.readOptionalCollectionAsList(StreamInput::readString) - : List.of() - ); - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - var xcb = builder.startObject().field("success", success); - if (results != null) { - xcb = xcb.field("results", results); - } - if (failures != null) { - xcb = xcb.field("failures", failures); - } - return xcb.endObject(); - } - - public SearchResult fromXContent(XContentParser parser) { - return PARSER.apply(parser, null); - } - - } - - public void testRoundTripTransportSerialization() throws IOException { - var result = new SearchResult(true, List.of("hit1"), List.of()); - - try (var out = new BytesStreamOutput()) { - result.writeTo(out); - var received = new SearchResult(out.bytes().streamInput()); - - System.out.println("Original: " + result); - System.out.println("Received: " + received); - } - } - - public void testToXContent() { - var result = new SearchResult(true, List.of("hit1", "hit2"), List.of("failure1")); - - try (var builder = XContentBuilder.builder(XContentType.JSON.xContent())) { - result.toXContent(builder, ToXContent.EMPTY_PARAMS); - builder.flush(); - String json = Strings.toString(builder); - System.out.println("JSON Output: " + json); - // test from XContent - try ( - var parser = XContentType.JSON.xContent() - .createParser( - NamedXContentRegistry.EMPTY, - LoggingDeprecationHandler.INSTANCE, - new ByteArrayInputStream(json.getBytes()) - ) - ) { - var parsedResult = result.fromXContent(parser); - System.out.println("Parsed Result: " + parsedResult); - } - } catch (IOException e) { - fail("Failed to convert to XContent: " + e.getMessage()); - } - } - -} From 3df7a1df89671fa0df007c889e5dfbba9922b547 Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Thu, 26 Jun 2025 13:45:37 +0200 Subject: [PATCH 3/4] Fix ups --- .../java/org/elasticsearch/FlatIndicesRequest.java | 6 +++++- .../elasticsearch/action/search/SearchRequest.java | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java index 8e9785d3f82fa..2d4594c05c6f1 100644 --- a/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java +++ b/server/src/main/java/org/elasticsearch/FlatIndicesRequest.java @@ -14,5 +14,9 @@ import java.util.List; public interface FlatIndicesRequest extends IndicesRequest { - void indices(List indices); + boolean requiresRewrite(); + + void indexExpressions(List indexExpressions); + + record IndexExpression(String original, List rewritten) {} } diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java index 60b01bda53b28..cd5c0ad466bc7 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchRequest.java @@ -75,6 +75,9 @@ public class SearchRequest extends LegacyActionRequest private String[] indices = Strings.EMPTY_ARRAY; + @Nullable + private List indexExpressions; + @Nullable private String routing; @Nullable @@ -860,7 +863,14 @@ public String toString() { } @Override - public void indices(List indices) { - indices(indices.toArray(Strings.EMPTY_ARRAY)); + public boolean requiresRewrite() { + return indexExpressions == null; + } + + @Override + public void indexExpressions(List indexExpressions) { + assert requiresRewrite(); + this.indexExpressions = indexExpressions; + indices(indexExpressions.stream().flatMap(indexExpression -> indexExpression.rewritten().stream()).toArray(String[]::new)); } } From c21a0a9d89845009cd722ee04e00be3a77956c29 Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Thu, 26 Jun 2025 13:57:33 +0200 Subject: [PATCH 4/4] The missing commit --- .../authz/IndicesAndAliasesResolver.java | 34 ++++++++++++------- .../xpack/security/authz/RBACEngine.java | 5 +-- 2 files changed, 25 insertions(+), 14 deletions(-) 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 f3573d8fb6518..e895c8be3ce69 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 @@ -129,7 +129,7 @@ ResolvedIndices resolve( throw new IllegalStateException("Request [" + request + "] is not an Indices request, but should be."); } - if (request instanceof FlatIndicesRequest flatIndicesRequest) { + if (request instanceof FlatIndicesRequest flatIndicesRequest && flatIndicesRequest.requiresRewrite()) { rewrite(flatIndicesRequest, authorizedIndices); } @@ -137,11 +137,13 @@ ResolvedIndices resolve( } void rewrite(FlatIndicesRequest request, AuthorizationEngine.AuthorizedIndices authorizedIndices) { + assert request.requiresRewrite(); + var clusters = remoteClusterResolver.clusters(); logger.info("Clusters available for remote indices: {}", clusters); // no remotes, nothing to rewrite... if (clusters.isEmpty()) { - logger.info("Skipping..."); + logger.info("No remote clusters linked, skipping..."); return; } @@ -156,22 +158,30 @@ void rewrite(FlatIndicesRequest request, AuthorizationEngine.AuthorizedIndices a } } - // TODO do not rewrite twice - List rewrittenIndices = new ArrayList<>(indices.length); + if (authorizedClusters.isEmpty()) { + logger.info("No remote clusters authorized, skipping..."); + return; + } + ResolvedIndices resolved = remoteClusterResolver.splitLocalAndRemoteIndexNames(indices); + // skip handling searches where there's both qualified and flat expressions to simplify POC + // in the real thing, we'd also rewrite these + if (resolved.getRemote().isEmpty() == false) { + return; + } + + List indexExpressions = new ArrayList<>(indices.length); for (var local : resolved.getLocal()) { - String rewritten = local; + List rewritten = new ArrayList<>(); + rewritten.add(local); for (var cluster : authorizedClusters) { - rewritten += "," + RemoteClusterAware.buildRemoteIndexName(cluster, local); - rewrittenIndices.add(rewritten); + rewritten.add(RemoteClusterAware.buildRemoteIndexName(cluster, local)); + indexExpressions.add(new FlatIndicesRequest.IndexExpression(local, rewritten)); } logger.info("Rewrote [{}] to [{}]", local, rewritten); } - if (resolved.getRemote().isEmpty() == false) { - rewrittenIndices.addAll(resolved.getRemote()); - } - request.indices(rewrittenIndices); - // skipping mixed expressions, _local expressions and all that jazz + + request.indexExpressions(indexExpressions); } /** diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java index 2ef0319e4729a..9c6b8c26ae312 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java @@ -999,8 +999,9 @@ static AuthorizedIndices resolveAuthorizedIndicesFromRole( } return predicate.test(indexAbstraction, selector); }, name -> { - // just some bogus predicate that lets us differentiate between roles - return Arrays.asList(role.names()).contains("remote_searcher"); + // just some bogus predicate that lets us differentiate between roles, not at all + // how this will work in the end + return Arrays.asList(role.names()).contains("_es_test_root"); }); }