Skip to content

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

Conversation

albertzaharovits
Copy link
Contributor

@albertzaharovits albertzaharovits commented Apr 22, 2021

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).

@albertzaharovits albertzaharovits added the :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC label Apr 22, 2021
@albertzaharovits albertzaharovits self-assigned this Apr 22, 2021
@albertzaharovits albertzaharovits changed the title Only expand authorized indices for requests with wildcards Do not expand authorized indices for bulk shard requests Apr 24, 2021
@albertzaharovits
Copy link
Contributor Author

albertzaharovits commented Apr 24, 2021

SocketTimeout @elasticmachine run elasticsearch-ci/1

EDIT: it was actually legit failure in this PR

}).addListener(listener, EsExecutors.newDirectExecutorService());
}
}
}
Copy link
Contributor Author

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);
Copy link
Contributor Author

@albertzaharovits albertzaharovits Apr 25, 2021

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),
Copy link
Contributor Author

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 -> {
Copy link
Contributor Author

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);
Copy link
Contributor Author

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) {
Copy link
Contributor Author

@albertzaharovits albertzaharovits Apr 25, 2021

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable.

@albertzaharovits
Copy link
Contributor Author

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);
Copy link
Contributor Author

@albertzaharovits albertzaharovits Apr 25, 2021

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)

@albertzaharovits
Copy link
Contributor Author

tests passed @elasticmachine test this please

@albertzaharovits
Copy link
Contributor Author

Raised #72194
@elasticmachine run elasticsearch-ci/2

ResolvedIndices.Builder resolvedIndicesBuilder = new ResolvedIndices.Builder();
// cannot pass the async supplier
List<String> replaced = indexAbstractionResolver.resolveIndexAbstractions(split.getLocal(), indicesOptions, metadata,
authorizedIndices, replaceWildcards, indicesRequest.includeDataStreams());
Copy link
Contributor Author

@albertzaharovits albertzaharovits Apr 25, 2021

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?

Copy link
Contributor Author

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.

@albertzaharovits albertzaharovits changed the title Do not expand authorized indices for bulk shard requests Only expand authorized indices for requests with wildcards Apr 25, 2021
@albertzaharovits
Copy link
Contributor Author

tests passed @elasticmachine test this please

@tvernum
Copy link
Contributor

tvernum commented Apr 26, 2021

Do not change the AuthorizationEngine interface.

If it helps, you can drop that condition.
There's no problem changing the interface is it's necessary to make this sort of performance change.

@albertzaharovits
Copy link
Contributor Author

Do not change the AuthorizationEngine interface.

If it helps, you can drop that condition.
There's no problem changing the interface is it's necessary to make this sort of performance change.

Thanks Tim!
Yes that would help. I will raise another PR where instead of an "authorized list" there would be an "authorized predicate".
All the authz algorithms have access to the the Metadata already, and they can use that together with the predicate to achieve an equivalent "authorized list".
I think this change would be much much simpler.

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.

@tvernum
Copy link
Contributor

tvernum commented May 14, 2021

should we backport this to 6.8 ?

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.

@repantis
Copy link

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?

@tvernum
Copy link
Contributor

tvernum commented Jul 21, 2021

It's still on our task list, but we don't have the cycles to do anything with it right now.

@repantis
Copy link

Got it, Tim, thank you for explaining.

@albertzaharovits
Copy link
Contributor Author

Superseded by #81237

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>enhancement :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants