Files
Cloud-20Engineering/Powershell/Lists/Azure/KeyVaultNonRBACSecrets.ps1
Jurjen Ladenius a226ca97ac added documetation
2025-11-03 08:12:01 +01:00

566 lines
32 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
Inventories secrets in Azure Key Vaults using legacy access policies across all management groups and subscriptions.
.DESCRIPTION
This script performs a comprehensive inventory of secrets stored in Azure Key Vaults that use
traditional access policy-based authentication (not RBAC). It temporarily grants list permissions
to a specified user account, enumerates all secret names, and then removes the temporary access.
⚠️ SECURITY WARNING: This script temporarily modifies Key Vault access policies during execution.
It grants temporary secret list permissions to the specified user account and removes them afterwards.
The script is designed for:
- Security auditing and secret inventory management
- Compliance reporting for secret governance
- Migration planning from access policies to RBAC
- Secret lifecycle management and cleanup identification
- Risk assessment of stored secrets across the organization
- Regular security reviews and secret attestation processes
Key features:
- Multi-tenant hierarchical scanning (Management Groups → Subscriptions → Key Vaults)
- Access policy-based Key Vault filtering (excludes RBAC-only vaults)
- Temporary access policy modification for secret enumeration
- Automatic cleanup of temporary permissions
- Comprehensive secret name inventory (does not retrieve secret values)
- Resource tagging extraction for governance analysis
- Structured CSV export for security team analysis
IMPORTANT SECURITY CONSIDERATIONS:
- Script only retrieves secret names, not secret values
- Temporary access policies are automatically cleaned up
- Requires privileged permissions to modify Key Vault access policies
- Should be run from secure, controlled environments only
- All activities are logged in Azure Activity Log for audit trails
.PARAMETER None
This script does not accept command-line parameters. The user Object ID must be configured
within the script before execution.
.EXAMPLE
# Update the userObjectId variable with your Object ID first
$userObjectId = "your-user-object-id-here"
.\KeyVaultNonRBACSecrets.ps1
Runs the complete Key Vault secret inventory after configuring the user Object ID.
.EXAMPLE
# Get your current user Object ID
$currentUser = Get-AzADUser -Mail (Get-AzContext).Account.Id
Write-Host "Your Object ID: $($currentUser.Id)"
# Then update the script and run
.\KeyVaultNonRBACSecrets.ps1
Retrieves your Object ID for configuration and runs the inventory.
.EXAMPLE
# Connect with specific account first for security
Connect-AzAccount -Tenant "your-tenant-id"
.\KeyVaultNonRBACSecrets.ps1
Authenticates with specific tenant context before running the sensitive inventory operation.
.NOTES
Author: Cloud Engineering Team
Version: 1.0
⚠️ SECURITY NOTICE: This script requires and uses highly privileged permissions to temporarily
modify Key Vault access policies. Use with extreme caution and only in authorized security
audit scenarios.
Prerequisites:
- Azure PowerShell module (Az) must be installed
- User must be authenticated to Azure (Connect-AzAccount)
- User must have Key Vault Access Policy management permissions across target vaults
- User Object ID must be configured in the script before execution
Required Permissions:
- Management Group Reader permissions at the tenant root or target management groups
- Key Vault Contributor or Key Vault Access Policy Administrator on all target Key Vaults
- Reader access to all subscriptions containing Key Vaults
- Sufficient privileges to modify and remove Key Vault access policies
Security Context:
- Script temporarily grants 'List' permissions on secrets to the specified user
- Access policies are automatically removed after secret enumeration
- Only secret names are collected, not secret values
- All access policy modifications are logged in Azure Activity Log
- Failed cleanup operations may leave temporary permissions (manual removal required)
Output File:
- Format: "YYYY-MM-DD HHMM azure_key_vault_secrets.csv"
- Location: Current directory
- Content: Secret inventory with Key Vault context and governance tags
CSV Structure:
- Management Group information (ID, Name)
- Subscription details (ID, Name)
- Key Vault resource information (ID, Name, Location, Resource Group)
- Secret name (Secret_Key field)
- Resource tags (Team, Product, Environment, Data classification, Deployment, Creation date)
Performance Considerations:
- Processing time depends on the number of Key Vaults and secrets
- Access policy modifications add latency to each Key Vault operation
- Large Azure tenants may require extended execution time
- Network latency affects both enumeration and policy modification operations
Risk Mitigation:
- Script implements automatic cleanup of temporary permissions
- Only grants minimal required permissions (List secrets only)
- Does not retrieve or expose secret values
- Focuses only on access policy-based vaults (skips RBAC vaults)
- All operations are auditable through Azure Activity Log
Failure Scenarios:
- If script fails during execution, temporary access policies may remain
- Manual cleanup may be required using Remove-AzKeyVaultAccessPolicy
- Network interruptions may prevent proper cleanup
- Insufficient permissions may cause partial processing
Compliance and Governance:
- Use only for authorized security audits and compliance activities
- Document all executions for audit trail purposes
- Ensure proper approval for access policy modification activities
- Handle exported secret names with appropriate data classification
- Consider encryption for long-term storage of inventory results
.LINK
https://docs.microsoft.com/en-us/azure/key-vault/general/security-features
https://docs.microsoft.com/en-us/azure/key-vault/general/rbac-guide
https://docs.microsoft.com/en-us/azure/key-vault/general/logging
#>
# Ensure user is authenticated to Azure
# Uncomment the following line if authentication is needed:
# Connect-AzAccount
# ⚠️ SECURITY CONFIGURATION: Dynamically retrieve current user's Object ID for temporary access policy grants
# This user will receive temporary 'List' permissions on secrets during processing
Write-Host "Retrieving current user's Object ID for temporary access policy grants..."
try {
$currentContext = Get-AzContext -ErrorAction Stop
if (-not $currentContext) {
throw "No Azure context found. Please run Connect-AzAccount first."
}
# Get the current user's Object ID from Azure AD
$currentUser = Get-AzADUser -Mail $currentContext.Account.Id -ErrorAction Stop
[string] $userObjectId = $currentUser.Id
if ([string]::IsNullOrEmpty($userObjectId)) {
throw "Could not retrieve Object ID for current user: $($currentContext.Account.Id)"
}
Write-Host "✓ Successfully retrieved Object ID for user: $($currentContext.Account.Id)"
Write-Host " Object ID: $userObjectId"
Write-Host " Display Name: $($currentUser.DisplayName)"
} catch {
Write-Error "Failed to retrieve current user's Object ID: $($_.Exception.Message)"
Write-Error "Please ensure you are authenticated with Connect-AzAccount and have a valid user account"
Write-Error "Note: Service Principal authentication is not supported for this operation"
throw $_
}
# Define comprehensive resource information class for Key Vault secret inventory
class ResourceCheck {
# Management Group hierarchy information
[string] $ManagementGroupId = "" # Azure Management Group ID
[string] $ManagementGroupName = "" # Management Group display name
# Subscription context information
[string] $SubscriptionId = "" # Azure subscription ID
[string] $SubscriptionName = "" # Subscription display name
# Resource location and identification
[string] $ResourceGroup = "" # Resource group containing the Key Vault
[string] $ResourceId = "" # Full Azure resource ID of the Key Vault
[string] $Location = "" # Azure region where Key Vault is deployed
[string] $ResourceName = "" # Key Vault name
# Secret inventory information
[string] $Secret_Key = "" # Name of the secret (not the secret value)
# Resource governance tags for compliance and organization
[string] $Tag_Team = "" # Team responsible for the Key Vault
[string] $Tag_Product = "" # Product or service associated with the Key Vault
[string] $Tag_Environment = "" # Environment classification (dev/test/prod)
[string] $Tag_Data = "" # Data classification level
[string] $Tag_Deployment = "" # Deployment method or automation tag
[string] $Tag_CreatedOnDate = "" # Resource creation timestamp
}
Write-Host "======================================================================================================================================================================"
Write-Host "🔐 Azure Key Vault Secret Inventory (Access Policy-Based Vaults Only)"
Write-Host "======================================================================================================================================================================"
Write-Host "⚠️ SECURITY WARNING: This script temporarily modifies Key Vault access policies during execution"
Write-Host "Script execution started: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host ""
Write-Host "Security Configuration:"
Write-Host " User Object ID for temporary access: $userObjectId"
Write-Host " Permissions granted: List secrets only (temporary)"
Write-Host " Scope: Access policy-based Key Vaults only (RBAC vaults excluded)"
Write-Host " Data collected: Secret names only (values are NOT retrieved)"
Write-Host "======================================================================================================================================================================"
Write-Host ""
# Validate that we successfully retrieved a user Object ID
if ([string]::IsNullOrEmpty($userObjectId)) {
Write-Host "❌ CRITICAL ERROR: Could not retrieve current user's Object ID" -ForegroundColor Red
Write-Host ""
Write-Host "This could indicate:"
Write-Host " - You are not authenticated to Azure (run Connect-AzAccount)"
Write-Host " - You are authenticated with a Service Principal (user account required)"
Write-Host " - Your account is not found in Azure AD"
Write-Host " - Insufficient permissions to query Azure AD user information"
Write-Host ""
Write-Host "Please ensure you are authenticated with a valid user account and try again."
Write-Host "======================================================================================================================================================================"
throw "User Object ID retrieval failed"
}
# Generate timestamped filename for export
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date azure_key_vault_secrets.csv"
Write-Host "Export file: $fileName"
Write-Host ""
# Initialize processing counters and security tracking
$totalManagementGroups = 0
$totalSubscriptions = 0
$totalKeyVaults = 0
$totalSecrets = 0
$rbacOnlyVaults = 0
$processedResourceGroups = 0
$accessPolicyModifications = 0
$cleanupFailures = @()
Write-Host "Discovering Management Group hierarchy..."
try {
$managementGroups = Get-AzManagementGroup -ErrorAction Stop
$totalManagementGroups = $managementGroups.Count
Write-Host "✓ Found $totalManagementGroups Management Group(s) to process:"
foreach ($mg in $managementGroups) {
Write-Host " - $($mg.DisplayName) ($($mg.Name))"
}
} catch {
Write-Error "Failed to retrieve Management Groups. Please ensure you have appropriate permissions."
Write-Error "Required permissions: Management Group Reader at tenant root or target management groups"
throw $_
}
Write-Host ""
# Process each Management Group in the hierarchy
foreach ($managementGroup in $managementGroups) {
Write-Host "======================================================================================================================================================================"
Write-Host "Processing Management Group: $($managementGroup.DisplayName) ($($managementGroup.Name))"
Write-Host "======================================================================================================================================================================"
try {
# Get all active subscriptions within this management group
Write-Host "Discovering active subscriptions in management group..."
$subscriptions = Get-AzManagementGroupSubscription -Group $managementGroup.Name -ErrorAction Stop | Where-Object State -eq "Active"
if (-not $subscriptions -or $subscriptions.Count -eq 0) {
Write-Host " No active subscriptions found in management group '$($managementGroup.DisplayName)'"
continue
}
Write-Host "✓ Found $($subscriptions.Count) active subscription(s):"
foreach ($sub in $subscriptions) {
Write-Host " - $($sub.DisplayName)"
}
Write-Host ""
# Process each active subscription
foreach ($subscription in $subscriptions) {
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
# Extract subscription ID from the full resource path
$scope = $subscription.Id.Substring($subscription.Parent.Length, $subscription.Id.Length - $subscription.Parent.Length)
$subscriptionId = $scope.Replace("/subscriptions/", "")
Write-Host "Processing Subscription: $($subscription.DisplayName) ($subscriptionId)"
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
try {
# Set Azure context to the current subscription
Set-AzContext -SubscriptionId $subscriptionId -ErrorAction Stop | Out-Null
Write-Host "✓ Successfully connected to subscription context"
$totalSubscriptions++
# Get all resource groups in the current subscription
Write-Host "Discovering resource groups..."
$allResourceGroups = Get-AzResourceGroup -ErrorAction Stop
Write-Host "✓ Found $($allResourceGroups.Count) resource group(s) to scan"
# Initialize result collection for this subscription
[ResourceCheck[]]$Result = @()
$subscriptionKeyVaults = 0
$subscriptionSecrets = 0
$subscriptionRbacVaults = 0
$subscriptionAccessPolicyMods = 0
# Process each resource group to find Key Vaults
foreach ($group in $allResourceGroups) {
Write-Host " Scanning resource group: $($group.ResourceGroupName)"
$processedResourceGroups++
try {
# Get all Key Vaults in the current resource group
$allVaults = Get-AzKeyVault -ResourceGroupName $group.ResourceGroupName -ErrorAction Stop
if (-not $allVaults -or $allVaults.Count -eq 0) {
Write-Host " No Key Vaults found in resource group"
continue
}
Write-Host " ✓ Found $($allVaults.Count) Key Vault(s)"
# Process each Key Vault found
foreach ($vault in $allVaults) {
Write-Host " 🔐 Processing Key Vault: $($vault.VaultName)"
try {
# Get detailed Key Vault properties
$vaultWithAllProps = Get-AzKeyVault -ResourceGroupName $group.ResourceGroupName -Name $vault.VaultName -ErrorAction Stop
$totalKeyVaults++
$subscriptionKeyVaults++
# Check if vault uses traditional access policies (not RBAC-only)
if ($vaultWithAllProps.EnableRbacAuthorization -ne $true) {
Write-Host " 📋 Access Policy-based vault - processing secrets..."
# ⚠️ SECURITY CRITICAL: Temporarily grant List permissions to enumerate secrets
try {
Write-Host " 🔑 Granting temporary List permissions to user: $userObjectId"
Set-AzKeyVaultAccessPolicy -VaultName $vault.VaultName -ObjectId $userObjectId -PermissionsToSecrets "List" -ErrorAction Stop
$accessPolicyModifications++
$subscriptionAccessPolicyMods++
# Enumerate all secrets in the vault
Write-Host " 📝 Enumerating secrets..."
$secrets = Get-AzKeyVaultSecret -VaultName $vault.VaultName -ErrorAction Stop
if (-not $secrets -or $secrets.Count -eq 0) {
Write-Host " No secrets found in this vault"
} else {
Write-Host " ✓ Found $($secrets.Count) secret(s)"
# Process each secret found
foreach ($secret in $secrets) {
Write-Host " Secret: $($secret.Name)"
# Create comprehensive resource check entry
[ResourceCheck] $resourceCheck = [ResourceCheck]::new()
# Populate management group and subscription information
$resourceCheck.ManagementGroupId = $managementGroup.Id
$resourceCheck.ManagementGroupName = $managementGroup.DisplayName
$resourceCheck.SubscriptionId = $subscription.Id
$resourceCheck.SubscriptionName = $subscription.DisplayName
# Populate Key Vault resource information
$resourceCheck.ResourceGroup = $vaultWithAllProps.ResourceGroupName
$resourceCheck.ResourceId = $vaultWithAllProps.ResourceId
$resourceCheck.Location = $vaultWithAllProps.Location
$resourceCheck.ResourceName = $vaultWithAllProps.VaultName
# Populate secret information (name only, not value)
$resourceCheck.Secret_Key = $secret.Name
# Extract resource tags for governance analysis
$resourceCheck.Tag_Team = $vaultWithAllProps.Tags.team
$resourceCheck.Tag_Product = $vaultWithAllProps.Tags.product
$resourceCheck.Tag_Environment = $vaultWithAllProps.Tags.environment
$resourceCheck.Tag_Data = $vaultWithAllProps.Tags.data
$resourceCheck.Tag_CreatedOnDate = $vaultWithAllProps.Tags.CreatedOnDate
$resourceCheck.Tag_Deployment = $vaultWithAllProps.Tags.drp_deployment
# Add to results collection
$Result += $resourceCheck
$totalSecrets++
$subscriptionSecrets++
}
}
# ⚠️ SECURITY CRITICAL: Remove temporary permissions immediately
Write-Host " 🧹 Removing temporary permissions..."
try {
Remove-AzKeyVaultAccessPolicy -VaultName $vault.VaultName -ObjectId $userObjectId -ErrorAction Stop
Write-Host " ✓ Successfully removed temporary permissions"
} catch {
Write-Host " ❌ CLEANUP FAILURE: Could not remove temporary permissions!" -ForegroundColor Red
$cleanupFailures += @{
VaultName = $vault.VaultName
ResourceGroup = $group.ResourceGroupName
SubscriptionId = $subscriptionId
Error = $_.Exception.Message
}
Write-Host " ⚠️ Manual cleanup required: Remove-AzKeyVaultAccessPolicy -VaultName '$($vault.VaultName)' -ObjectId '$userObjectId'" -ForegroundColor Yellow
}
} catch {
Write-Host " ❌ Error accessing vault secrets: $($_.Exception.Message)" -ForegroundColor Red
Write-Host " This may indicate insufficient permissions or vault access restrictions"
# Ensure cleanup attempt even on failure
try {
Remove-AzKeyVaultAccessPolicy -VaultName $vault.VaultName -ObjectId $userObjectId -ErrorAction SilentlyContinue
} catch {
# Silent cleanup attempt
}
}
} else {
Write-Host " 🔐 RBAC-only vault (skipped): $($vault.VaultName)" -ForegroundColor Blue
$rbacOnlyVaults++
$subscriptionRbacVaults++
}
} catch {
Write-Host " ❌ Error processing vault '$($vault.VaultName)': $($_.Exception.Message)" -ForegroundColor Red
}
}
} catch {
Write-Host " ❌ Error scanning resource group '$($group.ResourceGroupName)': $($_.Exception.Message)" -ForegroundColor Red
}
}
Write-Host ""
Write-Host "Subscription Summary:"
Write-Host " Key Vaults found: $subscriptionKeyVaults"
Write-Host " Secrets inventoried: $subscriptionSecrets"
Write-Host " Access policy modifications: $subscriptionAccessPolicyMods"
Write-Host " RBAC-only vaults (skipped): $subscriptionRbacVaults"
if ($cleanupFailures.Count -gt 0) {
Write-Host " ⚠️ Cleanup failures: $($cleanupFailures.Count)" -ForegroundColor Yellow
}
Write-Host ""
# Export subscription results to CSV file
if ($Result.Count -gt 0) {
Write-Host "Exporting $($Result.Count) secret entries to CSV..."
try {
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation -ErrorAction Stop
Write-Host "✓ Successfully exported subscription data"
} catch {
Write-Error "Failed to export data for subscription '$($subscription.DisplayName)': $($_.Exception.Message)"
}
} else {
Write-Host " No secret data to export from this subscription"
}
} catch {
Write-Host "❌ Error processing subscription '$($subscription.DisplayName)': $($_.Exception.Message)" -ForegroundColor Red
Write-Host " Please verify subscription access and permissions" -ForegroundColor Red
}
Write-Host ""
}
} catch {
Write-Host "❌ Error processing management group '$($managementGroup.DisplayName)': $($_.Exception.Message)" -ForegroundColor Red
Write-Host " Please verify management group access and permissions" -ForegroundColor Red
}
}
Write-Host ""
Write-Host "======================================================================================================================================================================"
Write-Host "🔐 Key Vault Secret Inventory Completed"
Write-Host "======================================================================================================================================================================"
Write-Host "Execution completed: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host ""
# Display comprehensive execution statistics
Write-Host "Processing Statistics:"
Write-Host " Management Groups processed: $totalManagementGroups"
Write-Host " Subscriptions processed: $totalSubscriptions"
Write-Host " Resource Groups scanned: $processedResourceGroups"
Write-Host " Total Key Vaults found: $totalKeyVaults"
Write-Host " Secrets inventoried: $totalSecrets"
Write-Host " Access policy modifications: $accessPolicyModifications"
Write-Host " RBAC-only vaults (skipped): $rbacOnlyVaults"
Write-Host ""
# Critical security status check
if ($cleanupFailures.Count -gt 0) {
Write-Host "🚨 CRITICAL SECURITY ALERT: MANUAL CLEANUP REQUIRED" -ForegroundColor Red
Write-Host "======================================================================================================================================================================"
Write-Host "The following Key Vaults still have temporary permissions that need manual removal:" -ForegroundColor Red
Write-Host ""
foreach ($failure in $cleanupFailures) {
Write-Host " Vault: $($failure.VaultName)" -ForegroundColor Red
Write-Host " Resource Group: $($failure.ResourceGroup)" -ForegroundColor Red
Write-Host " Subscription: $($failure.SubscriptionId)" -ForegroundColor Red
Write-Host " Cleanup Command: Remove-AzKeyVaultAccessPolicy -VaultName '$($failure.VaultName)' -ObjectId '$userObjectId'" -ForegroundColor Yellow
Write-Host " Error: $($failure.Error)" -ForegroundColor Red
Write-Host ""
}
Write-Host "⚠️ Please run the cleanup commands above to remove temporary permissions immediately!" -ForegroundColor Yellow
Write-Host "======================================================================================================================================================================"
} else {
Write-Host "✅ Security Status: All temporary access policies were successfully removed"
}
Write-Host ""
# Analyze and display key findings
if ($totalSecrets -gt 0) {
Write-Host "✓ Successfully exported Key Vault secret inventory to: $fileName"
Write-Host ""
Write-Host "Security Analysis Insights:"
if ($rbacOnlyVaults -gt 0) {
Write-Host "$rbacOnlyVaults Key Vault(s) using modern RBAC authentication"
}
$legacyVaults = $totalKeyVaults - $rbacOnlyVaults
if ($legacyVaults -gt 0) {
Write-Host "$legacyVaults Key Vault(s) still using legacy access policies" -ForegroundColor Yellow
Write-Host " Consider migrating to Azure RBAC for improved security and management"
}
Write-Host ""
Write-Host "Output File Information:"
if (Test-Path $fileName) {
Write-Host " File Path: $fileName"
Write-Host " File Size: $([Math]::Round((Get-Item $fileName).Length / 1KB, 2)) KB"
Write-Host " Records Exported: $totalSecrets"
}
} else {
Write-Host " No Key Vault secrets found across all processed subscriptions"
Write-Host " This could indicate:"
Write-Host " - All Key Vaults are using RBAC-only authentication"
Write-Host " - No secrets exist in the scanned Key Vaults"
Write-Host " - Permission issues preventing access to Key Vault contents"
}
Write-Host ""
Write-Host "Security Recommendations:"
Write-Host " - Review exported secret inventory for unused or expired secrets"
Write-Host " - Implement secret rotation policies for all identified secrets"
Write-Host " - Consider implementing Azure RBAC for new Key Vaults"
Write-Host " - Plan migration from access policies to RBAC for existing vaults"
Write-Host " - Implement regular secret lifecycle management and cleanup"
Write-Host " - Ensure proper governance tags are applied to all Key Vaults"
Write-Host ""
Write-Host "Compliance and Security Notes:"
Write-Host " - This inventory contains sensitive secret name information"
Write-Host " - Handle output file with appropriate data classification controls"
Write-Host " - All access policy modifications are logged in Azure Activity Log"
Write-Host " - Consider encryption for long-term storage of inventory results"
Write-Host " - Schedule regular execution for continuous secret governance"
Write-Host " - Ensure manual cleanup is performed if cleanup failures occurred"
Write-Host ""
Write-Host "Audit Trail:"
Write-Host " - All temporary access policy changes are logged in Azure Activity Log"
Write-Host " - Search for 'Microsoft.KeyVault/vaults/accessPolicies/write' operations"
Write-Host " - Filter by Object ID: $userObjectId"
Write-Host " - Execution timeframe: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
Write-Host "======================================================================================================================================================================"