Skip to content

Commit 2ae90af

Browse files
committed
Local index resolution
1 parent 18d6bcb commit 2ae90af

File tree

4 files changed

+148
-5
lines changed

4 files changed

+148
-5
lines changed

server/src/main/java/org/elasticsearch/CrossProjectResolvableRequest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
import org.elasticsearch.action.IndicesRequest;
1313
import org.elasticsearch.core.Nullable;
14+
import org.elasticsearch.transport.RemoteClusterAware;
1415

1516
import java.util.List;
1617

18+
// Extend indices.replaceable instead?
1719
public interface CrossProjectResolvableRequest extends IndicesRequest {
1820
/**
1921
* Only called if cross-project rewriting was applied
@@ -23,10 +25,22 @@ public interface CrossProjectResolvableRequest extends IndicesRequest {
2325
@Nullable
2426
List<RewrittenExpression> getRewrittenExpressions();
2527

28+
default boolean isCrossProjectModeEnabled() {
29+
return getRewrittenExpressions() != null;
30+
}
31+
2632
/**
2733
* Used to track a mapping from original expression (potentially flat) to canonical CCS expressions.
2834
*/
2935
record RewrittenExpression(String original, List<CanonicalExpression> canonicalExpressions) {}
3036

31-
record CanonicalExpression(String expression) {}
37+
record CanonicalExpression(String expression) {
38+
public boolean isQualified() {
39+
return RemoteClusterAware.isRemoteIndexName(expression);
40+
}
41+
42+
public boolean isUnqualified() {
43+
return false == isQualified();
44+
}
45+
}
3246
}

server/src/main/java/org/elasticsearch/cluster/metadata/IndexAbstractionResolver.java

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
package org.elasticsearch.cluster.metadata;
1111

12+
import org.apache.logging.log4j.LogManager;
13+
import org.apache.logging.log4j.Logger;
14+
import org.elasticsearch.CrossProjectResolvableRequest;
1215
import org.elasticsearch.action.support.IndexComponentSelector;
1316
import org.elasticsearch.action.support.IndicesOptions;
1417
import org.elasticsearch.action.support.UnsupportedSelectorException;
@@ -18,6 +21,7 @@
1821
import org.elasticsearch.index.Index;
1922
import org.elasticsearch.index.IndexNotFoundException;
2023
import org.elasticsearch.indices.SystemIndices.SystemIndexAccessLevel;
24+
import org.elasticsearch.transport.RemoteClusterAware;
2125

2226
import java.util.ArrayList;
2327
import java.util.HashSet;
@@ -28,12 +32,118 @@
2832

