Skip to content

Commit

Permalink
Resource deletion - exemptions (#564)
Browse files Browse the repository at this point in the history
* InitialBaseUpdate

* policyExemptions - Deletion

* policyExemptions - Deletion Update

* policyExemptions - Deletion Update 2

* Update src/functions/Invoke-AzOpsPush.ps1

Co-authored-by: Johan Dahlbom <[email protected]>

* Exemption Clean

Co-authored-by: Johan Dahlbom <[email protected]>
  • Loading branch information
Jefajers and daltondhcp authored Feb 18, 2022
1 parent 773ae2d commit de9fad9
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 125 deletions.
104 changes: 32 additions & 72 deletions docs/wiki/ResourceDeletion.md
Original file line number Diff line number Diff line change
@@ -1,99 +1,59 @@
# AzOps Resource Deletion

- [Introduction](#Introduction)
- [Supported Action](#Supported-Action)
- [How to use](#How-to-use)
- [Integration with AzOps Accelerator](#Integration-with-AzOps-Accelerator)

## Introduction

### Introduction
**AzOps Resource Deletion** performs deletion of policyAssignments, policyExemptions and roleAssignments in Azure, based on `AzOps - Pull` generated templates at all Azure scope levels `(Management Group/Subscription/Resource Group)`.

The purpose of this wiki is to provide detailed information about **AzOps Resource Deletion**.
- For any other resource type **deletion** is **not** supported by AzOps at this time.

**AzOps Resource Deletion** performs deletion of role and policy assignments in Azure, based on `AzOps - Pull` generated templates at all Azure scope levels.
By removing a AzOps generated file representing an policyAssignment, policyExemption or a roleAssignment AzOps removes the corresponding resource in Azure.

**_Please Note_**

### Supported Actions

- Deleting custom or built-in roleAssignments: When `Invoke-AzOpsPull` runs, it fetches the existing environment which includes custom and built-in roleAssignments. By removing the assignment file, roleAssignments at all Azure scope levels `(Management Group/Subscription/Resource Group)` can be managed from the repository directly.

- Deleting custom or built-in policyAssignments: When `Invoke-AzOpsPull` runs, its fetches the existing environment which includes custom and built-in policyAssignments. By removing the assignment file, policyAssignments at all Azure scope levels `(Management Group/Subscription/Resource Group)` can be managed from the repository directly.

### How to use

Detailed steps:

1. Trigger the pull to fetch the fresh data of existing Azure environment. Navigate to Actions and run `AzOps - Pull`

![ResourceDeletion_workflow](./Media/ResourceDeletion/ResourceDeletion_workflow.PNG)
![ResourceDeletion_intial_Pull](./Media/ResourceDeletion/ResourceDeletion_intial_Pull.PNG)

2. It's recommended to capture the current stage either from `portal` or via any `script` to validate the behaviour after completion of the deletion.

![ResourceDeletion_RBAC_portal](./Media/ResourceDeletion/ResourceDeletion_RBAC_portal.PNG)
![ResourceDeletion_azpolicy_portal](./Media/ResourceDeletion/ResourceDeletion_azpolicy_portal.PNG)

3.Browse to the repository and to the `feature branch` and delete the Role or Policy assignment file or both which are required to be deleted.

![ResourceDeletion_RBAC_File](./Media/ResourceDeletion/ResourceDeletion_RBAC_File.PNG)
![ResourceDeletion_azpolicy_File](./Media/ResourceDeletion/ResourceDeletion_azpolicy_File.PNG)

4. Once file has been deleted from the branch, create pull request from `Feature Branch` to `Main Branch`.

![ResourceDeletion_Pull_Request_creation](./Media/ResourceDeletion/ResourceDeletion_Pull_Request_creation.PNG)
![ResourceDeletion_Pull_Request_status](./Media/ResourceDeletion/ResourceDeletion_Pull_Request_status.PNG)

5. Once Pull Requested has been created, it will trigger the `AzOps - Validate` pipeline to do initial check. Wait for the pipeline to complete.

![ResourceDeletion_azops_validate_pipeline](./Media/ResourceDeletion/ResourceDeletion_azops_validate_pipeline.PNG)

6. Now the `Approver` can review the pull request. It contains detailed information about which file to delete and pull request can be approved based on that.

![ResourceDeletion_azops_validate_pipeline](./Media/ResourceDeletion/ResourceDeletion_Pull_Request_review.PNG)
![ResourceDeletion_azops_validate_pipeline](./Media/ResourceDeletion/ResourceDeletion_Pull_Request_merge.PNG)
- SPN used for deletion/change action, requires the below actions in its role definition.

7. With the approval, `AzOps - Push` pipeline will get triggered to apply/implement the requested changes.
- For Azure Policy Assignment removal

![ResourceDeletion_azops_push_pipeline](./Media/ResourceDeletion/ResourceDeletion_azops_push_pipeline.PNG)
```bash
Microsoft.Authorization/policyAssignments/delete
OR
Microsoft.Authorization/policyAssignments/*
OR
Microsoft.Authorization/* OR * (For everything)
```

8. Now the changes can be validated via `Portal` or `Script`
- For Azure Policy Exemption removal

![ResourceDeletion_RBAC_portal1](./Media/ResourceDeletion/ResourceDeletion_RBAC_portal1.PNG)
![ResourceDeletion_azpolicy_portal1](./Media/ResourceDeletion/ResourceDeletion_azpolicy_portal1.PNG)
```bash
Microsoft.Authorization/policyExemptions/delete
OR
Microsoft.Authorization/policyExemptions/*
OR
Microsoft.Authorization/* OR * (For everything)
```

- For Azure Role Assignment removal

**_Please Note_**
```bash
Microsoft.Authorization/roleAssignments/delete
OR
Microsoft.Authorization/roleAssignments/*
OR
Microsoft.Authorization/* OR * (For everything)
```

- Resource Deletion is supported for templates generated by `AzOps - Pull` for resource type `roleAssignments` and `policyAssignments`.
- For any other resource type **deletion is not supported in AzOps at this time**.
- SPN used for deletion/change action, should have the below scope in its role definition.

- For Azure Policy assignment removal
```bash
Microsoft.Authorization/policyAssignments/delete
OR
Microsoft.Authorization/policyAssignments/*
OR
Microsoft.Authorization/* OR * (For everything)
```
- For Azure Role assignment removal
```bash
Microsoft.Authorization/roleAssignments/delete
OR
Microsoft.Authorization/roleAssignments/*
OR
Microsoft.Authorization/* OR * (For everything)
```

### Integration with AzOps Accelerator
## Integration with AzOps Accelerator

The [AzOps Accelerator pipelines](https://github.com/azure/azops-accelerator) (including `Git Hub Actions` & `Azure Pipelines`) incorporates the execution of resource deletion.

Conditional logic has been implemented to call `Invoke-AzOpsPush` with required change set in case of resource deletion operation, while existing logic without resource deletion remains same.

![ResourceDeletion_Pipeline_logic](./Media/ResourceDeletion/ResourceDeletion_pipelineupdate.PNG)

### How to Add AzOps Resource Deletion to existing AzOps - Push and Validate pipelines (applicable to implementations created prior to AzOps release v1.6.0)
## How to Add AzOps Resource Deletion to existing AzOps - Push and Validate pipelines (applicable to implementations created prior to AzOps release v1.6.0)

1. Update the `AzOps - Push` pipeline by copying content from the latest upstream [push.yml](https://github.com/Azure/AzOps-Accelerator/blob/main/.pipelines/push.yml) file into your existing file.
2. Update the `AzOps - Validate` pipeline by copying content from the latest upstream [validate.yml](https://github.com/Azure/AzOps-Accelerator/blob/main/.pipelines/validate.yml) file into your existing file.
4 changes: 3 additions & 1 deletion src/functions/Invoke-AzOpsPush.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Applies a change to Azure from the AzOps configuration.
.PARAMETER ChangeSet
Set of changes from the last execution that need to be applied.
.PARAMETER DeleteSetContents
Set of content from the deleted files in ChangeSet.
.PARAMETER StatePath
The root path to where the entire state is being built in.
.PARAMETER AzOpsMainTemplate
Expand Down Expand Up @@ -262,7 +264,7 @@
}

$templateContent = Get-Content $deletion | ConvertFrom-Json -AsHashtable
if (-not($templateContent.resources[0].type -eq "Microsoft.Authorization/roleAssignments" -or $templateContent.resources[0].type -eq "Microsoft.Authorization/policyAssignments")) {
if (-not($templateContent.resources[0].type -in "Microsoft.Authorization/policyAssignments","Microsoft.Authorization/policyExemptions","Microsoft.Authorization/roleAssignments")) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.SkipUnsupportedResource' -StringValues $deletion -Target $scopeObject
continue
}
Expand Down
74 changes: 38 additions & 36 deletions src/internal/functions/Remove-AzOpsDeployment.ps1
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
function Remove-AzOpsDeployment {
<#
.SYNOPSIS
Delete a Role Assignment / policy Assignment from azure.
Delete policyAssignments, policyExemptions and roleAssignments from azure.
.DESCRIPTION
Delete a Role Assignment / policy Assignment from azure.
Delete policyAssignments, policyExemptions and roleAssignments from azure.
.PARAMETER TemplateFilePath
Path where the ARM templates can be found.
.PARAMETER StatePath
Expand All @@ -28,10 +28,9 @@
process {
#Deployment Name
$fileItem = Get-Item -Path $TemplateFilePath
$deploymentName = $fileItem.BaseName -replace '\.json$' -replace ' ', '_'
$deploymentName = "AzOps-RemoveResource-$deploymentName"
$roleAssignmentPermissionCheck = $false
Write-PSFMessage -Level Important -String 'Remove-AzOpsDeployment.Processing' -StringValues $deploymentName, $TemplateFilePath -Target $TemplateFilePath
$removeJobName = $fileItem.BaseName -replace '\.json$' -replace ' ', '_'
$removeJobName = "AzOps-RemoveResource-$removeJobName"
Write-PSFMessage -Level Important -String 'Remove-AzOpsDeployment.Processing' -StringValues $removeJobName, $TemplateFilePath -Target $TemplateFilePath
#region Parse Content
$templateContent = Get-Content $TemplateFilePath | ConvertFrom-Json -AsHashtable
#endregion
Expand Down Expand Up @@ -64,65 +63,68 @@

#GetContext
$contextObjectId = (Get-AzOpsCurrentPrincipal).id
#region PolicyAssignment

#region policyAssignments
if ($scopeObject.Resource -eq "policyAssignments") {
#Validate
$policyAssignment = Get-AzPolicyAssignment -Id $scopeObject.scope -ErrorAction Continue -ErrorVariable resultsError
if ($policyAssignment) {
$validatePermissionList = @("Microsoft.Authorization/policyAssignments/delete", "Microsoft.Authorization/policyAssignments/*", "Microsoft.Authorization/*")
$roleAssignmentPermissionCheck = Get-AzOpsContextPermissionCheck -contextObjectId $contextObjectId -scope $policyAssignment.Properties.Scope -validatePermissionList $validatePermissionList
}
if (($resultsError -ne $null) -or (-not $policyAssignment)) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.RemovePolicyAssignment.NoPolicyAssignmentFound' -StringValues $scopeObject.Scope, $resultsError -Target $scopeObject
$results = '{0}: What if Operation Failed: Performing the operation "Deleting the policy assignment..." on target {1}.' -f $deploymentName, $scopeObject.scope
$results = '{0}: What if Operation Failed: Performing the operation "Deleting the policy assignment..." on target {1}.' -f $removeJobName, $scopeObject.scope
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
return
}
elseif ((-not $roleAssignmentPermissionCheck)) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.RemoveAssignment.MissingPermissionOnContext' -StringValues $contextObjectId, $scopeObject.Scope -Target $scopeObject
$results = '{0}: What if Operation Failed: Performing the operation "Deleting the policy assignment..." on target {1}.' -f $deploymentName, $scopeObject.scope
else {
$results = '{0}: What if Successful: Performing the operation "Deleting the policy assignment..." on target {1}.' -f $removeJobName, $scopeObject.scope
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfResults' -StringValues $results -Target $scopeObject
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfFile' -Target $scopeObject
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
}
#remove resource
if ($PSCmdlet.ShouldProcess("RemovePolicyAssignment?")) {
Remove-AzPolicyAssignment -Id $scopeObject.scope -ErrorAction Stop
}
else {
Write-PSFMessage -Level Verbose -String 'Remove-AzOpsDeployment.SkipDueToWhatIf'
}
}
#endregion policyAssignments
#region policyExemptions
if ($scopeObject.Resource -eq "policyExemptions") {
$policyExemption = Get-AzPolicyExemption -Id $scopeObject.scope -ErrorAction Continue -ErrorVariable resultsError
if (($resultsError -ne $null) -or (-not $policyExemption)) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.RemovePolicyExemption.NoPolicyExemptionFound' -StringValues $scopeObject.Scope, $resultsError -Target $scopeObject
$results = '{0}: What if Operation Failed: Performing the operation "Deleting the policy exemption..." on target {1}.' -f $removeJobName, $scopeObject.scope
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
return
}
else {
$results = '{0}: What if Successful: Performing the operation "Deleting the policy assignment..." on target {1}.' -f $deploymentName, $scopeObject.scope
$results = '{0}: What if Successful: Performing the operation "Deleting the policy exemption..." on target {1}.' -f $removeJobName, $scopeObject.scope
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfResults' -StringValues $results -Target $scopeObject
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfFile' -Target $scopeObject
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
}
#removal of resource
if ($PSCmdlet.ShouldProcess("RemovePolicyAssignment?")) {
Remove-AzPolicyAssignment -Id $scopeObject.scope -ErrorAction Stop
#remove resource
if ($PSCmdlet.ShouldProcess("RemovePolicyExemption?")) {
$null = Remove-AzPolicyExemption -Id $scopeObject.scope -Force -ErrorAction Stop
}
else {
Write-PSFMessage -Level Verbose -String 'Remove-AzOpsDeployment.SkipDueToWhatIf'
}
}
#endregion PolicyAssignment
#Region roleAssignments
#endregion policyExemptions
#region roleAssignments
if ($scopeObject.Resource -eq "roleAssignments") {
#Validate
$scopeOfRoleAssignment = $scopeObject.scope
$scopeOfRoleAssignment = $scopeOfRoleAssignment.Substring(0, $scopeOfRoleAssignment.LastIndexOf('/providers'))
$roleAssignment = Get-AzRoleAssignment -ObjectId $templateContent.resources[0].properties.PrincipalId -RoleDefinitionName $templateContent.resources[0].properties.RoleDefinitionName -scope $scopeOfRoleAssignment -ErrorAction Continue -ErrorVariable roleAssignmentError -WarningAction SilentlyContinue
if ($roleAssignment) {
$validatePermissionList = @("Microsoft.Authorization/roleAssignments/delete", "Microsoft.Authorization/roleAssignments/*", "Microsoft.Authorization/*")
$roleAssignmentPermissionCheck = Get-AzOpsContextPermissionCheck -contextObjectId $contextObjectId -scope $roleAssignment.Scope -validatePermissionList $validatePermissionList
}
if (($roleAssignmentError -ne $null) -or (-not $roleAssignment)) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.RemoveRoleAssignment.NoRoleAssignmentFound' -StringValues $scopeObject.Scope, $resultsError -Target $scopeObject
$results = '{0}: What if Failed: Performing the operation Removing role assignment for AD object {1} on scope {2} with role definition {3} on target {1}' -f $deploymentName, $templateContent.resources[0].properties.PrincipalId, $roleAssignment.Scope, $templateContent.resources[0].properties.RoleDefinitionName
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
return
}
elseif (-not $roleAssignmentPermissionCheck) {
Write-PSFMessage -Level Warning -String 'Remove-AzOpsDeployment.RemoveAssignment.MissingPermissionOnContext' -StringValues $contextObjectId, $scopeObject.Scope -Target $scopeObject
$results = '{0}: What if Failed: Performing the operation Removing role assignment for AD object {1} on scope {2} with role definition {3} on target {1}' -f $deploymentName, $templateContent.resources[0].properties.PrincipalId, $roleAssignment.Scope, $templateContent.resources[0].properties.RoleDefinitionName
$results = '{0}: What if Failed: Performing the operation Removing role assignment for AD object {1} on scope {2} with role definition {3} on target {1}' -f $removeJobName, $templateContent.resources[0].properties.PrincipalId, $roleAssignment.Scope, $templateContent.resources[0].properties.RoleDefinitionName
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
return
}
else {
$results = '{0}: What if Successful: Performing the operation Removing role assignment for AD object {1} on scope {2} with role definition {3} on target {1}' -f $deploymentName, $templateContent.resources[0].properties.PrincipalId, $roleAssignment.Scope, $templateContent.resources[0].properties.RoleDefinitionName
$results = '{0}: What if Successful: Performing the operation Removing role assignment for AD object {1} on scope {2} with role definition {3} on target {1}' -f $removeJobName, $templateContent.resources[0].properties.PrincipalId, $roleAssignment.Scope, $templateContent.resources[0].properties.RoleDefinitionName
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfResults' -StringValues $results -Target $scopeObject
Write-PSFMessage -Level Verbose -String 'Set-AzOpsWhatIfOutput.WhatIfFile' -Target $scopeObject
Set-AzOpsWhatIfOutput -TemplatePath $TemplateFilePath -Results $results -RemoveAzOpsFlag $true
Expand All @@ -135,6 +137,6 @@
Write-PSFMessage -Level Verbose -String 'Remove-AzOpsDeployment.SkipDueToWhatIf'
}
}
#endregion Roleassignments
#endregion roleassignments
}
}
Loading

0 comments on commit de9fad9

Please sign in to comment.