Private/Read-AACKeyVaultObject.ps1
|
function Read-AACKeyVaultObject { <# .SYNOPSIS Lists each Key Vault's secrets, keys and certificates - their metadata only, never a value - from the vault's data plane. .DESCRIPTION GET {vaultUri}secrets, keys and certificates (api-version 7.5), following nextLink, with a token for https://vault.azure.net from the Connect-AAC sign-in, through Invoke-AACHttpBatch. Secrets that back a certificate (managed) are left out: the certificate is listed. Needs Key Vault Reader (RBAC vaults) or a List permission in an access policy. A vault that refuses is reported, not fatal: Not reachable the vault's firewall or private endpoint blocks this machine (ForbiddenByFirewall) Denied no data-plane permission -Vault: @(@{ Id; Name; Uri }). Returns @{ Items = @(@{ VaultId; Kind; Name; Enabled; Expires (UTC or $null) }); Access = @{ vault id = 'Read' | 'Not reachable' | 'Denied' | 'Failed: ...' } }. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [object[]] $Vault, [ValidateRange(1, 32)] [int] $ThrottleLimit = 8 ) $items = [System.Collections.Generic.List[object]]::new() $byKey = @{} $requests = @(foreach ($v in $Vault) { $base = ([string]$v.Uri).TrimEnd('/') foreach ($kind in 'secrets', 'keys', 'certificates') { $key = "$($v.Id)|$kind" $byKey[$key] = $v @{ Key = $key; Body = $null; Uri = "$base/$($kind)?api-version=7.5&maxresults=25" } } }) $label = @{ secrets = 'Secret'; keys = 'Key'; certificates = 'Certificate' } $onResponse = { param($Key, [string] $Content) $page = ConvertFrom-Json -InputObject $Content -AsHashtable -Depth 20 $vaultId, $kind = $Key -split '\|' foreach ($o in @($page['value'])) { if ($null -eq $o -or ($kind -eq 'secrets' -and $o['managed'] -eq $true)) { continue } $attributes = $o['attributes'] $expires = if ($attributes -and $attributes['exp']) { [DateTimeOffset]::FromUnixTimeSeconds([long]$attributes['exp']).UtcDateTime } else { $null } $items.Add(@{ VaultId = $vaultId; Kind = $label[$kind]; Name = (([string]$(if ($o['id']) { $o['id'] } else { $o['kid'] })) -split '/')[-1]; Enabled = -not ($attributes -and $attributes['enabled'] -eq $false); Expires = $expires }) } if ($page['nextLink']) { @{ Uri = [string]$page['nextLink'] } } } $failures = if ($requests.Count) { Invoke-AACHttpBatch -Request $requests -OnResponse $onResponse -Resource 'https://vault.azure.net' -ThrottleLimit $ThrottleLimit } else { @{} } $access = @{} foreach ($v in $Vault) { $reasons = @(foreach ($kind in 'secrets', 'keys', 'certificates') { $f = $failures["$($v.Id)|$kind"]; if ($f) { [string]$f } }) $access[$v.Id] = if (-not $reasons.Count) { 'Read' } elseif (@($reasons | Where-Object { $_ -match '(?i)firewall|client address|private (link|endpoint)' }).Count) { 'Not reachable' } elseif (@($reasons | Where-Object { $_ -match '(?i)forbidden|403|401|does not have|not authorized|unauthorized' }).Count) { 'Denied' } else { "Failed: $($reasons[0])" } } @{ Items = $items.ToArray(); Access = $access } } |