Skip to content

Delete role assignments when locally-managed users / groups are deleted - #11713

Open
charliepark wants to merge 10 commits into
mainfrom
clean-up-role-assignments
Open

charliepark wants to merge 10 commits into
mainfrom
clean-up-role-assignments

Conversation

@charliepark

@charliepark charliepark commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

In working on a console PR (oxidecomputer/console#3211) I discovered that #8605 was still open. (#9276 handled deletion of role assignments for SCIM-managed users / groups, but not for local ones). This PR adds a method that handles the deletion of role assignments for locally-managed users and groups, and calls it when the user / group is deleted. It also deletes silo-scoped role assignments when a silo is deleted.

Note: there is no method to delete groups locally, but deleting a silo without addressing the roles would leave some orphaned rows, so this code cleans up the roles during the silo deletion process.

"But wait!", I hear you say, "what about the orphaned rows from users that have already been deleted from the silo? Isn't that what prompted #8605 in the first place?" Good question. That is outside the scope of this PR, but the robot suggests there's a migration from before that covers similar territory: schema/crdb/cleanup-orphaned-subnet-pool-silo-links (version 248):

SET LOCAL disallow_full_table_scans = off;

DELETE FROM omicron.public.subnet_pool_silo_link
WHERE subnet_pool_id NOT IN (
    SELECT id FROM omicron.public.subnet_pool WHERE time_deleted IS NULL
);

So that's something we can talk through separately.

Unrelated, but I also came across some old uncalled code in silo_group_delete; this PR removes that method as well.

#8605 will still be open after this merges. We will need to clean up the leftover rows, per the note above, to fully close that issue.

@charliepark charliepark changed the title Clean up role assignments Delete role assignments when locally-managed users / groups are deleted Nov 5, 2026
@charliepark

Copy link
Copy Markdown
Contributor Author

@claude review

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I reviewed this PR and found no bugs. Because it changes how authorization data (role assignments) is deleted across project, user and silo deletion, a human look is still worthwhile.

What was reviewed:

  • The new role_assignment_delete_by_identity_on_connection and role_assignment_delete_by_resource_on_connection helpers in role.rs, and how project.rs, silo_user.rs, scim_provider_store.rs and the silo delete transaction call them.
  • The bulk deletes outside the transaction in silo.rs. They run before users and groups are soft-deleted, and their subquery filters (silo_id, time_deleted IS NULL, user_provision_type) match the partial indexes on silo_user and silo_group. The identity-side delete can use lookup_role_assignment_by_identity_id.
  • Removing silo_group_delete, which nothing called, and the imports only it used.
Extended reasoning...

The change touches role assignment cleanup in six datastore files and adds a schema join allowance. It also adds integration tests for project, silo, user and group deletion. The surface it touches is authorization data: it deletes rows that grant permissions on silos and projects. No query was found that deletes too much or too little, and the indexes support the new subqueries. It is deferred rather than approved because it is a non-trivial change to permission-related data paths, and the silo bulk deletes run outside the transaction.

@charliepark
charliepark marked this pull request as ready for review November 12, 2026 00:03
@david-crespo

Copy link
Copy Markdown
Contributor

I had some questions about what happens when you do what actions in different kinds of silos.

Action Local (LocalOnly) SAML + JIT (SamlJit) SAML + SCIM (SamlScim)
User delete Fixed. silo_user_delete now deletes the user's rows from the fleet, silo, and project policies. Previously left orphaned rows in all of them. N/A (no API to delete JIT users; SAML login only creates users). Already worked (#9276). Refactored here to use the shared helper on DELETE /scim/v2/Users/{id}.
Group delete N/A (local silos have no groups; removes dead silo_group_delete). N/A (SAML login only creates groups, never deletes them, and there is no group-delete API). Already worked (#9276). Refactored here to use the shared helper on DELETE /scim/v2/Groups/{id}.
Group membership change N/A (no groups). Unchanged. silo_group_membership is replaced on each SAML login; policy assignments belong to the group, not memberships. Unchanged. Updated via SCIM PUT/PATCH /scim/v2/Groups/{id}.
Policy delete / clear N/A as DELETE (no policy-delete endpoint). Unchanged via PUT .../policy, which replaces all non-builtin rows in the fleet, silo, or project policy (including with []). Same as Local. Same as Local.
Project delete Fixed. project_delete now deletes all rows in the project policy inside the delete transaction. DB cleanup only, since a deleted project's policy already 404s. Fixed (same as Local). Fixed (same as Local).
Silo delete (silo policy) Fixed. silo_delete now deletes all rows in the silo policy inside the delete transaction, including roles assigned to users or groups from other silos. DB cleanup only, since a deleted silo's policy already 404s. Fixed (same as Local). Fixed (same as Local).
Silo delete (other policies) Fixed. Before soft-deleting silo_user, silo_delete removes the silo's live users from the fleet policy and from any other silos' policies. Fixed. Removes the deleted silo's JIT users and JIT groups from the fleet policy and from any other silos' policies. Fixed. Deleting a SCIM silo does not run individual SCIM deletes, so remaining SCIM users and groups are now removed from the fleet policy and from any other silos' policies.
Fleet delete N/A (fleet cannot be deleted). N/A N/A
Existing orphaned rows Not touched here (even if the silo is later deleted, since silo_delete filters time_deleted IS NULL). Left for a migration in #8605. Not touched here for silos deleted before this PR whose JIT users or groups had roles in the fleet policy or another silo's policy. Left for #8605. Not touched here for SCIM silos deleted before this PR (or SCIM users/groups deleted before #9276). Left for #8605.

pub(super) async fn role_assignment_delete_by_identity_on_connection(
&self,
conn: &async_bb8_diesel::Connection<DbConnection>,
identity_type: IdentityType,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could drop IdentityType here because there is only ever going to be one type for the ID anyway. Passing the type at the callsite feels kind of like a safeguard, but it isn't really. It would just do nothing if it was the wrong type, and only a test expecting the assignment to go away (which you have) would catch the problem.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants