-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Only expand authorized indices for requests with wildcards #72118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Only expand authorized indices for requests with wildcards #72118
Conversation
EDIT: it was actually legit failure in this PR |
}).addListener(listener, EsExecutors.newDirectExecutorService()); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a nested class inside the AuthorizationEngine
, but because it is now passed around to more classes, I had to pull it up somewhere.
I also improved the threading of it, as it was harder to reason about the corner cases than to fix it.
authzIndicesListener)); | ||
final AsyncSupplier<ResolvedIndices> resolvedIndicesAsyncSupplier = new CachingAsyncSupplier<>((resolvedIndicesListener) -> { | ||
authorizedIndicesSupplier.getAsync(ActionListener.wrap(authorizedIndices -> { | ||
resolveIndexNames(request, metadata, authorizedIndices, resolvedIndicesListener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the core part of this PR!
We don't have to call authzEngine.loadAuthorizedIndices
for all indicesAndAliasesResolver.resolve
invocations.
The authorized list is only required if the request contains wildcards.
authzEngine.authorizeIndexAction(requestInfo, authzInfo, resolvedIndicesAsyncSupplier, | ||
metadata.getIndicesLookup(), wrapPreservingContext(new AuthorizationResultListener<>(result -> | ||
handleIndexActionAuthorizationResult(result, requestInfo, requestId, authzInfo, authzEngine, authorizedIndicesSupplier, | ||
handleIndexActionAuthorizationResult(result, requestInfo, requestId, authzInfo, authzEngine, | ||
resolvedIndicesAsyncSupplier, metadata, listener), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolvedIndicesAsyncSupplier
is all that's needed, authorizedIndicesSupplier
is a superset of it.
final AuditTrail auditTrail = auditTrailService.get(); | ||
|
||
authorizedIndicesSupplier.getAsync(ActionListener.wrap(authorizedIndices -> { | ||
resolvedIndicesAsyncSupplier.getAsync(ActionListener.wrap(overallResolvedIndices -> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overallResolvedIndices
is a subset of authorizedIndices
, they're not both required.
for (BulkItemRequest item : request.items()) { | ||
String resolvedIndex = resolvedIndexNames.computeIfAbsent(item.index(), key -> { | ||
final ResolvedIndices resolvedIndices = | ||
indicesAndAliasesResolver.resolveIndicesAndAliases(item.request(), metadata, localIndices); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be utterly mucky to refactor this to use the "async" variant, the structure of the code is really not amenable to listeners.
Instead, we assume that the indicesAndAliasesResolver.resolveIndicesAndAliases
is not in fact async if the authorized indices supplier is not async. This assumption is within our control (but it's not the most elegant thing, I admit).
|
||
ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, Metadata metadata, List<String> authorizedIndices) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This got changed a lot in order to support the AsyncSupplier<List<String>>
instead of List<String>
, and it is the hardest thing to review in this PR.
@@ -546,7 +546,7 @@ GetUserPrivilegesResponse buildUserPrivilegesResponseObject(Role userRole) { | |||
return Collections.unmodifiableList(new ArrayList<>(indicesAndAliases)); | |||
} | |||
|
|||
private void buildIndicesAccessControl(Authentication authentication, String action, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable.
tests passed @elasticmachine test this please |
PlainActionFuture<ResolvedIndices> future = PlainActionFuture.newFuture(); | ||
// if the supplier is not async, the method is not async, hence get is non-blocking | ||
resolve(request, metadata, listener -> listener.onResponse(authorizedIndices), future); | ||
return FutureUtils.get(future, 0, TimeUnit.MILLISECONDS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sort of a hack:
If the authorized supplier is not in fact async (listener -> listener.onResponse(authorizedIndices)
) we know (assume) that the resolve
invocation is going to be sync. Hence, we can call FutureUtils.get
. But this calls the timeout variant, because the get without timeout variant asserts that the calling thread supports blocking (which it usually doesn't)
tests passed @elasticmachine test this please |
Raised #72194 |
ResolvedIndices.Builder resolvedIndicesBuilder = new ResolvedIndices.Builder(); | ||
// cannot pass the async supplier | ||
List<String> replaced = indexAbstractionResolver.resolveIndexAbstractions(split.getLocal(), indicesOptions, metadata, | ||
authorizedIndices, replaceWildcards, indicesRequest.includeDataStreams()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an interesting point to be made here.
indexAbstractionResolver.resolveIndexAbstractions
can be made to work without the authorizedIndices
list expanded, BUT, because it has to iterate over the split.getLocal()
list in order, it will do it ON THE STACK. Do we have a stack for all the request index parts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed c2466b8 to make indexAbstractionResolver.resolveIndexAbstractions
async.
tests passed @elasticmachine test this please |
If it helps, you can drop that condition. |
Thanks Tim! This PR is ready for review, I won't work on it further, but I expect the follow-up to be preferable in comparison. I've added you as a reviewer. One question, though, but we can defer it when both PRs are on the table, should we backport this to 6.8 ? I really hoped this variant would be simpler to code and thought it had the best chances to be backported. I'm gonna keep backporting as a semi-objective for the follow-up as well. |
I don't think so. This isn't really a bug, and I don't think it's worth the risk of trying to backport in a patch release. |
Hi @albertzaharovits @tvernum thanks a lot for working on this performance improvement. It came up in the context of improvements to reduce support load. Is the work in this PR and associated PRs still planned? |
It's still on our task list, but we don't have the cycles to do anything with it right now. |
Got it, Tim, thank you for explaining. |
Superseded by #81237 |
This is a performance improvement that avoids unnecessarily expanding the list of "authorized indices" while authorizing requests that do not contain wildcards.
This PR, as it currently stands, avoids it for all request types. It can be made simpler, see #72118 (comment) to limit it so it only avoids expansions for bulk ingest requests.
The objectives are:
Do not change the
AuthorizationEngine
interface.Do not fork from transport thread when iterating over all the indices in the cluster.
Do not change the authorization algorithm.
BUT, this requires passing the async supplier more deeply to the callees, which greatly complicates the algorithms (think loops - it is a mess).