Skip to content

Drop role cache entries when project is deleted #131908

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

Merged
merged 4 commits into from
Jul 30, 2025

Conversation

tvernum
Copy link
Contributor

@tvernum tvernum commented Jul 25, 2025

The CompositeRoleStore caches roles by project id, which includes an invalidation counter per project.

This change clears all entries from the cache and drops the invalidation counter when a project is removed from the cluster.

The `CompositeRoleStore` caches roles by project id, which includes an
invalidation counter per project.

This change clears all entries from the cache and drops the
invalidation counter when a project is removed from the cluster.
@tvernum tvernum requested a review from ywangd July 25, 2025 08:17
@tvernum tvernum added >non-issue :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC v9.2.0 labels Jul 25, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Security Meta label for security team label Jul 25, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-security (Team:Security)

/**
* Utility class to make it easy to run a block of code whenever a project is deleted (e.g. to cleanup cache entries)
*/
public class ProjectDeletedListener {
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 class doesn't do a lot, but it makes the code that uses it a lot clearer.
The 1 line in CompositeRolesStore becomes really obvious:

  new ProjectDeletedListener(this::removeProject).attach(clusterService);

Copy link
Member

Choose a reason for hiding this comment

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

It's useful if the class does not already listen for cluster state changes. I guess there are probably a number of such classes.

Copy link
Member

@ywangd ywangd left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -633,6 +638,12 @@ public void invalidateProject(ProjectId projectId) {
}
}

final void removeProject(ProjectId projectId) {
numInvalidation.remove(projectId);
Copy link
Member

Choose a reason for hiding this comment

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

There is a fix annotation for this field on line 116 which can now be removed.

Comment on lines 41 to 48
var cs = mock(ClusterService.class);

final AtomicReference<ClusterStateListener> csl = new AtomicReference<>();
doAnswer(inv -> {
assertThat(inv.getArguments(), arrayWithSize(1));
csl.set(inv.getArgument(0));
return null;
}).when(cs).addListener(any(ClusterStateListener.class));
Copy link
Member

Choose a reason for hiding this comment

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

Instead of mocking, we could create an actual cluster service with something like ClusterServiceUtils.createClusterService(new DeterministicTaskQueue().getThreadPool()) and ClusterServiceUtils.setState to apply new states. This is often preferrable to excercise the entire event loop for the listener.

/**
* Utility class to make it easy to run a block of code whenever a project is deleted (e.g. to cleanup cache entries)
*/
public class ProjectDeletedListener {
Copy link
Member

Choose a reason for hiding this comment

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

It's useful if the class does not already listen for cluster state changes. I guess there are probably a number of such classes.

Comment on lines +2300 to +2308
final ClusterService clusterService = mock(ClusterService.class);

final AtomicReference<ClusterStateListener> listener = new AtomicReference<>();
doAnswer(inv -> {
assertThat(inv.getArguments(), Matchers.arrayWithSize(1));
assertThat(listener.get(), nullValue());
listener.set(inv.getArgument(0));
return null;
}).when(clusterService).addListener(any(ClusterStateListener.class));
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

@ywangd
Copy link
Member

ywangd commented Jul 28, 2025

I didn't check it too closely. It seems possible that we can cache roles for non-existing project if the role can be found in files? CP should not route request with non-existing project-id to ES. But I am not entirely sure that's guaranteed during projects creation/deletion. No change is needed for this PR. Just writing down a thought came to me when reading this PR.

@tvernum tvernum enabled auto-merge (squash) July 30, 2025 05:28
@tvernum tvernum merged commit 8d3634a into elastic:main Jul 30, 2025
39 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
>non-issue :Security/Authorization Roles, Privileges, DLS/FLS, RBAC/ABAC Team:Security Meta label for security team v9.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants