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

Purge Key Vaults after deleting resource group #1894

Merged
8 commits merged into from
Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions eng/common/TestResources/Remove-TestResources.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ if (!$PSBoundParameters.ContainsKey('ErrorAction')) {
$ErrorActionPreference = 'Stop'
}

# Support actions to invoke on exit.
$exitActions = @({
if ($exitActions.Count -gt 1) {
Write-Verbose 'Running registered exit actions.'
}
})

trap {
# Like using try..finally in PowerShell, but without keeping track of more braces or tabbing content.
$exitActions.Invoke()
}

# Source helpers to purge resources.
. "$PSScriptRoot\..\scripts\Helpers\Resource-Helpers.ps1"

function Log($Message) {
Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message)
}
Expand All @@ -86,18 +101,6 @@ function Retry([scriptblock] $Action, [int] $Attempts = 5) {
}
}

# Support actions to invoke on exit.
$exitActions = @({
if ($exitActions.Count -gt 1) {
Write-Verbose 'Running registered exit actions.'
}
})

trap {
# Like using try..finally in PowerShell, but without keeping track of more braces or tabbing content.
$exitActions.Invoke()
}

if ($ProvisionerApplicationId) {
$null = Disable-AzContextAutosave -Scope Process

Expand Down Expand Up @@ -213,16 +216,23 @@ $verifyDeleteScript = {
}
}

# Get any resources that can be purged after the resource group is deleted coerced into a collection even if empty.
$purgeableResources = @(Get-PurgeableGroupResources $ResourceGroupName)

