Last active
August 30, 2023 13:13
-
-
Save TeamDman/262a14bfaacb8918fc6e6983977b20f5 to your computer and use it in GitHub Desktop.
Find the owners of an Azure resource group by looking at assigned roles, tags, and the activity log
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env pwsh | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory = $true)][string]$ResourceGroup, | |
[Parameter(Mandatory = $false)][string]$Subscription | |
); | |
function Format-TableHighlight($data) { | |
$data | Format-Table @{ | |
Label = "Name" | |
Expression = | |
{ | |
$color = 0; | |
switch ($_.Type) { | |
"Group" { $color = 35; } | |
"ADObject" { $color = 33; } | |
"User" { $color = 32; } | |
default { $color = 32; } | |
} | |
$e = [char]27; | |
return "$e[${color}m$($_.Name)${e}[0m"; | |
} | |
}, Id | |
} | |
function Get-OwnersFromTags( | |
$tags, | |
$ownerTags = @( | |
"owner", | |
"email", | |
"contact" | |
) | |
) { | |
# Get iterable list of tag keys | |
$tagNames = $tags ` | |
| Get-Member -Type Properties ` | |
| Select-Object -ExpandProperty Name; | |
Write-Host "Found $($tagNames.Count) resource group tags"; | |
$owners = @(); | |
foreach ($key in $tagNames) { | |
$value = $tags.$key; | |
foreach ($pattern in $ownerTags) { | |
if ($key -Match $pattern) { | |
# Write-Host -ForegroundColor "Green" "Found owner key $key=$value"; | |
$owners += [PSCustomObject]@{ | |
Id = "tag - $key" | |
Name = $value | |
} | |
break; | |
} | |
} | |
} | |
return $owners; | |
} | |
function Get-OwnersFromActivityLogs($ResourceGroup) { | |
$owners = @(); | |
Write-Host -ForegroundColor "Blue" "Querying activity logs"; | |
$callers = az monitor activity-log list ` | |
--subscription $Subscription ` | |
--start-time (Get-Date).AddDays(-89).ToString("yyyy-MM-dd") ` | |
--end-time (Get-Date).ToString("yyyy-MM-dd") ` | |
--resource-group $ResourceGroup ` | |
--max-events 10000 ` | |
--select "caller" ` | |
--query "[].caller" ` | |
--output json ` | |
| ConvertFrom-Json ` | |
| Sort-Object -Unique ` | |
Write-Host "Found $($callers.Count) activity log entries"; | |
$guids = $callers | Where-Object { $_ -NotMatch "@" }; | |
$emails = $callers | Where-Object { $_ -Match "@" }; | |
if ($guids.Count -gt 0) { | |
Write-Host -ForegroundColor "Blue" "Querying AD objects"; | |
$payload = @{ ids = $guids } ` | |
| ConvertTo-Json -Compress ` | |
| ForEach-Object { $_ -Replace """", "\""" }; | |
$owners += az rest ` | |
--subscription $Subscription ` | |
--method POST ` | |
--url 'https://graph.microsoft.com/v1.0/directoryObjects/getByIds' ` | |
--headers 'Content-Type=application/json' ` | |
--body $payload ` | |
--query "value | [].{Id: join('', ['audit - ', id]), Name: displayName, Type: 'ADObject'}" ` | |
| ConvertFrom-Json; | |
} | |
if ($emails.Count -gt 0) { | |
Write-Host -ForegroundColor "Blue" "Querying AD users"; | |
$payload = $emails ` | |
| ForEach-Object { "userPrincipalName eq '$_'" } ` | |
| Join-String -Separator " or "; | |
$owners += az ad user list ` | |
--filter $payload ` | |
--query "[].{Id: join('', ['audit - ', userPrincipalName]), Name: mailNickname}" ` | |
| ConvertFrom-Json; | |
} | |
return $owners; | |
} | |
function Get-OwnersFromRbac($scope) { | |
$owners = @(); | |
Write-Host -ForegroundColor "Blue" "Querying RBAC roles on scope $scope"; | |
$assignments = az role assignment list ` | |
--subscription $Subscription ` | |
--all ` | |
--query "[? contains(scope, '$scope') ]" ` | |
--output json ` | |
| ConvertFrom-Json; | |
Write-Host "Found $($assignments.Count) role assignments"; | |
$owners += $assignments ` | |
| ForEach-Object { | |
$_.scope = $_.scope -replace $scope, "ResourceGroup"; | |
return [PSCustomObject]@{ | |
Id = "rbac - $($_.roleDefinitionName) - $($_.scope) - $($_.principalId)" | |
Name = $_.principalName | |
Type = $_.principalType | |
}; | |
}; | |
$groups = $assignments ` | |
| Where-Object { $_.principalType -eq "Group" } ` | |
| Select-Object -ExpandProperty principalName; | |
Write-Host "Found $($groups.Count) groups in role assignments"; | |
foreach ($groupName in $groups) { | |
Write-Host -ForegroundColor "Blue" "Querying members for $groupName"; | |
$groupMembers = az ad group member list ` | |
--group $groupName ` | |
--output json ` | |
| ConvertFrom-Json; | |
Write-Host -ForegroundColor "Blue" "Querying owners for $groupName"; | |
$groupOwners = az ad group owner list ` | |
--group $groupName ` | |
--output json ` | |
| ConvertFrom-Json; | |
$owners += $groupMembers ` | |
| ForEach-Object { | |
return [PSCustomObject]@{ | |
Id = "$groupName - member" | |
name = "$($_.displayName) - $($_.jobTitle)" | |
} | |
}; | |
$owners += $groupOwners ` | |
| ForEach-Object { | |
return [PSCustomObject]@{ | |
Id = "$groupName - owner" | |
name = "$($_.displayName) - $($_.jobTitle)" | |
} | |
}; | |
} | |
return $owners; | |
} | |
if ([string]::IsNullOrEmpty($Subscription)) { | |
$Subscription = az account list ` | |
--query "[? isDefault].name" ` | |
--output tsv; | |
} | |
Write-Host -ForegroundColor "Blue" "Using subscription $Subscription"; | |
Write-Host -ForegroundColor "Blue" "Querying group"; | |
$group = az group show ` | |
--subscription $Subscription ` | |
--resource-group $ResourceGroup ` | |
--output json ` | |
| ConvertFrom-Json; | |
if (!$?) { | |
Write-Error "Could not find group ""$ResourceGroup"""; | |
return; | |
} | |
$owners = @(); | |
$owners += Get-OwnersFromTags $group.tags; | |
$owners += Get-OwnersFromActivityLogs $ResourceGroup; | |
$owners += Get-OwnersFromRbac $group.id; | |
if ($owners.Count -gt 0) { | |
Format-TableHighlight $owners; | |
} | |
else { | |
Write-Error "Found no owners for $ResourceGroup :("; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment