Skip to content
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

[CORE-58] Cascade policy deletion #1564

Merged
merged 25 commits into from
Oct 31, 2024
Merged

Conversation

calypsomatic
Copy link
Contributor

@calypsomatic calypsomatic commented Oct 15, 2024

Ticket: https://broadworkbench.atlassian.net/browse/CORE-58

This PR now cascade deletes policies when deleting a resource. Instead of throwing an error that some policy members are in use like deleteResource previous did, it simply removes the problematic rows from the table.

I've demonstrated on my BEE that with this code, when a workspace has policies attached to it due to an imported TDR snapshot, it can be successfully deleted without any errors or orphaned policy references.

Why:

Users were unable to delete any workspace that had had a TDR snapshot with a policy imported into it, as those policies had entries in Sam's database that were not being deleted.


PR checklist

  • I've followed the instructions if I've made any changes to the API, especially if they're breaking changes
  • I've filled out the Security Risk Assessment (requires Broad Internal network access) and attached the result to the JIRA ticket

@calypsomatic calypsomatic changed the title try new api [CORE-58] Cascade policy deletion Oct 15, 2024
Copy link
Contributor

@davidangb davidangb left a comment

Choose a reason for hiding this comment

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

Some comments inline about separate APIs/functions vs adding to the existing ones; otherwise, looks like a good approach to me. I'd love a knowledgable Sam person to review the SQL queries but it seems like a good high-level approach.

Can you write tests for this?

@dvoet
Copy link
Collaborator

dvoet commented Oct 17, 2024

I think this is fine and should just happen all the time. No new api, not even an option on the existing api. Just do it.

One other change that needs to happen is that any groups that are modified need to be synchronized by calling cloudExtensions.onGroupUpdate.

So, I would update ResourceService.deleteResource directly to first figure out any groups that are changing and sync them instead of calling checkNoPoliciesInUse. Then I would modify accessPolicyDAO.deleteResource to include your 2 new delete statements in the same transaction as the other deletes.

