Skip to content

Commit f209db6

Browse files
authored
[Failure Store] Fix resolved alias retrieval for failure indices (#127458)
While expanding our tests in #126891, we discovered that there was a difference in behaviour when an alias was used during search. Expected behaviour When a user uses an alias to access data, we pass this alias also to the node requests because that ensures that the user's authorisation will be evaluated based on the same premise. Observed behaviour When a user would use the alias and the ::failures selector, we noticed that the request was resolved to the failure indices and the alias was not used further which sometimes resulted in an unauthorised error. The reason was that when a node different than the coordinating node would try to evaluate if the user has permissions, they would evaluate that against the failure indices themselves and not the alias with the ::failures selector. Solution In this PR we ensure that a resolved alias is retrieved for failure indices too. The indexAliases method is used in two ways (in production code): To retrieve all the resolved aliases that match an index To retrieve the filtered aliases that match the index (when there is no unfiltered reference to this index) Because failure indices are not supported by filtered aliases, the code would return null when a failure index was encountered. That works well for the second case and not for the first. In order to address that we moved the check for the failure index to the data stream alias predicate and we let the code match failure indices against resolved failure expressions and backing indices against resolved data expressions. Furthermore, we added methods capturing these two common cases so the users of these methods for not need to know the details of how to call them.
1 parent 0ce8448 commit f209db6

File tree

5 files changed

+450
-99
lines changed

5 files changed

+450
-99
lines changed

server/src/main/java/org/elasticsearch/action/admin/cluster/shards/TransportClusterSearchShardsAction.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.elasticsearch.cluster.routing.ShardIterator;
2626
import org.elasticsearch.cluster.routing.ShardRouting;
2727
import org.elasticsearch.cluster.service.ClusterService;
28-
import org.elasticsearch.core.Predicates;
2928
import org.elasticsearch.index.shard.ShardId;
3029
import org.elasticsearch.indices.IndicesService;
3130
import org.elasticsearch.injection.guice.Inject;
@@ -105,14 +104,7 @@ protected void masterOperation(
105104
Set<ResolvedExpression> indicesAndAliases = indexNameExpressionResolver.resolveExpressions(project.metadata(), request.indices());
106105
for (String index : concreteIndices) {
107106
final AliasFilter aliasFilter = indicesService.buildAliasFilter(project, index, indicesAndAliases);
108-
final String[] aliases = indexNameExpressionResolver.indexAliases(
109-
project.metadata(),
110-
index,
111-
Predicates.always(),
112-
Predicates.always(),
113-
true,
114-
indicesAndAliases
115-
);
107+
final String[] aliases = indexNameExpressionResolver.allIndexAliases(project.metadata(), index, indicesAndAliases);
116108
indicesAndFilters.put(index, AliasFilter.of(aliasFilter.getQueryBuilder(), aliases));
117109
}
118110

server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
import org.elasticsearch.common.util.Maps;
6363
import org.elasticsearch.common.util.concurrent.CountDown;
6464
import org.elasticsearch.common.util.concurrent.EsExecutors;
65-
import org.elasticsearch.core.Predicates;
6665
import org.elasticsearch.core.TimeValue;
6766
import org.elasticsearch.index.Index;
6867
import org.elasticsearch.index.IndexNotFoundException;
@@ -232,14 +231,7 @@ private Map<String, OriginalIndices> buildPerIndexOriginalIndices(
232231
blocks.indexBlockedRaiseException(projectState.projectId(), ClusterBlockLevel.READ, index);
233232
}
234233

235-
String[] aliases = indexNameExpressionResolver.indexAliases(
236-
projectState.metadata(),
237-
index,
238-
Predicates.always(),
239-
Predicates.always(),
240-
true,
241-
indicesAndAliases
242-
);
234+
String[] aliases = indexNameExpressionResolver.allIndexAliases(projectState.metadata(), index, indicesAndAliases);
243235
String[] finalIndices = Strings.EMPTY_ARRAY;
244236
if (aliases == null
245237
|| aliases.length == 0

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

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import java.util.Set;
6060
import java.util.SortedMap;
6161
import java.util.function.BiFunction;
62+
import java.util.function.BiPredicate;
6263
import java.util.function.Function;
6364
import java.util.function.LongSupplier;
6465
import java.util.function.Predicate;
@@ -80,6 +81,13 @@ public class IndexNameExpressionResolver {
8081
public static final String EXCLUDED_DATA_STREAMS_KEY = "es.excluded_ds";
8182
public static final IndexVersion SYSTEM_INDEX_ENFORCEMENT_INDEX_VERSION = IndexVersions.V_8_0_0;
8283

84+
private static final BiPredicate<DataStreamAlias, Boolean> ALL_DATA_STREAM_ALIASES = (ignoredAlias, ignoredIsData) -> true;
85+
// Alias filters are not applied against indices in an abstraction's failure component.
86+
// They do not match the mapping of the data stream nor are the documents mapped for searching.
87+
private static final BiPredicate<DataStreamAlias, Boolean> ONLY_FILTERING_DATA_STREAM_ALIASES = (
88+
dataStreamAlias,
89+
isData) -> dataStreamAlias.filteringRequired() && isData;
90+
8391
private final ThreadContext threadContext;
8492
private final SystemIndices systemIndices;
8593
private final ProjectResolver projectResolver;
@@ -1054,12 +1062,21 @@ public String[] filteringAliases(ProjectMetadata project, String index, Set<Reso
10541062
project,
10551063
index,
10561064
AliasMetadata::filteringRequired,
1057-
DataStreamAlias::filteringRequired,
1065+
ONLY_FILTERING_DATA_STREAM_ALIASES,
10581066
false,
10591067
resolvedExpressions
10601068
);
10611069
}
10621070

1071+
/**
1072+
* Iterates through the list of indices and selects the effective list of all aliases for the
1073+
* given index. Aliases are returned even if the index is included in the resolved expressions.
1074+
* <b>NOTE</b>: The provided expressions must have been resolved already via {@link #resolveExpressions}.
1075+
*/
1076+
public String[] allIndexAliases(ProjectMetadata project, String index, Set<ResolvedExpression> resolvedExpressions) {
1077+
return indexAliases(project, index, Predicates.always(), ALL_DATA_STREAM_ALIASES, true, resolvedExpressions);
1078+
}
1079+
10631080
/**
10641081
* Whether to generate the candidate set from index aliases, or from the set of resolved expressions.
10651082
* @param indexAliasesSize the number of aliases of the index
@@ -1080,7 +1097,7 @@ public String[] indexAliases(
10801097
ProjectMetadata project,
10811098
String index,
10821099
Predicate<AliasMetadata> requiredAlias,
1083-
Predicate<DataStreamAlias> requiredDataStreamAlias,
1100+
BiPredicate<DataStreamAlias, Boolean> requiredDataStreamAlias,
10841101
boolean skipIdentity,
10851102
Set<ResolvedExpression> resolvedExpressions
10861103
) {
@@ -1107,13 +1124,8 @@ public String[] indexAliases(
11071124
IndexAbstraction ia = project.getIndicesLookup().get(index);
11081125
DataStream dataStream = ia.getParentDataStream();
11091126
if (dataStream != null) {
1110-
if (dataStream.getFailureComponent().containsIndex(index)) {
1111-
// Alias filters are not applied against indices in an abstraction's failure component.
1112-
// They do not match the mapping of the data stream nor are the documents mapped for searching.
1113-
return null;
1114-
}
1115-
1116-
if (skipIdentity == false && resolvedExpressionsContainsAbstraction(resolvedExpressions, dataStream.getName())) {
1127+
boolean isData = dataStream.isFailureStoreIndex(index) == false;
1128+
if (skipIdentity == false && resolvedExpressionsContainsAbstraction(resolvedExpressions, dataStream.getName(), isData)) {
11171129
// skip the filters when the request targets the data stream name + selector directly
11181130
return null;
11191131
}
@@ -1122,12 +1134,14 @@ public String[] indexAliases(
11221134
if (iterateIndexAliases(dataStreamAliases.size(), resolvedExpressions.size())) {
11231135
aliasesForDataStream = dataStreamAliases.values()
11241136
.stream()
1125-
.filter(dataStreamAlias -> resolvedExpressionsContainsAbstraction(resolvedExpressions, dataStreamAlias.getName()))
1137+
.filter(
1138+
dataStreamAlias -> resolvedExpressionsContainsAbstraction(resolvedExpressions, dataStreamAlias.getName(), isData)
1139+
)
11261140
.filter(dataStreamAlias -> dataStreamAlias.getDataStreams().contains(dataStream.getName()))
11271141
.toList();
11281142
} else {
11291143
aliasesForDataStream = resolvedExpressions.stream()
1130-
.filter(expression -> expression.selector() == null || expression.selector().shouldIncludeData())
1144+
.filter(expression -> (expression.selector() == null || expression.selector().shouldIncludeData()) == isData)
11311145
.map(ResolvedExpression::resource)
11321146
.map(dataStreamAliases::get)
11331147
.filter(dataStreamAlias -> dataStreamAlias != null && dataStreamAlias.getDataStreams().contains(dataStream.getName()))
@@ -1136,11 +1150,12 @@ public String[] indexAliases(
11361150

11371151
List<String> requiredAliases = null;
11381152
for (DataStreamAlias dataStreamAlias : aliasesForDataStream) {
1139-
if (requiredDataStreamAlias.test(dataStreamAlias)) {
1153+
if (requiredDataStreamAlias.test(dataStreamAlias, isData)) {
11401154
if (requiredAliases == null) {
11411155
requiredAliases = new ArrayList<>(aliasesForDataStream.size());
11421156
}
1143-
requiredAliases.add(dataStreamAlias.getName());
1157+
String alias = isData ? dataStreamAlias.getName() : dataStreamAlias.getName() + "::failures";
1158+
requiredAliases.add(alias);
11441159
} else {
11451160
// we have a non-required alias for this data stream so no need to check further
11461161
return null;
@@ -1162,7 +1177,7 @@ public String[] indexAliases(
11621177
// Indices can only be referenced with a data selector, or a null selector if selectors are disabled
11631178
for (AliasMetadata aliasMetadata : indexAliases.values()) {
11641179
var alias = aliasMetadata.alias();
1165-
if (resolvedExpressionsContainsAbstraction(resolvedExpressions, alias)) {
1180+
if (resolvedExpressionsContainsAbstraction(resolvedExpressions, alias, true)) {
11661181
if (requiredAlias.test(aliasMetadata) == false) {
11671182
return null;
11681183
}
@@ -1185,9 +1200,16 @@ public String[] indexAliases(
11851200
}
11861201
}
11871202

1188-
private static boolean resolvedExpressionsContainsAbstraction(Set<ResolvedExpression> resolvedExpressions, String abstractionName) {
1189-
return resolvedExpressions.contains(new ResolvedExpression(abstractionName))
1190-
|| resolvedExpressions.contains(new ResolvedExpression(abstractionName, IndexComponentSelector.DATA));
1203+
private static boolean resolvedExpressionsContainsAbstraction(
1204+
Set<ResolvedExpression> resolvedExpressions,
1205+
String abstractionName,
1206+
boolean isData
1207+
) {
1208+
if (isData) {
1209+
return resolvedExpressions.contains(new ResolvedExpression(abstractionName))
1210+
|| resolvedExpressions.contains(new ResolvedExpression(abstractionName, IndexComponentSelector.DATA));
1211+
}
1212+
return resolvedExpressions.contains(new ResolvedExpression(abstractionName, IndexComponentSelector.FAILURES));
11911213
}
11921214

11931215
/**

0 commit comments

Comments
 (0)