Log "Deleting resource group '$ResourceGroupName'"
if ($Force) {
if ($Force -and !$purgeableResources) {
Remove-AzResourceGroup -Name "$ResourceGroupName" -Force:$Force -AsJob
Write-Verbose "Running background job to delete resource group '$ResourceGroupName'"

Retry $verifyDeleteScript 3
Write-Verbose "Requested async deletion of resource group '$ResourceGroupName'"
} else {
# Don't swallow interactive confirmation when Force is false
Remove-AzResourceGroup -Name "$ResourceGroupName" -Force:$Force
}

# Now purge the resources that should have been deleted with the resource group.
Remove-PurgeableResources $purgeableResources

$exitActions.Invoke()

<#
Expand Down
99 changes: 99 additions & 0 deletions eng/common/scripts/Helpers/Resource-Helpers.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Add 'AzsdkResourceType' member to outputs since actual output types have changed over the years.

function Get-PurgeableGroupResources {
param (
[Parameter(Mandatory=$true, Position=0)]
[string] $ResourceGroupName
)

# Get any Key Vaults that will be deleted so they can be purged later if soft delete is enabled.
Get-AzKeyVault @PSBoundParameters | ForEach-Object {
# Enumerating vaults from a resource group does not return all properties we required.
Get-AzKeyVault -VaultName $_.VaultName | Where-Object { $_.EnableSoftDelete } `
| Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Key Vault' -PassThru
}

# Get any Managed HSMs in the resource group, for which soft delete cannot be disabled.
Get-AzKeyVaultManagedHsm @PSBoundParameters `
| Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Managed HSM' -PassThru
}

function Get-PurgeableResources {
$subscriptionId = (Get-AzContext).Subscription.Id

# Get deleted Key Vaults for the current subscription.
Get-AzKeyVault -InRemovedState `
| Add-Member -MemberType NoteProperty -Name AzsdkResourceType -Value 'Key Vault' -PassThru

# Get deleted Managed HSMs for the current subscription.
$response = Invoke-AzRestMethod -Method GET -Path "/subscriptions/$subscriptionId/providers/Microsoft.KeyVault/deletedManagedHSMs?api-version=2021-04-01-preview" -ErrorAction Ignore
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300 -and $response.Content) {
$content = $response.Content | ConvertFrom-Json
foreach ($r in $content.value) {
[pscustomobject] @{
AzsdkResourceType = 'Managed HSM'
Id = $r.
heaths marked this conversation as resolved.
Show resolved Hide resolved
Name = $r.name
Location = $r.properties.location
DeletionDate = $r.properties.deletionDate -as [DateTime]
ScheduledPurgeDate = $r.properties.scheduledPurgeDate -as [DateTime]
EnablePurgeProtection = $r.properties.purgeProtectionEnabled
}
}
}
}

function Remove-PurgeableResources {
param (
[Parameter(Position=0, ValueFromPipeline=$true)]
[psobject[]] $Resource
)

if (!$Resource) {
heaths marked this conversation as resolved.
Show resolved Hide resolved
return
}

$subscriptionId = (Get-AzContext).Subscription.Id

foreach ($r in $Resource) {
Log "Attempting to purge $($r.AzsdkResourceType) '$($r.VaultName)'"

switch ($r.AzsdkResourceType) {
'Key Vault' {
if ($r.EnablePurgeProtection) {
# We will try anyway but will ignore errors
Write-Warning "Key Vault '$($r.VaultName)' has purge protection enabled and may not be purged for $($r.SoftDeleteRetentionInDays) days"
}

Remove-AzKeyVault -VaultName $r.VaultName -Location $r.Location -InRemovedState -Force -ErrorAction Continue
}

'Managed HSM' {
if ($r.EnablePurgeProtection) {
# We will try anyway but will ignore errors
Write-Warning "Managed HSM '$($r.Name)' has purge protection enabled and may not be purged for $($r.SoftDeleteRetentionInDays) days"
}

$response = Invoke-AzRestMethod -Method POST -Path "/subscriptions/$subscriptionId/providers/Microsoft.KeyVault/locations/$($r.Location)/deletedManagedHSMs/$($r.Name)/purge?api-version=2021-04-01-preview" -ErrorAction Ignore
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 300) {
Write-Warning "Successfully requested that Managed HSM '$($r.Name)' be purged, but may take a few minutes before it is actually purged."
} elseif ($response.Content) {
$content = $response.Content | ConvertFrom-Json
if ($content.error) {
$err = $content.error
Write-Warning "Failed to deleted Managed HSM '$($r.Name)': ($($err.code)) $($err.message)"
}
}
}

default {
Write-Warning "Cannot purge resource type $($r.AzsdkResourceType). Add support to https://github.com/Azure/azure-sdk-tools/blob/main/eng/common/scripts/Helpers/Resource-Helpers.ps1."
}
}
}
}

# The Log function can be overridden by the sourcing script.
function Log($Message) {
Write-Host ('{0} - {1}' -f [DateTime]::Now.ToLongTimeString(), $Message)
}
21 changes: 19 additions & 2 deletions eng/scripts/live-test-resource-cleanup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ param (
)
eng/common/scripts/Import-AzModules.ps1

# Import resource management helpers and override its Log function.
. "$PSScriptRoot\..\common\scripts\Helpers\Resource-Helpers.ps1"

function Log($Message) {
heaths marked this conversation as resolved.
Show resolved Hide resolved
Write-Host $Message
}


Write-Verbose "Logging in"
$provisionerSecret = ConvertTo-SecureString -String $ProvisionerApplicationSecret -AsPlainText -Force
$provisionerCredential = [System.Management.Automation.PSCredential]::new($ProvisionerApplicationId, $provisionerSecret)
Expand All @@ -58,12 +66,21 @@ Write-Host "Count $($hasDeleteAfter.Count)"
$toDelete = $hasDeleteAfter.Where({ $deleteDate = ($_.Tags.DeleteAfter -as [DateTime]); (!$deleteDate -or $now -gt $deleteDate) })
Write-Host "Groups to delete: $($toDelete.Count)"

# Get purgeable resources already in a deleted state coerced into a collection even if empty.
$purgeableResources = @(Get-PurgeableResources)

foreach ($rg in $toDelete)
{
if ($Force -or $PSCmdlet.ShouldProcess("$($rg.ResourceGroupName) (UTC: $($rg.Tags.DeleteAfter))", "Delete Group"))
{
if ($Force -or $PSCmdlet.ShouldProcess("$($rg.ResourceGroupName) (UTC: $($rg.Tags.DeleteAfter))", "Delete Group")) {
# Add purgeable resources that will be deleted with the resource group to the collection.
$purgeableResources += Get-PurgeableGroupResources $rg.Name

Write-Verbose "Deleting group: $($rg.Name)"
Write-Verbose " tags $($rg.Tags | ConvertTo-Json -Compress)"
Write-Host ($rg | Remove-AzResourceGroup -Force -AsJob).Name
}
}

# Purge all the purgeable resources.
Write-Host "Deleting $($purgeableResources.Count) purgeable resources"
Remove-PurgeableResources $purgeableResources