mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
566 lines
32 KiB
PowerShell
566 lines
32 KiB
PowerShell
<#
|
||
.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 "======================================================================================================================================================================"
|
||
|