<# .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 "======================================================================================================================================================================"