2933
public class IndexAbstractionResolver {
3034

35+
private static final Logger logger = LogManager.getLogger(IndexAbstractionResolver.class);
36+
3137
private final IndexNameExpressionResolver indexNameExpressionResolver;
3238

3339
public IndexAbstractionResolver(IndexNameExpressionResolver indexNameExpressionResolver) {
3440
this.indexNameExpressionResolver = indexNameExpressionResolver;
3541
}
3642

43+
public List<CrossProjectResolvableRequest.RewrittenExpression> resolveIndexAbstractionsForOrigin(
44+
Iterable<CrossProjectResolvableRequest.RewrittenExpression> rewrittenExpressions,
45+
IndicesOptions indicesOptions,
46+
ProjectMetadata projectMetadata,
47+
Function<IndexComponentSelector, Set<String>> allAuthorizedAndAvailableBySelector,
48+
BiPredicate<String, IndexComponentSelector> isAuthorized,
49+
boolean includeDataStreams
50+
) {
51+
// TODO handle exclusions and consolidate with `resolveIndexAbstractions` somehow, maybe?
52+
List<CrossProjectResolvableRequest.RewrittenExpression> finalRewrittenExpressions = new ArrayList<>();
53+
for (CrossProjectResolvableRequest.RewrittenExpression rewrittenExpression : rewrittenExpressions) {
54+
// qualified original expression, nothing to rewrite (TODO handle origin)
55+
if (RemoteClusterAware.isRemoteIndexName(rewrittenExpression.original())) {
56+
logger.info("Skipping rewriting of qualified expression [{}] for origin", rewrittenExpression.original());
57+
finalRewrittenExpressions.add(rewrittenExpression);
58+
continue;
59+
}
60+
// no expressions targeting origin, also nothing to rewrite
61+
if (rewrittenExpression.canonicalExpressions()
62+
.stream()
63+
.noneMatch(CrossProjectResolvableRequest.CanonicalExpression::isUnqualified)) {
64+
logger.info(
65+
"Skipping rewriting of qualified expression [{}] because there are no origin expressions",
66+
rewrittenExpression.original()
67+
);
68+
finalRewrittenExpressions.add(rewrittenExpression);
69+
continue;
70+
}
71+
72+
String indexAbstraction = rewrittenExpression.original();
73+
74+
// Always check to see if there's a selector on the index expression
75+
Tuple<String, String> expressionAndSelector = IndexNameExpressionResolver.splitSelectorExpression(indexAbstraction);
76+
String selectorString = expressionAndSelector.v2();
77+
if (indicesOptions.allowSelectors() == false && selectorString != null) {
78+
throw new UnsupportedSelectorException(indexAbstraction);
79+
}
80+
indexAbstraction = expressionAndSelector.v1();
81+
IndexComponentSelector selector = IndexComponentSelector.getByKeyOrThrow(selectorString);
82+
83+
// we always need to check for date math expressions
84+
indexAbstraction = IndexNameExpressionResolver.resolveDateMathExpression(indexAbstraction);
85+
86+
if (indicesOptions.expandWildcardExpressions() && Regex.isSimpleMatchPattern(indexAbstraction)) {
87+
Set<String> resolvedIndices = new HashSet<>();
88+
for (String authorizedIndex : allAuthorizedAndAvailableBySelector.apply(selector)) {
89+
if (Regex.simpleMatch(indexAbstraction, authorizedIndex)
90+
&& isIndexVisible(
91+
indexAbstraction,
92+
selectorString,
93+
authorizedIndex,
94+
indicesOptions,
95+
projectMetadata,
96+
indexNameExpressionResolver,
97+
includeDataStreams
98+
)) {
99+
resolveSelectorsAndCollect(authorizedIndex, selectorString, indicesOptions, resolvedIndices, projectMetadata);
100+
}
101+
}
102+
if (resolvedIndices.isEmpty()) {
103+
// es core honours allow_no_indices for each wildcard expression, we do the same here by throwing index not found.
104+
if (indicesOptions.allowNoIndices() == false) {
105+
// TODO gotta do something here
106+
}
107+
finalRewrittenExpressions.add(replaceOriginIndices(rewrittenExpression, Set.of()));
108+
} else {
109+
finalRewrittenExpressions.add(replaceOriginIndices(rewrittenExpression, resolvedIndices));
110+
}
111+
} else {
112+
Set<String> resolvedIndices = new HashSet<>();
113+
resolveSelectorsAndCollect(indexAbstraction, selectorString, indicesOptions, resolvedIndices, projectMetadata);
114+
115+
// TODO this is probably not right?
116+
if (indicesOptions.ignoreUnavailable() == false) {
117+
finalRewrittenExpressions.add(replaceOriginIndices(rewrittenExpression, resolvedIndices));
118+
} else if (isAuthorized.test(indexAbstraction, selector)) {
119+
finalRewrittenExpressions.add(replaceOriginIndices(rewrittenExpression, resolvedIndices));
120+
} else {
121+
finalRewrittenExpressions.add(replaceOriginIndices(rewrittenExpression, Set.of()));
122+
}
123+
}
124+
}
125+
// TODO assert size is the same
126+
return finalRewrittenExpressions;
127+
}
128+
129+
private static CrossProjectResolvableRequest.RewrittenExpression replaceOriginIndices(
130+
CrossProjectResolvableRequest.RewrittenExpression rewrittenExpression,
131+
Set<String> resolvedIndices
132+
) {
133+
List<CrossProjectResolvableRequest.CanonicalExpression> resolvedOriginalExpressions = resolvedIndices.stream()
134+
.map(CrossProjectResolvableRequest.CanonicalExpression::new)
135+
.toList();
136+
List<CrossProjectResolvableRequest.CanonicalExpression> qualifiedExpressions = rewrittenExpression.canonicalExpressions()
137+
.stream()
138+
.filter(CrossProjectResolvableRequest.CanonicalExpression::isQualified)
139+
.toList();
140+
List<CrossProjectResolvableRequest.CanonicalExpression> combined = new ArrayList<>(resolvedOriginalExpressions);
141+
combined.addAll(qualifiedExpressions);
142+
var e = new CrossProjectResolvableRequest.RewrittenExpression(rewrittenExpression.original(), combined);
143+
logger.info("Replaced origin indices for expression [{}] with [{}]", rewrittenExpression, e);
144+
return e;
145+
}
146+
37147
public List<String> resolveIndexAbstractions(
38148
Iterable<String> indices,
39149
IndicesOptions indicesOptions,
@@ -105,6 +215,7 @@ && isIndexVisible(
105215
// discarded from the `finalIndices` list. Other "ways of unavailable" must be handled by the action
106216
// handler, see: https://github.com/elastic/elasticsearch/issues/90215
107217
finalIndices.addAll(resolvedIndices);
218+
108219
}
109220
}
110221
}

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,9 @@ void maybeRewriteCrossProjectResolvableRequest(
174174
for (String indexExpression : indices) {
175175
boolean isQualified = RemoteClusterAware.isRemoteIndexName(indexExpression);
176176
if (isQualified) {
177+
// TODO handle empty case here -- empty means "search all" in ES which is _not_ what we want
177178
List<CrossProjectResolvableRequest.CanonicalExpression> canonicalExpressions = rewriteQualified(indexExpression, projects);
178-
// could fail early here in ignore_unavailable_indices and allow_no_indices strict mode if things are empty
179+
// could fail early here in ignore_unavailable and allow_no_indices strict mode if things are empty
179180
rewrittenExpressions.add(new CrossProjectResolvableRequest.RewrittenExpression(indexExpression, canonicalExpressions));
180181
logger.info("Rewrote qualified expression [{}] to [{}]", indexExpression, canonicalExpressions);
181182
} else {
@@ -444,21 +445,38 @@ ResolvedIndices resolveIndicesAndAliases(
444445
// if we cannot replace wildcards the indices list stays empty. Same if there are no authorized indices.
445446
// we honour allow_no_indices like es core does.
446447
} else {
448+
if (indicesRequest instanceof CrossProjectResolvableRequest crossProjectResolvableRequest
449+
&& crossProjectResolvableRequest.isCrossProjectModeEnabled()) {
450+
assert crossProjectResolvableRequest.getRewrittenExpressions() != null;
451+
List<CrossProjectResolvableRequest.RewrittenExpression> rewrittenExpressions = indexAbstractionResolver
452+
.resolveIndexAbstractionsForOrigin(
453+
crossProjectResolvableRequest.getRewrittenExpressions(),
454+
indicesOptions,
455+
projectMetadata,
456+
authorizedIndices::all,
457+
authorizedIndices::check,
458+
indicesRequest.includeDataStreams()
459+
);
460+
crossProjectResolvableRequest.setRewrittenExpressions(rewrittenExpressions);
461+
assert ((IndicesRequest.Replaceable) indicesRequest).allowsRemoteIndices();
462+
return remoteClusterResolver.splitLocalAndRemoteIndexNames(indicesRequest.indices());
463+
}
464+
447465
final ResolvedIndices split;
448466
if (replaceable.allowsRemoteIndices()) {
449467
split = remoteClusterResolver.splitLocalAndRemoteIndexNames(indicesRequest.indices());
450468
} else {
451469
split = new ResolvedIndices(Arrays.asList(indicesRequest.indices()), Collections.emptyList());
452470
}
453-
List<String> replaced = indexAbstractionResolver.resolveIndexAbstractions(
471+
List<String> resolved = indexAbstractionResolver.resolveIndexAbstractions(
454472
split.getLocal(),
455473
indicesOptions,
456474
projectMetadata,
457475
authorizedIndices::all,
458476
authorizedIndices::check,
459477
indicesRequest.includeDataStreams()
460478
);
461-
resolvedIndicesBuilder.addLocal(replaced);
479+
resolvedIndicesBuilder.addLocal(resolved);
462480
resolvedIndicesBuilder.addRemote(split.getRemote());
463481
}
464482

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ requests from all the nodes are attached with a user (either a serialize
122122
});
123123
} else {
124124
if (SystemUser.is(authentication.getEffectiveSubject().getUser()) == false) {
125-
logger.info(
125+
logger.debug(
126126
"Authenticated request [{}] for action [{}] from [{}]",
127127
authentication.getEffectiveSubject().getUser(),
128128
securityAction,

0 commit comments

Comments
 (0)