Skip to content

Commit ab8b933

Browse files
committed
POC: plug ES|QL project routing into index resolution
1 parent 61575f0 commit ab8b933

File tree

14 files changed

+236
-11
lines changed

14 files changed

+236
-11
lines changed

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesRequest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.elasticsearch.common.Strings;
1919
import org.elasticsearch.common.io.stream.StreamInput;
2020
import org.elasticsearch.common.io.stream.StreamOutput;
21+
import org.elasticsearch.core.Nullable;
2122
import org.elasticsearch.index.query.BoolQueryBuilder;
2223
import org.elasticsearch.index.query.BoostingQueryBuilder;
2324
import org.elasticsearch.index.query.ConstantScoreQueryBuilder;
@@ -46,6 +47,9 @@ public final class FieldCapabilitiesRequest extends LegacyActionRequest implemen
4647

4748
private String clusterAlias = RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY;
4849

50+
@Nullable
51+
private String projectRouting;
52+
4953
private String[] indices = Strings.EMPTY_ARRAY;
5054
private IndicesOptions indicesOptions = DEFAULT_INDICES_OPTIONS;
5155
private String[] fields = Strings.EMPTY_ARRAY;
@@ -113,6 +117,15 @@ String clusterAlias() {
113117
return clusterAlias;
114118
}
115119