@calypsomatic calypsomatic marked this pull request as ready for review October 21, 2024 19:45
@@ -441,21 +440,11 @@ class ResourceService(
// leave a tomb stone if the resource type does not allow reuse
leaveTombStone = !resourceTypes(resource.resourceTypeName).reuseIds
_ <- accessPolicyDAO.deleteResource(resource, leaveTombStone, samRequestContext)
_ <- cloudSyncPolicies(resource, samRequestContext)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The resource was deleted in the line above, the database calls in cloudSyncPolicies won't find anything. I think you need to get the list of groups that will be changed by having the resource removed before deleting the resource then sync them afterwards.

And something else I think I missed: PostgresGroupDAO.updateGroupUpdatedDateAndVersion needs to be called for each group updated.

Copy link
Contributor Author

@calypsomatic calypsomatic Oct 23, 2024

Choose a reason for hiding this comment

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

Just to make sure I understand what you're asking, I should do something like:

affectedPolicies <- accessPolicyDAO.findAffectedPolicies(resource, samRequestContext) //New method
_ <- accessPolicyDAO.deleteResource(resource, leaveTombStone, samRequestContext)
_ <- cloudSyncPolicies(affectedPolicies, samRequestContext) //Update this method to take in policies instead of finding them
_ <- updateGroupUpdatedDateAndVersion(affectedPolicies, samRequestContext)

Is that correct?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes but updateGroupUpdatedDateAndVersion should come before cloudSyncPolicies. The sync process keeps track of the last synchronized version and won't do anything if that version is the same as the current version. So the order of operations should be change the policy/group in the sam db, bump the version, cloud sync.

And here is yet another idea (sorry if I am jerking you around): instead of your new database queries to bulk delete entries, you can list the policies that need to be updated along with which policy is being removed. For each of those pairs (policy changing, policy removed), call removeSubjectFromPolicy (in the same class, ResourceService). That takes care of the database calls, syncing, everything. The code would look something like

affectedPolicies <- accessPolicyDAO.findAffectedPolicies(resource, samRequestContext) //New method
_ <- affectedPolicies.traverse { case (policyToUpdate, policyToRemove) => removeSubjectFromPolicy(policyToUpdate, policyToRemove, samRequestContext) }

where findAffectedPolicies (or maybe there is a better name) returns a IO[List[(FullyQualifiedPolicyId, FullyQualifiedPolicyId)]]

.map { rs =>
val resourceTypeName1 = rs.get[ResourceTypeName](rt.resultName.name)
val resourceId1 = rs.get[ResourceId](r.resultName.name)
val accessPolicyName1 = rs.get[AccessPolicyName](pg.resultName.name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

pg is an alias to the group table so its name will not be an access policy name

Comment on lines 962 to 967
val g = GroupTable.syntax("g")
val pg = GroupTable.syntax("pg")
val gm = GroupMemberTable.syntax("gm")
val p = PolicyTable.syntax("p")
val rt = ResourceTypeTable.syntax("rt")
val r = ResourceTable.syntax("r")
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suggest naming these something more readable and include whether they are representing rows from the container or member policy. Same with fullyQualifiedPolicyId1 vs fullyQualifiedPolicyId2. One is the container and one is the member. Naming them better will help keep things straight.

@calypsomatic calypsomatic requested a review from dvoet October 29, 2024 18:41

// remove from cloud first so a failure there does not leave sam in a bad state
_ <- cloudDeletePolicies(resource, samRequestContext)
_ <- deleteActionManagedIdentitiesForResource(resource, samRequestContext)

// leave a tomb stone if the resource type does not allow reuse
leaveTombStone = !resourceTypes(resource.resourceTypeName).reuseIds
affectedPolicies <- accessPolicyDAO.findAffectedPolicyGroups(resource, samRequestContext) // New method
_ <- affectedPolicies.traverse { case (policyToUpdate, policyToRemove) => removeSubjectFromPolicy(policyToUpdate, policyToRemove, samRequestContext) }
_ <- cloudSyncPolicies(affectedPolicies, samRequestContext) // TODO: use this on affected policies
Copy link
Contributor

Choose a reason for hiding this comment

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

if you are using removeSubjectFromPolicy, do you still need the call to cloudSyncPolicies? Does removeSubjectFromPolicy handle the syncing internally?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure, actually. Maybe @dvoet knows?

Copy link
Collaborator

Choose a reason for hiding this comment

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

you don't need to call cloudSyncPolicies

resourceId: FullyQualifiedResourceId,
samRequestContext: SamRequestContext
): IO[List[(FullyQualifiedPolicyId, FullyQualifiedPolicyId)]]

Copy link
Contributor

Choose a reason for hiding this comment

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

findPolicyGroupMemberhips? findPolicyGroupParticipation? maybe findPolicyGroupsInUse to reflect the method it is supplanting?

@@ -569,6 +569,7 @@ class PostgresAccessPolicyDAO(

override def deleteResource(resource: FullyQualifiedResourceId, leaveTombStone: Boolean, samRequestContext: SamRequestContext): IO[Unit] =
serializableWriteTransaction("deleteResource", samRequestContext) { implicit session =>
deletePolicyMembersFromGroups(resource)
Copy link
Contributor

Choose a reason for hiding this comment

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

is this still necessary here, given that ResourceService calls removeSubjectFromPolicy?

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 guess not!

Comment on lines 3606 to 3608
policyGroups should not be empty
policyGroups.head._1 shouldEqual parentPolicy.id
policyGroups.head._2 shouldEqual childPolicy.id
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you could also write this as:

policyGroups should contain theSameElementsAs List(parentPolicy.id, childPolicy.id)

optional; pick whatever approach is more consistent with Sam code. I do see theSameElementsAs used elsewhere in Sam.

Copy link
Collaborator

Choose a reason for hiding this comment

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

need an extra set of parens in there
policyGroups should contain theSameElementsAs List((parentPolicy.id, childPolicy.id))
and that 1 line can replace your 3

Copy link
Collaborator

@dvoet dvoet left a comment

Choose a reason for hiding this comment

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

I think this looks right. Approved assuming the cleanup David pointed and my test comments.

Comment on lines 3606 to 3608
policyGroups should not be empty
policyGroups.head._1 shouldEqual parentPolicy.id
policyGroups.head._2 shouldEqual childPolicy.id
Copy link
Collaborator

Choose a reason for hiding this comment

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

need an extra set of parens in there
policyGroups should contain theSameElementsAs List((parentPolicy.id, childPolicy.id))
and that 1 line can replace your 3

childResourceFullyQualifiedId.resourceTypeName,
childResourceFullyQualifiedId.resourceId,
Set.empty,
Set(childPolicy)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would add 2 more policies to childResource, one that is not a member of a parentResource policy and on that is a member of a different policy. Maybe even add another parent resource.

Copy link

Copy link
Contributor

@davidangb davidangb left a comment

Choose a reason for hiding this comment

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

thanks for sticking with this one!

@calypsomatic calypsomatic merged commit 58be30b into develop Oct 31, 2024
25 checks passed
@calypsomatic calypsomatic deleted the core-58-cascade-policy branch October 31, 2024 12:56
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.

3 participants