Skip to content

Commit 7cbc236

Browse files
authored
Set resolved index expressions once (#135699)
There are scenarios in which authorization can be called twice for a single request (by `SecurityActionFilter` and `SecurityTransportFilter` under certain configurations). This leads to multiple calls to index resolution logic, which is duplicate work and also incorrectly overwrites already recorded index resolution results. This PR adds logic to avoid overwriting already recorded resolution results. We should investigate this more thoroughly and potentially address with a comprehensive follow-up PR.
1 parent 1aca0a9 commit 7cbc236

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77
package org.elasticsearch.xpack.security.authz;
88

9+
import org.apache.logging.log4j.LogManager;
10+
import org.apache.logging.log4j.Logger;
911
import org.elasticsearch.action.AliasesRequest;
1012
import org.elasticsearch.action.IndicesRequest;
1113
import org.elasticsearch.action.ResolvedIndexExpressions;
@@ -51,10 +53,13 @@
5153
import java.util.concurrent.CopyOnWriteArraySet;
5254
import java.util.function.BiPredicate;
5355

56+
import static org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.isNoneExpression;
5457
import static org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER;
5558

5659
class IndicesAndAliasesResolver {
5760

61+
private static final Logger logger = LogManager.getLogger(IndicesAndAliasesResolver.class);
62+
5863
private final IndexNameExpressionResolver nameExpressionResolver;
5964
private final IndexAbstractionResolver indexAbstractionResolver;
6065
private final RemoteClusterResolver remoteClusterResolver;
@@ -364,7 +369,7 @@ ResolvedIndices resolveIndicesAndAliases(
364369
// once we've migrated from `indices()` to using resolved expressions holistically,
365370
// we will always store them
366371
if (recordResolvedIndexExpressions) {
367-
replaceable.setResolvedIndexExpressions(resolved);
372+
setResolvedIndexExpressionsIfUnset(replaceable, resolved);
368373
}
369374
resolvedIndicesBuilder.addLocal(resolved.getLocalIndicesList());
370375
resolvedIndicesBuilder.addRemote(split.getRemote());
@@ -435,6 +440,24 @@ ResolvedIndices resolveIndicesAndAliases(
435440
return resolvedIndicesBuilder.build();
436441
}
437442

443+
private static void setResolvedIndexExpressionsIfUnset(IndicesRequest.Replaceable replaceable, ResolvedIndexExpressions resolved) {
444+
if (replaceable.getResolvedIndexExpressions() == null) {
445+
replaceable.setResolvedIndexExpressions(resolved);
446+
} else {
447+
// see https://github.com/elastic/elasticsearch/issues/135799
448+
String message = "resolved index expressions are already set to ["
449+
+ replaceable.getResolvedIndexExpressions()
450+
+ "] and should not be set again. Attempted to set to new expressions ["
451+
+ resolved
452+
+ "] for ["
453+
+ replaceable.getClass().getName()
454+
+ "]";
455+
logger.debug(message);
456+
// we are excepting `*,-*` below since we've observed this already -- keeping this assertion catch other cases
457+
assert replaceable.indices() == null || isNoneExpression(replaceable.indices()) : message;
458+
}
459+
}
460+
438461
/**
439462
* Special handling of the value to authorize for a put mapping request. Dynamic put mapping
440463
* requests use a concrete index, but we allow permissions to be defined on aliases so if the

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,15 @@
112112
import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.newInstance;
113113
import static org.elasticsearch.test.ActionListenerUtils.anyActionListener;
114114
import static org.elasticsearch.test.TestMatchers.throwableWithMessage;
115+
import static org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField.NO_INDICES_OR_ALIASES_ARRAY;
115116
import static org.elasticsearch.xpack.core.security.test.TestRestrictedIndices.RESTRICTED_INDICES;
116117
import static org.elasticsearch.xpack.security.authz.AuthorizedIndicesTests.getRequestInfo;
117118
import static org.elasticsearch.xpack.security.support.SecuritySystemIndices.SECURITY_MAIN_ALIAS;
118119
import static org.hamcrest.CoreMatchers.containsString;
119120
import static org.hamcrest.CoreMatchers.instanceOf;
120121
import static org.hamcrest.CoreMatchers.is;
121122
import static org.hamcrest.CoreMatchers.notNullValue;
123+
import static org.hamcrest.CoreMatchers.sameInstance;
122124
import static org.hamcrest.Matchers.arrayContaining;
123125
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
124126
import static org.hamcrest.Matchers.contains;
@@ -811,6 +813,29 @@ public void testResolveWildcardsStrictExpand() {
811813
);
812814
}
813815

816+
public void testResolveMultipleTimesDoesNotOverwriteExpressions() {
817+
SearchRequest request = new SearchRequest("barbaz", "foofoo*");
818+
request.indicesOptions(IndicesOptions.fromOptions(false, true, true, false));
819+
List<String> indices = resolveIndices(request, buildAuthorizedIndices(user, TransportSearchAction.TYPE.name())).getLocal();
820+
String[] replacedIndices = new String[] { "barbaz", "foofoobar", "foofoo" };
821+
assertThat(indices, hasSize(replacedIndices.length));
822+
assertThat(request.indices().length, equalTo(replacedIndices.length));
823+
assertThat(indices, hasItems(replacedIndices));
824+
assertThat(request.indices(), arrayContainingInAnyOrder(replacedIndices));
825+
ResolvedIndexExpressions actual = request.getResolvedIndexExpressions();
826+
assertThat(actual, is(notNullValue()));
827+
assertThat(
828+
actual.expressions(),
829+
contains(
830+
resolvedIndexExpression("barbaz", Set.of("barbaz"), CONCRETE_RESOURCE_UNAUTHORIZED),
831+
resolvedIndexExpression("foofoo*", Set.of("foofoobar", "foofoo"), SUCCESS)
832+
)
833+
);
834+
request.indices(NO_INDICES_OR_ALIASES_ARRAY);
835+
resolveIndices(request, buildAuthorizedIndices(user, TransportSearchAction.TYPE.name()));
836+
assertThat(actual, sameInstance(request.getResolvedIndexExpressions()));
837+
}
838+
814839
public void testResolveWildcardsExpandOpenAndClosedIgnoreUnavailable() {
815840
SearchRequest request = new SearchRequest("barbaz", "foofoo*");
816841
request.indicesOptions(IndicesOptions.fromOptions(true, randomBoolean(), true, true));
@@ -1812,7 +1837,7 @@ public void testResolveAliasesWildcardsGetAliasesRequestNoAuthorizedIndices() {
18121837
request.aliases("bar*");
18131838
request.indices("*bar");
18141839
resolveIndices(request, buildAuthorizedIndices(user, GetAliasesAction.NAME));
1815-
assertThat(request.aliases(), arrayContaining(IndicesAndAliasesResolverField.NO_INDICES_OR_ALIASES_ARRAY));
1840+
assertThat(request.aliases(), arrayContaining(NO_INDICES_OR_ALIASES_ARRAY));
18161841
}
18171842

18181843
public void testResolveAliasesExclusionWildcardsGetAliasesRequest() {
@@ -1869,7 +1894,7 @@ public void testResolveAliasesAllGetAliasesRequestNoAuthorizedIndices() {
18691894
ResolvedIndices resolvedIndices = resolveIndices(request, buildAuthorizedIndices(userNoIndices, GetAliasesAction.NAME));
18701895
assertThat(resolvedIndices.getLocal(), contains("non_existing"));
18711896
assertThat(Arrays.asList(request.indices()), contains("non_existing"));
1872-
assertThat(request.aliases(), arrayContaining(IndicesAndAliasesResolverField.NO_INDICES_OR_ALIASES_ARRAY));
1897+
assertThat(request.aliases(), arrayContaining(NO_INDICES_OR_ALIASES_ARRAY));
18731898
}
18741899

18751900
/**

0 commit comments

Comments
 (0)