120+
@Nullable
121+
public String projectRouting() {
122+
return projectRouting;
123+
}
124+
125+
public void projectRouting(String projectRouting) {
126+
this.projectRouting = projectRouting;
127+
}
128+
116129
@Override
117130
public void writeTo(StreamOutput out) throws IOException {
118131
super.writeTo(out);

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/esql/EsqlAsyncActionNames.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
public class EsqlAsyncActionNames {
1414
public static final String ESQL_ASYNC_GET_RESULT_ACTION_NAME = "indices:data/read/esql/async/get";
1515
public static final String ESQL_ASYNC_STOP_ACTION_NAME = "indices:data/read/esql/async/stop";
16+
1617
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.action;
9+
10+
import org.elasticsearch.action.ActionType;
11+
12+
public class EsqlProjectRoutingAction extends ActionType<EsqlProjectRoutingResponse> {
13+
public static final String NAME = "cluster:monitor/xpack/esql/project_routing";
14+
public static final EsqlProjectRoutingAction INSTANCE = new EsqlProjectRoutingAction(NAME);
15+
16+
public EsqlProjectRoutingAction(String name) {
17+
super(NAME);
18+
}
19+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.action;
9+
10+
import org.elasticsearch.action.ActionRequest;
11+
import org.elasticsearch.action.ActionRequestValidationException;
12+
import org.elasticsearch.action.support.TransportAction;
13+
import org.elasticsearch.common.io.stream.StreamOutput;
14+
15+
import java.io.IOException;
16+
import java.util.List;
17+
18+
public class EsqlProjectRoutingRequest extends ActionRequest {
19+
public EsqlProjectRoutingRequest(List<String> projects, String projectRoutingQuery) {
20+
21+
}
22+
23+
@Override
24+
public ActionRequestValidationException validate() {
25+
return null;
26+
}
27+
28+
@Override
29+
public void writeTo(StreamOutput out) throws IOException {
30+
TransportAction.localOnly();
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.action;
9+
10+
import org.elasticsearch.action.ActionResponse;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
13+
import java.io.IOException;
14+
import java.util.List;
15+
16+
public class EsqlProjectRoutingResponse extends ActionResponse {
17+
18+
private final List<String> projects;
19+
20+
public EsqlProjectRoutingResponse(List<String> projects) {
21+
this.projects = projects;
22+
}
23+
24+
@Override
25+
public void writeTo(StreamOutput out) throws IOException {
26+
out.writeGenericList(projects, StreamOutput::writeString);
27+
}
28+
29+
public List<String> getProjects() {
30+
return projects;
31+
}
32+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.action;
9+
10+
import org.elasticsearch.action.ActionListener;
11+
import org.elasticsearch.action.support.ActionFilters;
12+
import org.elasticsearch.action.support.TransportAction;
13+
import org.elasticsearch.cluster.service.ClusterService;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.common.util.concurrent.EsExecutors;
16+
import org.elasticsearch.injection.guice.Inject;
17+
import org.elasticsearch.tasks.Task;
18+
import org.elasticsearch.transport.TransportService;
19+
20+
import java.util.List;
21+
22+
public class TransportEsqlProjectRoutingAction extends TransportAction<EsqlProjectRoutingRequest, EsqlProjectRoutingResponse> {
23+
@Inject
24+
public TransportEsqlProjectRoutingAction(
25+
Settings settings,
26+
TransportService transportService,
27+
ActionFilters actionFilters,
28+
ClusterService clusterService
29+
) {
30+
super(EsqlProjectRoutingAction.NAME, actionFilters, transportService.getTaskManager(), EsExecutors.DIRECT_EXECUTOR_SERVICE);
31+
}
32+
33+
@Override
34+
protected void doExecute(Task task, EsqlProjectRoutingRequest request, ActionListener<EsqlProjectRoutingResponse> listener) {
35+
// whatever fancy ES|QL needs to happen here can happen here
36+
listener.onResponse(new EsqlProjectRoutingResponse(List.of("project1", "project2", "project3")));
37+
}
38+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import org.elasticsearch.xpack.esql.action.EsqlAsyncStopAction;
5858
import org.elasticsearch.xpack.esql.action.EsqlGetQueryAction;
5959
import org.elasticsearch.xpack.esql.action.EsqlListQueriesAction;
60+
import org.elasticsearch.xpack.esql.action.EsqlProjectRoutingAction;
6061
import org.elasticsearch.xpack.esql.action.EsqlQueryAction;
6162
import org.elasticsearch.xpack.esql.action.EsqlQueryRequestBuilder;
6263
import org.elasticsearch.xpack.esql.action.EsqlResolveFieldsAction;
@@ -67,6 +68,7 @@
6768
import org.elasticsearch.xpack.esql.action.RestEsqlListQueriesAction;
6869
import org.elasticsearch.xpack.esql.action.RestEsqlQueryAction;
6970
import org.elasticsearch.xpack.esql.action.RestEsqlStopAsyncAction;
71+
import org.elasticsearch.xpack.esql.action.TransportEsqlProjectRoutingAction;
7072
import org.elasticsearch.xpack.esql.analysis.PlanCheckerProvider;
7173
import org.elasticsearch.xpack.esql.common.Failures;
7274
import org.elasticsearch.xpack.esql.enrich.EnrichLookupOperator;
@@ -276,7 +278,8 @@ public List<ActionHandler> getActions() {
276278
new ActionHandler(EsqlSearchShardsAction.TYPE, EsqlSearchShardsAction.class),
277279
new ActionHandler(EsqlAsyncStopAction.INSTANCE, TransportEsqlAsyncStopAction.class),
278280
new ActionHandler(EsqlListQueriesAction.INSTANCE, TransportEsqlListQueriesAction.class),
279-
new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class)
281+
new ActionHandler(EsqlGetQueryAction.INSTANCE, TransportEsqlGetQueryAction.class),
282+
new ActionHandler(EsqlProjectRoutingAction.INSTANCE, TransportEsqlProjectRoutingAction.class)
280283
);
281284
}
282285

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ private static FieldCapabilitiesRequest createFieldCapsRequest(String index, Set
270270
req.fields(fieldNames.toArray(String[]::new));
271271
req.includeUnmapped(true);
272272
req.indexFilter(requestFilter);
273+
// Note: this is just some bogus query to demonstrate that we can invoke the full ES|QL engine from the security layer
274+
req.projectRouting("row a = 1, b = \"x\", c = 1000000000000, d = 1.1");
273275
// lenient because we throw our own errors looking at the response e.g. if something was not resolved
274276
// also because this way security doesn't throw authorization exceptions but rather honors ignore_unavailable
275277
req.indicesOptions(FIELD_CAPS_INDICES_OPTIONS);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.security;
9+
10+
import org.apache.logging.log4j.LogManager;
11+
import org.apache.logging.log4j.Logger;
12+
import org.elasticsearch.action.ActionListener;
13+
import org.elasticsearch.client.internal.Client;
14+
import org.elasticsearch.xpack.core.esql.action.EsqlQueryRequest;
15+
import org.elasticsearch.xpack.core.esql.action.EsqlQueryRequestBuilder;
16+
import org.elasticsearch.xpack.core.esql.action.EsqlQueryResponse;
17+
18+
import java.util.List;
19+
20+
public class EsqlProjectRouter {
21+
22+
private static final Logger logger = LogManager.getLogger(EsqlProjectRouter.class);
23+
24+
private final Client client;
25+
26+
public EsqlProjectRouter(Client client) {
27+
this.client = client;
28+
}
29+
30+
public List<String> route(List<String> projects, String projectRoutingQuery) {
31+
// Note: this just demonstrates that we can invoke the full ES|QL engine from the security layer
32+
// we certainly don't want to just run a generic ES|QL query
33+
34+
// Instead we should expose the EsqlProjectRoutingAction to be called the same way we
35+
// expose the EsqlQueryAction and call EsqlProjectRoutingAction here
36+
// (we will need to repeat the dance done in https://github.com/elastic/elasticsearch/issues/104413)
37+
38+
// EsqlProjectRoutingAction will be localOnly as it will only access an on-the fly in-memory index
39+
// so there are no network costs associated
40+
41+
@SuppressWarnings("unchecked")
42+
EsqlQueryRequestBuilder<EsqlQueryRequest, EsqlQueryResponse> b = (EsqlQueryRequestBuilder<
43+
EsqlQueryRequest,
44+
EsqlQueryResponse>) EsqlQueryRequestBuilder.newRequestBuilder(client);
45+
46+
b.query(projectRoutingQuery).execute(new ActionListener<>() {
47+
@Override
48+
public void onResponse(EsqlQueryResponse response) {
49+
logger.info("EsqlProjectRouter response: {}", response);
50+
}
51+
52+
@Override
53+
public void onFailure(Exception e) {
54+
logger.warn("EsqlProjectRouter failure", e);
55+
}
56+
});
57+
58+
return projects;
59+
}
60+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,8 @@ Collection<Object> createComponents(
11301130
operatorPrivilegesService.get(),
11311131
restrictedIndices,
11321132
authorizationDenialMessages.get(),
1133-
projectResolver
1133+
projectResolver,
1134+
new EsqlProjectRouter(client)
11341135
);
11351136

11361137
components.add(nativeRolesStore); // used by roles actions

0 commit comments

Comments
 (0)