Skip to content

Commit

Permalink
update cmk script
Browse files Browse the repository at this point in the history
  • Loading branch information
Ron Friedner committed Jan 30, 2025
1 parent 9e6cbf9 commit e474822
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 92 deletions.
196 changes: 109 additions & 87 deletions Powershell scripts/Agentless Scanning CMK support/AddCmkPermissions.ps1
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
<#
.SYNOPSIS
This script iterate over all VMs in given subscriptions, flitering VMs with CMK (customer managed keys). It then shows all keyvaults connected to these VMs disks Disk Encryption Sets, and optionally
Sets permissions on this keyvaults to allow disk scanning app id to access them.
This script iterates over all VMs in specified subscriptions, identifying those with Customer Managed Keys (CMK).
It applies RBAC permissions at the **subscription level** by default but can also apply permissions at the **Key Vault level** if specified.
.PARAMETER Subscriptions
An array of Azure Subscription IDs.
.PARAMETER Apply
A switch parameter to control whether to apply Key Vault permissions.
.PARAMETER DryRun
A switch parameter to simulate the process without making changes.
.PARAMETER ApplyAtKeyVaultLevel
A switch parameter to apply permissions at the Key Vault level instead of the default subscription level.
.NOTES
- **Access Policies Key Vaults**: Subscription-level RBAC permissions do not apply. The script detects such cases and offers options to configure manually.
- **Migration to RBAC is recommended** for better security & manageability.
- Migration Guide: https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-migration
- RBAC vs. Access Policies: https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy
.EXAMPLE
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1", "Subscription2" -Apply $true
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1", "Subscription2" -DryRun
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1" -ApplyAtKeyVaultLevel
#>

param (
[Parameter(Mandatory=$true)]
[string[]]$Subscriptions,
[switch]$Apply
)

# Function to print debug information
function Print-DebugInfo {
param (
[string]$Message,
[object]$Data
)
Write-Host "DEBUG: $Message"
$Data | Format-Table -AutoSize
}
[switch]$DryRun,

function Green
{
process { Write-Host $_ -ForegroundColor Green }
}
[switch]$ApplyAtKeyVaultLevel
)

