-
Notifications
You must be signed in to change notification settings - Fork 24.6k
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
Improve "Has Privilege" performance for boolean-only response #86685
Improve "Has Privilege" performance for boolean-only response #86685
Conversation
Hi @albertzaharovits, I've created a changelog YAML for you. |
...ugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/AuthorizationEngine.java
Outdated
Show resolved
Hide resolved
Set<String> checkForIndexPatterns, | ||
boolean allowRestrictedIndices, | ||
Set<String> checkForPrivileges | ||
Set<String> checkForPrivileges, | ||
@Nullable ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder | ||
); |
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 would like to take some space to explain my interface decisions for: Role#checkIndicesPrivilegeges
, Role#checkApplicationResourcePrivileges
, IndicesPermission#checkREsourcePrivileges
, and ApplicationPermission#checkResourcePrivileges
.
These methods must be aware of the mode of operation, ie whether to short-circuit or not, because they must return early in the short-circuit mode (in addition, the response "type" is different between the 2 modes) . So, one of the parameters of these methods must express this operation mode.
In this case, I think there are two implementation options to go about it:
- pass a dedicated parameter, to signal the short-circuit mode, and make the return value contain an optional field which is
null
when in the short-circuit mode; egRBACEngine#checkPrivileges(..., boolean runDetailedCheck, ..., ActionListener<PrivilegesCheckResult> listener)
wherePrivilegesCheckResult
has two fields:boolean allChecksSuccess
and an optionalDetails details
. - return 2 values, rather than a single one with an optional
null
field. The first return value is theallChecksSuccess
flag. The second returned value is via an out-parameter 👻 . When the out-parameter is non-null
, it is used to return the privileges details, ie it works in the regular, non-short-circuit mode; otherwise, ifnull
, no check details are returned and the operation mode is short-circuit.
I had 2 reasons to choose the second option for these methods (unlike RBACEngine#checkPrivileges
):
- I disliked having a
boolean
parameter going from the REST handler all the layers through to the permission level inside a role. ResourcePrivilegesMap
has a boolean fieldallowAll
. Its value is currently driven by the individual privilege check results. But In the short-circuit mode, it becomes separated from the check results, as it can betrue
/false
when the individual privilege check results arenull
. With such a separatedallowAll
parameter, keeping it still a member of theResourcePrivilegesMap
felt janky (what does it mean to update its value in the non-short-circuit mode, for every privilege result, when its value can technically also be set externally - the encapsulation was gone).
I hope the scare of an out-parameter is not too great.
I think this is the more elegant implementation of the two. I like that these check*
methods always return a simple boolean. Also, the out parameter works OK in the application check section in RBACEngine#checkPrivileges
and in the LimitedRole
class, I think.
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 noticed the out parameters on my first reading of the PR. I would probably approach it differently. But what is proposed in this PR works as well. Plus you already considered the other option and ruled it out with reasons. Also your reasoning about the standalone allowAll
field and the individual check results of ResourcePrivilegesMap
is a good one.
In summary, I am OK with the out parameters.
PS: In future, we may need support caching for LimitedRole (now it is only supported for SimpleRole). For that I am thinking that the result intersecting between role
and limited-by-role
should be done at the overall PrivilegesCheckResult
level rather than the individual checks level (and also move most RBCEngine#checkPrivileges
to Role
level). This has nothing to do with the current PR. Just thinking out loud in case it is something you also thought about.
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.
In summary, I am OK with the out parameters.
🙇
PS: In future, we may need support caching for LimitedRole (now it is only supported for SimpleRole). For that I am thinking that the result intersecting between role and limited-by-role should be done at the overall PrivilegesCheckResult level rather than the individual checks level (and also move most RBCEngine#checkPrivileges to Role level). This has nothing to do with the current PR. Just thinking out loud in case it is something you also thought about.
No I haven't considered something like that. I'm not sure I follow either. My first approach would be to investigate whether the LimitedRole
can be cached like the Role
can.
Pinging @elastic/es-security (Team:Security) |
@ywangd this is ready for review |
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.
LGTM
Some comments nothing major. Up to you how to deal with them. Thanks!
* The result of a (has) privilege check. This is not an Elasticsearch authorization result (though clients can base their authorization | ||
* decisions on this response). The {@link #allChecksSuccess} field tells if all the privileges are granted over all the resources. |
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.
Can you clarify what do you mean by "thi is not an Elasticsearch authorization result"? Do you mean it is literally not the AuthorizationResult
class or do you mean the authorization result might be different if you execute a request with the same privileged user?
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 mean that this not to be used/interpreted as the Elasticsearch authorization result.
boolean privilegesGranted = userRole.checkIndicesPrivileges( | ||
Sets.newHashSet(check.getIndices()), | ||
check.allowRestrictedIndices(), | ||
Sets.newHashSet(check.getPrivileges()) | ||
Sets.newHashSet(check.getPrivileges()), | ||
combineIndicesResourcePrivileges | ||
); |
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 is still a split information in the return boolean and the out-parameter. So I think it's helpful to assert the returned value is consistent with the out-parameter when it is not null
(here and other call sites).
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 had to revert these the assertions on the ResourcePrivilegesMap.Builder
.
I have encountered NPEs which I was unable to track down all day.
I suspect there is some stupid magic about working with streams that I was unable to untangle. I don't think there are issues with the main code.
...src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ResourcePrivileges.java
Show resolved
Hide resolved
ResourcePrivilegesMap.Builder resourcePrivilegesMapBuilder = randomBoolean() ? ResourcePrivilegesMap.builder() : null; | ||
boolean privilegesCheck = role.checkIndicesPrivileges( | ||
checkForIndexPatterns, | ||
allowRestrictedIndices, | ||
checkForPrivileges, | ||
resourcePrivilegesMapBuilder | ||
); | ||
assertThat(privilegesCheck, is(expectedCheckResult)); | ||
|
||
if (resourcePrivilegesMapBuilder == null) { | ||
resourcePrivilegesMapBuilder = ResourcePrivilegesMap.builder(); | ||
privilegesCheck = role.checkIndicesPrivileges( | ||
checkForIndexPatterns, | ||
allowRestrictedIndices, | ||
checkForPrivileges, | ||
resourcePrivilegesMapBuilder | ||
); | ||
assertThat(privilegesCheck, is(expectedCheckResult)); | ||
} else { | ||
privilegesCheck = role.checkIndicesPrivileges(checkForIndexPatterns, allowRestrictedIndices, checkForPrivileges, null); | ||
assertThat(privilegesCheck, is(expectedCheckResult)); | ||
} | ||
assertThat(resourcePrivilegesMapBuilder.build(), equalTo(expectedAppPrivsByResource)); |
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 think we can just remove the randomisation and just let the code call role.checkIndicesPrivileges
twice with null
and non-null
builder? That is what it does anyway. Removing randomisation probably reads a bit easier.
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 prefer we keep it. I think there's a high chance the order can make a difference when we evolve the caching.
public boolean allAllowed() { | ||
return allAllowed; | ||
} |
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 wonder whether there is value to keep this method and let it compute it on the fly from the inner map?
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.
Part of the ethos of this change was to keep the ResourcePrivilegesMap
reserved for the check details only, so not include the authorization result (because they can exist separately). The allAllowed
result can be confounded with the authorization result. Overall, I think it is better we don't expose it.
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
Outdated
Show resolved
Hide resolved
...security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java
Outdated
Show resolved
Hide resolved
@elasticmachine test this please |
1 similar comment
@elasticmachine test this please |
@elasticmachine run elasticsearch-ci/bwc |
Boolean-only privilege checks, i.e. the ones currently used in the "profile has privilege" API,
now benefit from a performance improvement, because the check will now stop upon first
encountering a privilege that is NOT granted over a resource (and return
false
overall).Previously, all the privileges were always checked over all the resources in order to assemble
a comprehensive response with all the privileges that are not granted.