Skip to content

Commit ecee471

Browse files
authored
Merge branch 'main' into ccs-many-it
2 parents e4ce906 + e8749d9 commit ecee471

File tree

11 files changed

+300
-108
lines changed

11 files changed

+300
-108
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public List<RestHandler> getRestHandlers(
182182
Predicate<NodeFeature> clusterSupportsFeature
183183
) {
184184
List<RestHandler> handlers = new ArrayList<>();
185-
handlers.add(new PainlessExecuteAction.RestAction());
185+
handlers.add(new PainlessExecuteAction.RestAction(settings));
186186
handlers.add(new PainlessContextAction.RestAction());
187187
return handlers;
188188
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.elasticsearch.common.io.stream.StreamOutput;
4848
import org.elasticsearch.common.io.stream.Writeable;
4949
import org.elasticsearch.common.network.NetworkAddress;
50+
import org.elasticsearch.common.settings.Settings;
5051
import org.elasticsearch.common.util.concurrent.EsExecutors;
5152
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
5253
import org.elasticsearch.common.xcontent.XContentHelper;
@@ -94,6 +95,7 @@
9495
import org.elasticsearch.script.ScriptService;
9596
import org.elasticsearch.script.ScriptType;
9697
import org.elasticsearch.script.StringFieldScript;
98+
import org.elasticsearch.search.crossproject.CrossProjectModeDecider;
9799
import org.elasticsearch.search.lookup.SearchLookup;
98100
import org.elasticsearch.tasks.Task;
99101
import org.elasticsearch.threadpool.ThreadPool;
@@ -351,6 +353,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
351353
private final Script script;
352354
private final ScriptContext<?> context;
353355
private final ContextSetup contextSetup;
356+
private transient IndicesOptions indicesOptions;
354357

355358
static Request parse(XContentParser parser) throws IOException {
356359
return PARSER.parse(parser, null);
@@ -390,6 +393,14 @@ public ContextSetup getContextSetup() {
390393
return contextSetup;
391394
}
392395

396+
@Override
397+
public void markOriginOnly() {
398+
assert contextSetup != null
399+
: "Painless/execute request without context setup can't have index, this method shouldn't be called";
400+
// strip off cluster alias from the index in this request
401+
index(contextSetup.getIndex());
402+
}
403+
393404
@Override
394405
public ActionRequestValidationException validate() {
395406
ActionRequestValidationException validationException = null;
@@ -407,6 +418,20 @@ public ActionRequestValidationException validate() {
407418
return validationException;
408419
}
409420

421+
public void enableCrossProjectMode() {
422+
this.indicesOptions = IndicesOptions.builder(super.indicesOptions())
423+
.crossProjectModeOptions(new IndicesOptions.CrossProjectModeOptions(true))
424+
.build();
425+
}
426+
427+
@Override
428+
public IndicesOptions indicesOptions() {
429+
if (indicesOptions == null) {
430+
return super.indicesOptions();
431+
}
432+
return indicesOptions;
433+
}
434+
410435
@Override
411436
public void writeTo(StreamOutput out) throws IOException {
412437
super.writeTo(out);
@@ -532,7 +557,11 @@ public TransportAction(
532557

533558
@Override
534559
protected void doExecute(Task task, Request request, ActionListener<Response> listener) {
535-
if (request.getContextSetup() == null || request.getContextSetup().getClusterAlias() == null) {
560+
// By this point index resolution has completed, and we should not try to resolve indices for child requests
561+
// to avoid the second attempt of project authorization in CPS
562+
request.indicesOptions = null;
563+
564+
if (isLocalIndex(request)) {
536565
super.doExecute(task, request, listener);
537566
} else {
538567
// forward to remote cluster after stripping off the clusterAlias from the index expression
@@ -547,10 +576,19 @@ protected void doExecute(Task task, Request request, ActionListener<Response> li
547576
}
548577
}
549578

579+
private boolean isLocalIndex(Request request) {
580+
if (request.getContextSetup() == null) {
581+
return true;
582+
}
583+
String index = request.index();
584+
return index == null || RemoteClusterAware.isRemoteIndexName(index) == false;
585+
}
586+
550587
// Visible for testing
551588
static void removeClusterAliasFromIndexExpression(Request request) {
552-
if (request.index() != null) {
553-
String[] split = RemoteClusterAware.splitIndexName(request.index());
589+
String index = request.index();
590+
if (index != null) {
591+
String[] split = RemoteClusterAware.splitIndexName(index);
554592
if (split[0] != null) {
555593
/*
556594
* if the cluster alias is null and the index field has a clusterAlias (clusterAlias:index notation)
@@ -854,6 +892,11 @@ private static Response prepareRamIndex(
854892

855893
@ServerlessScope(Scope.PUBLIC)
856894
public static class RestAction extends BaseRestHandler {
895+
private final CrossProjectModeDecider crossProjectModeDecider;
896+
897+
public RestAction(Settings settings) {
898+
this.crossProjectModeDecider = new CrossProjectModeDecider(settings);
899+
}
857900

858901
@Override
859902
public List<Route> routes() {
@@ -868,6 +911,10 @@ public String getName() {
868911
@Override
869912
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
870913
final Request request = Request.parse(restRequest.contentOrSourceParamParser());
914+
if (crossProjectModeDecider.crossProjectEnabled()) {
915+
request.enableCrossProjectMode();
916+
}
917+
871918
return channel -> client.executeLocally(INSTANCE, request, new RestToXContentListener<>(channel));
872919
}
873920
}

modules/lang-painless/src/test/java/org/elasticsearch/painless/action/PainlessExecuteApiTests.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,12 @@ public void testRemoveClusterAliasFromIndexExpression() {
561561
}
562562
}
563563

564+
public void testMarkOriginOnly() {
565+
PainlessExecuteAction.Request request = createRequest("p1:blogs");
566+
request.markOriginOnly();
567+
assertThat(request.index(), equalTo("blogs"));
568+
}
569+
564570
private PainlessExecuteAction.Request createRequest(String indexExpression) {
565571
return new PainlessExecuteAction.Request(
566572
new Script("100.0 / 1000.0"),

muted-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,12 @@ tests:
509509
- class: org.elasticsearch.test.rest.yaml.CcsCommonYamlTestSuiteIT
510510
method: test {p0=eql/10_basic/Sequence checking correct join key ordering.}
511511
issue: https://github.com/elastic/elasticsearch/issues/139173
512+
- class: org.elasticsearch.test.rest.yaml.RcsCcsCommonYamlTestSuiteIT
513+
method: test {p0=search.retrievers/result-diversification/10_mmr_result_diversification_retriever/Test MMR result diversification single index float type}
514+
issue: https://github.com/elastic/elasticsearch/issues/139195
515+
- class: org.elasticsearch.smoketest.MlWithSecurityIT
516+
method: test {yaml=ml/sparse_vector_search/Test sparse_vector search with query vector and pruning config}
517+
issue: https://github.com/elastic/elasticsearch/issues/139196
512518

513519
# Examples:
514520
#

server/src/main/java/org/elasticsearch/action/IndicesRequest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,20 @@ interface SingleIndexNoWildcards extends IndicesRequest, CrossProjectCandidate {
118118
default boolean allowsRemoteIndices() {
119119
return true;
120120
}
121+
122+
/**
123+
* Determines whether the request type allows cross-project processing. Cross-project processing entails cross-project search
124+
* index resolution and error handling. Note: this method only determines in the request _supports_ cross-project.
125+
* Whether cross-project processing is actually performed is determined by {@link IndicesOptions}.
126+
*/
127+
default boolean allowsCrossProject() {
128+
return true;
129+
}
130+
131+
/**
132+
* Marks request local. Local requests should be processed on the same cluster, even if they have cluster-alias prefix.
133+
*/
134+
void markOriginOnly();
121135
}
122136

123137
/**

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ public class InternalUsers {
180180
// System data stream for result history of fleet actions (see Fleet#fleetActionsResultsDescriptor)
181181
".fleet-actions-results",
182182
// System data streams for storing uploaded file data for Agent diagnostics and Endpoint response actions
183-
".fleet-fileds*"
183+
".fleet-fileds*",
184+
// System data stream for kibana workflows logs
185+
".workflows-execution-data-stream-logs"
184186
)
185187
.privileges(
186188
filterNonNull(

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/user/InternalUsersTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@ public void testDataStreamLifecycleUser() {
253253
assertThat(role.application(), is(ApplicationPermission.NONE));
254254
assertThat(role.remoteIndices(), is(RemoteIndicesPermission.NONE));
255255

256-
final List<String> allowedSystemDataStreams = Arrays.asList(".fleet-actions-results", ".fleet-fileds*");
256+
final List<String> allowedSystemDataStreams = Arrays.asList(
257+
".fleet-actions-results",
258+
".fleet-fileds*",
259+
".workflows-execution-data-stream-logs"
260+
);
257261
for (var group : role.indices().groups()) {
258262
if (group.allowRestrictedIndices()) {
259263
assertThat(group.indices(), arrayContaining(allowedSystemDataStreams.toArray(new String[0])));

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/EsqlRestValidationTestCase.java

Lines changed: 39 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
package org.elasticsearch.xpack.esql.qa.rest;
99

1010
import org.elasticsearch.client.Request;
11-
import org.elasticsearch.client.RequestOptions;
12-
import org.elasticsearch.client.Response;
1311
import org.elasticsearch.client.ResponseException;
1412
import org.elasticsearch.client.RestClient;
1513
import org.elasticsearch.client.WarningsHandler;
@@ -20,6 +18,7 @@
2018
import org.junit.Before;
2119

2220
import java.io.IOException;
21+
import java.util.List;
2322

2423
import static org.hamcrest.Matchers.containsString;
2524
import static org.hamcrest.Matchers.equalTo;
@@ -28,24 +27,6 @@ public abstract class EsqlRestValidationTestCase extends ESRestTestCase {
2827

2928
private static final String indexName = "test_esql";
3029
private static final String aliasName = "alias-test_esql";
31-
protected static final String[] existentIndexWithWildcard = new String[] {
32-
indexName + ",inexistent*",
33-
indexName + "*,inexistent*",
34-
"inexistent*," + indexName };
35-
private static final String[] existentIndexWithoutWildcard = new String[] { indexName + ",inexistent", "inexistent," + indexName };
36-
protected static final String[] existentAliasWithWildcard = new String[] {
37-
aliasName + ",inexistent*",
38-
aliasName + "*,inexistent*",
39-
"inexistent*," + aliasName };
40-
private static final String[] existentAliasWithoutWildcard = new String[] { aliasName + ",inexistent", "inexistent," + aliasName };
41-
private static final String[] inexistentIndexNameWithWildcard = new String[] { "inexistent*", "inexistent1*,inexistent2*" };
42-
private static final String[] inexistentIndexNameWithoutWildcard = new String[] { "inexistent", "inexistent1,inexistent2" };
43-
private static final String createAlias = "{\"actions\":[{\"add\":{\"index\":\"" + indexName + "\",\"alias\":\"" + aliasName + "\"}}]}";
44-
private static final String removeAlias = "{\"actions\":[{\"remove\":{\"index\":\""
45-
+ indexName
46-
+ "\",\"alias\":\""
47-
+ aliasName
48-
+ "\"}}]}";
4930

5031
@Before
5132
@After
@@ -73,79 +54,76 @@ public void wipeTestData() throws IOException {
7354
}
7455
}
7556

76-
private String getInexistentIndexErrorMessage() {
77-
return "\"reason\" : \"Found 1 problem\\nline 1:1: Unknown index ";
78-
}
79-
80-
public void testInexistentIndexNameWithWildcard() throws IOException {
81-
assertErrorMessages(inexistentIndexNameWithWildcard, getInexistentIndexErrorMessage(), 400);
57+
public void testInexistentIndexNameWithWildcard() {
58+
for (String pattern : List.of("inexistent*", "inexistent1*,inexistent2*")) {
59+
assertError(pattern, 400, "Found 1 problem\\nline 1:1: Unknown index [" + clusterSpecificIndexName(pattern) + "]");
60+
}
8261
}
8362

84-
public void testInexistentIndexNameWithoutWildcard() throws IOException {
85-
assertErrorMessages(inexistentIndexNameWithoutWildcard, getInexistentIndexErrorMessage(), 400);
63+
public void testInexistentIndexNameWithoutWildcard() {
64+
for (String pattern : List.of("inexistent", "inexistent1,inexistent2")) {
65+
assertError(pattern, "Found 1 problem\\nline 1:1: Unknown index [" + clusterSpecificIndexName(pattern) + "]", 400);
66+
}
8667
}
8768

8869
public void testExistentIndexWithoutWildcard() throws IOException {
89-
for (String indexName : existentIndexWithoutWildcard) {
90-
assertErrorMessage(indexName, "\"reason\" : \"no such index [inexistent]\"", 404);
70+
for (String pattern : List.of(indexName + ",inexistent", "inexistent," + indexName)) {
71+
assertError(pattern, 404, "no such index [inexistent]");
9172
}
9273
}
9374

9475
public void testExistentIndexWithWildcard() throws IOException {
95-
assertValidRequestOnIndices(existentIndexWithWildcard);
76+
for (String pattern : List.of(indexName + ",inexistent*", indexName + "*,inexistent*", "inexistent*," + indexName)) {
77+
assertOK(client().performRequest(createRequest(pattern)));
78+
}
9679
}
9780

9881
public void testAlias() throws IOException {
99-
createAlias();
82+
updateAliases("""
83+
{"actions":[{"add":{"index":"%s","alias":"%s"}}]}
84+
""".formatted(indexName, aliasName));
10085

101-
for (String indexName : existentAliasWithoutWildcard) {
102-
assertErrorMessage(indexName, "\"reason\" : \"no such index [inexistent]\"", 404);
86+
for (String indexName : List.of(aliasName + ",inexistent", "inexistent," + aliasName)) {
87+
assertError(indexName, "no such index [inexistent]", 404);
10388
}
104-
assertValidRequestOnIndices(existentAliasWithWildcard);
105-
106-
deleteAlias();
107-
}
108-
109-
private void assertErrorMessages(String[] indices, String errorMessage, int statusCode) throws IOException {
110-
for (String indexName : indices) {
111-
assertErrorMessage(indexName, errorMessage + "[" + clusterSpecificIndexName(indexName) + "]", statusCode);
89+
for (String indexName : List.of(aliasName + ",inexistent*", aliasName + "*,inexistent*", "inexistent*," + aliasName)) {
90+
assertOK(client().performRequest(createRequest(indexName)));
11291
}
92+
93+
updateAliases("""
94+
{"actions":[{"remove":{"index":"%s","alias":"%s"}}]}
95+
""".formatted(indexName, aliasName));
11396
}
11497

11598
protected String clusterSpecificIndexName(String indexName) {
11699
return indexName;
117100
}
118101

119-
private void assertErrorMessage(String indexName, String errorMessage, int statusCode) throws IOException {
120-
var specificName = clusterSpecificIndexName(indexName);
121-
final var request = createRequest(specificName);
122-
ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(request));
102+
private void assertError(String indexName, String errorMessage, int statusCode) {
103+
ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(createRequest(indexName)));
104+
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(statusCode));
105+
assertThat(exc.getMessage(), containsString("\"reason\" : \"" + errorMessage + "\""));
106+
}
123107

108+
private void assertError(String indexName, int statusCode, String errorMessage) {
109+
ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(createRequest(indexName)));
124110
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(statusCode));
125-
assertThat(exc.getMessage(), containsString(errorMessage));
111+
assertThat(exc.getMessage(), containsString("\"reason\" : \"" + errorMessage + "\""));
126112
}
127113

128114
private Request createRequest(String indexName) throws IOException {
129115
final var request = new Request("POST", "/_query");
130116
request.addParameter("error_trace", "true");
131117
request.addParameter("pretty", "true");
132118
request.setJsonEntity(
133-
Strings.toString(JsonXContent.contentBuilder().startObject().field("query", "from " + indexName).endObject())
119+
Strings.toString(
120+
JsonXContent.contentBuilder().startObject().field("query", "from " + clusterSpecificIndexName(indexName)).endObject()
121+
)
134122
);
135-
RequestOptions.Builder options = request.getOptions().toBuilder();
136-
options.setWarningsHandler(WarningsHandler.PERMISSIVE);
137-
request.setOptions(options);
123+
request.setOptions(request.getOptions().toBuilder().setWarningsHandler(WarningsHandler.PERMISSIVE));
138124
return request;
139125
}
140126

141-
private void assertValidRequestOnIndices(String[] indices) throws IOException {
142-
for (String indexName : indices) {
143-
final var request = createRequest(clusterSpecificIndexName(indexName));
144-
Response response = client().performRequest(request);
145-
assertOK(response);
146-
}
147-
}
148-
149127
// Returned client is used to load the test data, either in the local cluster or a remote one (for
150128
// multi-clusters). The client()/adminClient() will always connect to the local cluster
151129
protected RestClient provisioningClient() throws IOException {
@@ -156,15 +134,9 @@ protected RestClient provisioningAdminClient() throws IOException {
156134
return adminClient();
157135
}
158136

159-
private void createAlias() throws IOException {
137+
private void updateAliases(String update) throws IOException {
160138
var r = new Request("POST", "_aliases");
161-
r.setJsonEntity(createAlias);
139+
r.setJsonEntity(update);
162140
assertOK(provisioningClient().performRequest(r));
163141
}
164-
165-
private void deleteAlias() throws IOException {
166-
var r = new Request("POST", "/_aliases/");
167-
r.setJsonEntity(removeAlias);
168-
assertOK(provisioningAdminClient().performRequest(r));
169-
}
170142
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ private AsyncSupplier<ResolvedIndices> makeResolvedIndicesAsyncSupplier(
573573
var resolvedIndices = indicesAndAliasesResolver.resolvePITIndices(searchRequest);
574574
return SubscribableListener.newSucceeded(resolvedIndices);
575575
}
576-
final ResolvedIndices resolvedIndices = indicesAndAliasesResolver.tryResolveWithoutWildcards(action, request);
576+
final ResolvedIndices resolvedIndices = indicesAndAliasesResolver.tryResolveWithoutWildcards(action, request, targetProjects);
577577
if (resolvedIndices != null) {
578578
return SubscribableListener.newSucceeded(resolvedIndices);
579579
} else {

0 commit comments

Comments
 (0)