function Red
{
process { Write-Host $_ -ForegroundColor Red }
if (-not $PSBoundParameters.ContainsKey('DryRun')) {
$DryRun = $false
}

function Green { process { Write-Host $_ -ForegroundColor Green } }
function Red { process { Write-Host $_ -ForegroundColor Red } }

# Check if the user is logged in to Azure
$loggedIn = az account show --output none 2>&1

Expand All @@ -47,76 +47,98 @@ if ($LASTEXITCODE -ne 0) {
Exit 1
}

# Iterate over Subscriptions
# Microsoft Defender for Cloud Servers Scanner Resource Provider
$appId = '0c7668b5-3260-4ad0-9f53-34ed54fa19b2'

# Iterate over each subscription
foreach ($subscription in $Subscriptions) {
Write-Output "Processing subscription $subscription" | Green

# Step 3: Iterate over VMs and get all disk encryption sets
$vmsInSubscription = az vm list --subscription $subscription --query "[].{OsDiskDES:storageProfile.osDisk.managedDisk.diskEncryptionSet.id, DataDisksDES:storageProfile.dataDisks[].managedDisk.diskEncryptionSet.id}" --output json

# Step 4: Get a list of disk encryption set unique ids
# Step 1: Get Disk Encryption Sets (DES) from VMs
$vmsInSubscription = az vm list --subscription $subscription --query "[].{OsDiskDES:storageProfile.osDisk.managedDisk.diskEncryptionSet.id, DataDisksDES:storageProfile.dataDisks[].managedDisk.diskEncryptionSet.id}" --output json

# Step 2: Extract Unique DES IDs
$desOsIds = $vmsInSubscription | ConvertFrom-Json | ForEach-Object { $_.OsDiskDES } | Where-Object { $_ -ne $null }
$desDataIds = $vmsInSubscription | ConvertFrom-Json | ForEach-Object { $_.DataDisksDES } | Where-Object { $_ -ne $null }
$desIds = @($desOsIds + $desDataIds | Sort-Object -Unique)
Print-DebugInfo -Message "Disk encryption set IDs:" -Data $desIds
$desDataIds = $vmsInSubscription | ConvertFrom-Json | ForEach-Object { $_.DataDisksDES } | Where-Object { $_ -ne $null }
$desIds = @($desOsIds + $desDataIds | Sort-Object -Unique)

if ($desIds.count -eq 0) {
Write-Output "No disk encryption sets in subscription $Subscription" | Green
continue
}

# Step 5: Get a list of key vaults unique ids
$keyVaultIds = az disk-encryption-set show --ids @desIds --query "[].activeKey.sourceVault.id || activeKey.sourceVault.id" --output json
$keyVaultIds = $keyVaultIds | ConvertFrom-Json | Sort-Object -Unique
Print-DebugInfo -Message "Key vault IDs:" -Data $keyVaultIds

# Disk scanning Microsoft Entra app: "Microsoft Defender for Cloud Servers Scanner Resource Provider".
$appId = '0c7668b5-3260-4ad0-9f53-34ed54fa19b2'

if (-not $Apply) {
Write-Output "Key Vault permissions will not be applied. Use -apply to apply permissions." | Green
Write-Output "No disk encryption sets found in subscription $subscription" | Green
continue
}
else
{
Write-Output "Key Vault permissions will be applied. WARNING: keyvaults referenced by VMs disks disk encryption sets may reside in different subscriptions then the VMs subscriptions given as input to this script." | Green
}

# Iterate over the Key Vault IDs
foreach ($keyVaultId in $keyVaultIds) {
# Extract Key Vault name from the resource ID
$keyVaultName = ($keyVaultId -split '/')[-1]
$keyVaultSubscription = ($keyVaultId -split '/')[2]
Print-DebugInfo -Message "Processing Key Vault id $keyVaultId"

# Check if Key Vault is RBAC or non-RBAC
$keyVaultProperties = az keyvault show --subscription $subscription --name $keyVaultName --query "properties"--output json | ConvertFrom-Json
$keyVaultRbacEnabled = $keyVaultProperties.enableRbacAuthorization -eq $true
Print-DebugInfo -Message "Key Vault: $keyVaultName at subscription: $keyVaultSubscription enableRbacAuthorization: $keyVaultRbacEnabled"

if (-not $Apply) {
continue

if ($ApplyAtKeyVaultLevel) {
# Step 3: Get Key Vaults associated with DES
Write-Output "Applying permissions at the Key Vault level." | Green
$keyVaultIds = az disk-encryption-set show --ids @desIds --query "[].activeKey.sourceVault.id || activeKey.sourceVault.id" --output json
$keyVaultIds = $keyVaultIds | ConvertFrom-Json | Sort-Object -Unique

foreach ($keyVaultId in $keyVaultIds) {
$keyVaultName = ($keyVaultId -split '/')[-1]
$keyVaultSubscription = ($keyVaultId -split '/')[2]
Write-Output "Processing Key Vault: $keyVaultName in subscription: $keyVaultSubscription" | Green

# Check if the Key Vault is RBAC or Access Policy-based
$keyVaultProperties = az keyvault show --subscription $subscription --name $keyVaultName --query "properties" --output json | ConvertFrom-Json
$keyVaultRbacEnabled = $keyVaultProperties.enableRbacAuthorization -eq $true
Write-Output "Key Vault: $keyVaultName, RBAC Enabled: $keyVaultRbacEnabled" | Green

if ($DryRun) {
Write-Output "DryRun mode enabled. No changes will be made for Key Vault: $keyVaultName." | Green
} else {
if ($keyVaultRbacEnabled) {
Write-Output "Applying RBAC permissions for App ID '$appId' to Key Vault: $keyVaultName." | Green
az role assignment create --assignee $appId --role "Key Vault Crypto Service Encryption User" --scope $keyVaultId
} else {
Write-Output "Applying access policies for App ID '$appId' to Key Vault: $keyVaultName." | Green
az keyvault set-policy --subscription $subscription --name $keyVaultName --spn $appId --key-permissions get wrapKey unwrapKey
}
}
}
} else {
# Step 4: Apply RBAC permissions at the subscription level (default)
Write-Output "Applying RBAC permissions at the subscription level for App ID '$appId' in subscription $subscription." | Green

if ($DryRun) {
Write-Output "DryRun mode enabled. No changes will be made for subscription: $subscription." | Green
} else {
Write-Output "Applying RBAC permissions for App ID '$appId' at subscription scope." | Green
az role assignment create --assignee $appId --role "Key Vault Crypto Service Encryption User" --scope "/subscriptions/$subscription"
}

if ($Apply) {
$confirmation = Read-Host "Do you want to apply Key Vault permissions for '$keyVaultId'? (Y/N)"
if (-not ($confirmation -eq 'Y' -or $confirmation -eq 'y')) {
Write-Output "Key Vault permissions not applied." | Green
continue
}
}

if ($keyVaultRbacEnabled -eq $true) {
Write-Output "Key Vault '$keyVaultId' is RBAC-enabled. Adding 'Key Vault Crypto Service Encryption User' role for App ID '$appId' at keyvault scope: $keyVaultId" | Green

# Add "Key Vault Crypto Service Encryption User" role to the specified app ID
$res = az role assignment create --assignee $appId --role "Key Vault Crypto Service Encryption User" --scope $keyVaultId
} else {
Write-Output "Key Vault '$keyVaultId' is non-RBAC. Adding key get,wrap,unwrap permissions for App ID '$appId'." | Green

# Add permissions for key get, wrap, and unwrap to the specified app ID
$res = az keyvault set-policy --subscription $subscription --name $keyVaultName --spn $appId --key-permissions get wrapKey unwrapKey
}
}

# Step 5: Handle Access Policy Key Vaults (since RBAC does not apply to them)
$accessPolicyKVs = az disk-encryption-set show --ids @desIds --query "[?properties.activeKey.sourceVault.id && !properties.enableRbacAuthorization].properties.activeKey.sourceVault.id" --output json | ConvertFrom-Json | Sort-Object -Unique

if ($accessPolicyKVs.Count -gt 0) {
Write-Output "Found $( $accessPolicyKVs.Count ) Key Vault(s) using Access Policies. They need separate permission setup." | Red

if ($DryRun) {
Write-Output "DryRun mode enabled. No changes will be made for Access Policies Key Vaults." | Green
} else {
$response = Read-Host "Do you want to apply Key Vault permissions for access policy Key Vaults?`n
(A)ll - Apply permissions to all access policy Key Vaults`n
(O)ne-by-one - Ask for approval for each Key Vault`n
(N)o - Skip access policy Key Vaults"

if ($response -eq "A" -or $response -eq "a") {
foreach ($kvId in $accessPolicyKVs) {
Write-Output "Applying permissions to $kvId" | Green
az keyvault set-policy --subscription $subscription --name ($kvId -split '/')[-1] --spn $appId --key-permissions get wrapKey unwrapKey
}
} elseif ($response -eq "O" -or $response -eq "o") {
foreach ($kvId in $accessPolicyKVs) {
$confirm = Read-Host "Apply permissions to $kvId? (Y/N)"
if ($confirm -eq "Y" -or $confirm -eq "y") {
Write-Output "Applying permissions to $kvId" | Green
az keyvault set-policy --subscription $subscription --name ($kvId -split '/')[-1] --spn $appId --key-permissions get wrapKey unwrapKey
}
}
} else {
Write-Output "Skipping access policy Key Vaults." | Green
}
}
}
}
}

Write-Output "Done." | Green
Write-Output "Script execution complete." | Green
39 changes: 34 additions & 5 deletions Powershell scripts/Agentless Scanning CMK support/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,36 @@
# Add CMK (Customer managed Keys) related permissions for agentless scanning Entra App Id
# Add CMK Permissions for Agentless Scanning

This script iterate over all VMs in given subscriptions, flitering VMs with CMK (customer managed keys). It then shows all keyvaults connected to these VMs disks Disk Encryption Sets, and optionally
Sets permissions on this keyvaults to allow disk scanning app id to access them.
This script identifies and configures Key Vaults associated with Customer Managed Keys (CMKs) to ensure agentless scanning permissions are in place.

Usage Example:
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1", "Subscription2" -Apply $true
## Features

- Grants permissions at **subscription level** (default) or **Key Vault level**.
- Supports Key Vaults in different subscriptions than their disks.
- Detects **access policies** (legacy model) and advises migration to Azure RBAC.

## EXAMPLES

```powershell
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1", "Subscription2" -DryRun
.\AddCmkPermissions.ps1 -Subscriptions "Subscription1" -ApplyAtKeyVaultLevel
```

## SYNOPSIS
This script iterates over all VMs in specified subscriptions, identifying those with Customer Managed Keys (CMK). It applies RBAC permissions at the **subscription level** by default but can also apply permissions at the **Key Vault level** if specified.

## PARAMETERS

### Subscriptions
An array of Azure Subscription IDs.

### DryRun
A switch parameter to simulate the process without making changes.

### ApplyAtKeyVaultLevel
A switch parameter to apply permissions at the Key Vault level instead of the default subscription level.

## NOTES
- **Access Policies Key Vaults**: Subscription-level RBAC permissions do not apply. The script detects such cases and offers options to configure manually.
- **Migration to RBAC is recommended** for better security & manageability.
- Migration Guide: [https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-migration](https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-migration)
- RBAC vs. Access Policies: [https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy](https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy)

0 comments on commit e474822

Please sign in to comment.