-
Notifications
You must be signed in to change notification settings - Fork 25k
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
Updatable API keys - noop check #88346
Conversation
for (final String clusterPrivilege : clusterPrivileges) { | ||
putRoleRequest.cluster(clusterPrivilege); | ||
} | ||
putRoleRequest.cluster(clusterPrivileges); |
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.
Fix test to use all cluster privs, not just last.
@elasticmachine run elasticsearch-ci/part-2-fips plz. Unrelated failure. |
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 have a few comments.
I appreciate your exhaustive work for the isNoop
method.
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java
Show resolved
Hide resolved
assert userRoles != null; | ||
final List<RoleDescriptor> currentLimitedByRoleDescriptorRoles = parseRoleDescriptorsBytes( |
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.
Nit: The name currentLimitedByRoleDescriptorRoles
is a bit awkward. It should just be currentLimitedByRoleDescriptors
. Also, userRoles
really should be userRoleDescriptors
since the code has distinct concepts for role, role name and role descriptor.
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.
Oh whoops currentLimitedByRoleDescriptorRoles
is definitely a copy&mishap. Thanks for catching 👍
Also, userRoles really should be userRoleDescriptors since the code has distinct concepts for role, role name and role descriptor.
I'm good with that, I went with userRoles
because that's what it was called before the work on updatable API keys elsewhere in this class.
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 went with userRoles because that's what it was called before the work on updatable API keys elsewhere in this class.
I figured. I wasn't a fan of this name in other places as well. But we never bothered to change them ...
return builder.endObject(); | ||
return new ApiKeyDocBuilderWithNoopFlag( | ||
builder.endObject(), | ||
isNoop(currentApiKeyDoc, targetDocVersion, authentication, request, userRoles) | ||
); |
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 works. But I think it can be simplified a bit if:
- Perform
isNoop
check in the beginning of the method. So we can avoid creating and preparing the builder all together if no change. - Remove the
ApiKeyDocBuilderWithNoopFlag
record type and just returnnull
if there is no change or the actual builder if there are any changes.
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.
Performing the noop check at the beginning of the method is how I had this structured initially, so I'm happy to switch it back to that. My thinking around always building was that it makes the method more generic and gives it simpler semantics: it creates the object and tells the caller if it's a noop -- then it's up to the caller to decide what to do with it.
This is just to give context. I'm good with your suggestion 👍
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.
Thanks for the context. It helps. I think it will at least partially ease your concern if we rename this method to maybeBuildUpdatedDocument
to indicate caller needs to perform null check.
// Role descriptor corresponding to SecuritySettingsSource.TEST_ROLE_YML to ensure that these stay the same and | ||
// don't cause an update | ||
Set.of( | ||
new RoleDescriptor( | ||
TEST_ROLE, | ||
new String[] { "ALL" }, | ||
new RoleDescriptor.IndicesPrivileges[] { | ||
RoleDescriptor.IndicesPrivileges.builder() | ||
.indices("*") | ||
.allowRestrictedIndices(true) | ||
.privileges("ALL") | ||
.build() }, | ||
null | ||
) | ||
), |
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.
For this assumption to uphold for future changes, it would be better if we check that it indeed results into a noop update before this check. Otherwise, any future change to the test role will essentially make this test not doing anything.
final List<RoleDescriptor> currentLimitedByRoleDescriptorRoles = parseRoleDescriptorsBytes( | ||
request.getId(), | ||
apiKeyDoc.limitedByRoleDescriptorsBytes, | ||
RoleReference.ApiKeyRoleType.LIMITED_BY |
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 might be OK though the behaviour is not perfect.
The method parseRoleDescriptorsBytes
replaces old superuser role definition (7.x) with the new one (8.0). This means it can return a role descriptor that is different from what is actually stored. If we only test against the returned role descriptor, we may miss an update because the equality does not apply to the actual stored descriptor.
In practice this can only happen in a mix version cluster (8.4 + 7.17). If update skips for a superuser
limitedByRoleDescriptor. It may not be too big of a deal especially since the legacy descriptor will be handled correctly when loaded (at authentication time).
Alternatively, we can check whether the current doc has version 7.x and the returned a descriptor is a superuser
. If answers to both are true
, it is not a noop (because we will be updating the legacy superuser descriptor to the new one). This would also means after update, the key will lose the legacy superuser behaviour even when it is used on a 7.17 node. But that is probably OK (and even desired due to consistency). But this is still not always correct because if the key is updated for a second time, the check would suggest an update is needed but it is not true (because it was updated already in the first update).
It is definitely an edge case. I don't really have a strong opinion. But at least it worths some code level comment. What do you 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.
Discussed this on Slack:
For this PR, we will simply document the behavior as a comment. We will tweak the behavior in a follow up PR to call parseRoleDescriptorsBytes
such that it does not replace superuser role descriptors, effectively allowing legacy descriptors to be updated to 8.x descriptors.
@elasticmachine run elasticsearch-ci/part-2 |
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
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java
Outdated
Show resolved
Hide resolved
* upstream/master: (38 commits) Simplify map copying (elastic#88432) Make DiffableUtils.diff implementation agnostic (elastic#88403) Ingest: Start separating Metadata from IngestSourceAndMetadata (elastic#88401) Move runtime fields base scripts out of scripting fields api package. (elastic#88488) Enable TRACE Logging for test and increase timeout (elastic#88477) Mute ReactiveStorageIT#testScaleDuringSplitOrClone (elastic#88480) Track the count of failed invocations since last successful policy snapshot (elastic#88398) Avoid noisy exceptions on data nodes when aborting snapshots (elastic#88476) Fix ReactiveStorageDeciderServiceTests testNodeSizeForDataBelowLowWatermark (elastic#88452) INFO logging of snapshot restore and completion (elastic#88257) unmute test (elastic#88454) Updatable API keys - noop check (elastic#88346) Corrected an incomplete sentence. (elastic#86542) Use consistent shard map type in IndexService (elastic#88465) Stop registering TestGeoShapeFieldMapperPlugin in ESIntegTestCase (elastic#88460) TSDB: RollupShardIndexer logging improvements (elastic#88416) Audit API key ID when create or grant API keys (elastic#88456) Bound random negative size test in SearchSourceBuilderTests#testNegativeSizeErrors (elastic#88457) Updatable API keys - logging audit trail event (elastic#88276) Polish reworked LoggedExec task (elastic#88424) ... # Conflicts: # x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/v2/RollupShardIndexer.java
This PR adds a noop check for API key updates. If we detect a noop
update, i.e., an update that does not result in any changes to the
existing doc, we skip the index step and return
updated = false
inthe response.
This PR also extends test coverage around various corner cases.