Created
April 15, 2026 10:58
-
-
Save kell05/1d9eb119eaed96dd3c5ba1fe487e7294 to your computer and use it in GitHub Desktop.
Compare rbac between two users
This file contains hidden or 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 bash | |
| # compare_azure_permissions.sh | |
| # Usage: ./compare_azure_permissions.sh <your-email> <colleague-email> [subscription-id] | |
| # | |
| # Shows what your colleague has that you don't: | |
| # - Direct Azure RBAC role assignments | |
| # - Entra group memberships | |
| # - Effective roles (via groups) | |
| # | |
| # Requires: az login already done, and sufficient Entra read permissions | |
| # (User.Read.All or Directory.Read.All, or both users in same tenant) | |
| set -euo pipefail | |
| MY_EMAIL="${1:-}" | |
| COLLEAGUE_EMAIL="${2:-}" | |
| SUBSCRIPTION="${3:-}" | |
| usage() { | |
| echo "Usage: $0 <your-email> <colleague-email> [subscription-id]" | |
| echo "" | |
| echo " If subscription-id is omitted, uses the current default subscription." | |
| exit 1 | |
| } | |
| [[ -z "$MY_EMAIL" || -z "$COLLEAGUE_EMAIL" ]] && usage | |
| # ── helpers ────────────────────────────────────────────────────────────────── | |
| header() { echo ""; echo "════════════════════════════════════════════════"; echo " $*"; echo "════════════════════════════════════════════════"; } | |
| section() { echo ""; echo "── $* ──────────────────────────────────────"; } | |
| resolve_upn() { | |
| local email="$1" | |
| az ad user show --id "$email" --query "userPrincipalName" -o tsv 2>/dev/null \ | |
| || { echo "ERROR: Could not resolve user '$email'. Check the UPN or your Entra read permissions." >&2; exit 1; } | |
| } | |
| get_object_id() { | |
| az ad user show --id "$1" --query "id" -o tsv 2>/dev/null | |
| } | |
| get_display_name() { | |
| az ad user show --id "$1" --query "displayName" -o tsv 2>/dev/null | |
| } | |
| # Get transitive group memberships (names), one per line | |
| get_groups() { | |
| local upn="$1" | |
| az ad user get-member-groups --id "$upn" --query "[].displayName" -o tsv 2>/dev/null | sort | |
| } | |
| # Get direct RBAC role assignments for a user at subscription scope | |
| # Returns: "role | scope" lines | |
| get_direct_roles() { | |
| local upn="$1" | |
| local sub="$2" | |
| az role assignment list \ | |
| --assignee "$upn" \ | |
| --subscription "$sub" \ | |
| --include-inherited \ | |
| --query "[].{role:roleDefinitionName, scope:scope}" \ | |
| -o tsv 2>/dev/null \ | |
| | awk '{print $1 " | " $2}' | sort | |
| } | |
| # Get roles assigned to a specific group name (by resolving to object id first) | |
| get_group_roles() { | |
| local group_name="$1" | |
| local sub="$2" | |
| local oid | |
| oid=$(az ad group show --group "$group_name" --query "id" -o tsv 2>/dev/null) || return 0 | |
| az role assignment list \ | |
| --assignee "$oid" \ | |
| --subscription "$sub" \ | |
| --include-inherited \ | |
| --query "[].{role:roleDefinitionName, scope:scope}" \ | |
| -o tsv 2>/dev/null \ | |
| | awk -v grp="$group_name" '{print $1 " | " $2 " (via group: " grp ")"}' | sort | |
| } | |
| diff_lists() { | |
| # Items in file2 that are NOT in file1 | |
| comm -13 <(sort "$1") <(sort "$2") | |
| } | |
| # ── setup ──────────────────────────────────────────────────────────────────── | |
| # Resolve subscription | |
| if [[ -z "$SUBSCRIPTION" ]]; then | |
| SUBSCRIPTION=$(az account show --query "id" -o tsv) | |
| SUB_NAME=$(az account show --query "name" -o tsv) | |
| else | |
| SUB_NAME=$(az account show --subscription "$SUBSCRIPTION" --query "name" -o tsv 2>/dev/null || echo "$SUBSCRIPTION") | |
| fi | |
| echo "" | |
| echo "Comparing Azure permissions" | |
| echo " Subscription : $SUB_NAME ($SUBSCRIPTION)" | |
| echo " You : $MY_EMAIL" | |
| echo " Colleague : $COLLEAGUE_EMAIL" | |
| MY_UPN=$(resolve_upn "$MY_EMAIL") | |
| COL_UPN=$(resolve_upn "$COLLEAGUE_EMAIL") | |
| MY_NAME=$(get_display_name "$MY_UPN") | |
| COL_NAME=$(get_display_name "$COL_UPN") | |
| echo " Resolved you : $MY_NAME ($MY_UPN)" | |
| echo " Resolved col : $COL_NAME ($COL_UPN)" | |
| # ── 1. Entra group membership diff ─────────────────────────────────────────── | |
| header "1. ENTRA GROUP MEMBERSHIPS" | |
| TMP_MY_GROUPS=$(mktemp) | |
| TMP_COL_GROUPS=$(mktemp) | |
| trap 'rm -f "$TMP_MY_GROUPS" "$TMP_COL_GROUPS"' EXIT | |
| echo "Fetching groups (this may take a moment)..." | |
| get_groups "$MY_UPN" > "$TMP_MY_GROUPS" | |
| get_groups "$COL_UPN" > "$TMP_COL_GROUPS" | |
| MY_GROUP_COUNT=$(wc -l < "$TMP_MY_GROUPS" | tr -d ' ') | |
| COL_GROUP_COUNT=$(wc -l < "$TMP_COL_GROUPS" | tr -d ' ') | |
| echo " You are in : $MY_GROUP_COUNT groups" | |
| echo " Colleague is in: $COL_GROUP_COUNT groups" | |
| section "Groups colleague has that YOU DON'T" | |
| MISSING_GROUPS=$(diff_lists "$TMP_MY_GROUPS" "$TMP_COL_GROUPS") | |
| if [[ -z "$MISSING_GROUPS" ]]; then | |
| echo " (none — you share all the same groups, or colleague has none extra)" | |
| else | |
| echo "$MISSING_GROUPS" | while IFS= read -r grp; do echo " + $grp"; done | |
| fi | |
| section "Groups YOU have that colleague doesn't" | |
| EXTRA_GROUPS=$(diff_lists "$TMP_COL_GROUPS" "$TMP_MY_GROUPS") | |
| if [[ -z "$EXTRA_GROUPS" ]]; then | |
| echo " (none)" | |
| else | |
| echo "$EXTRA_GROUPS" | while IFS= read -r grp; do echo " + $grp"; done | |
| fi | |
| # ── 2. Direct RBAC role assignment diff ────────────────────────────────────── | |
| header "2. DIRECT AZURE RBAC ROLE ASSIGNMENTS (subscription: $SUB_NAME)" | |
| TMP_MY_ROLES=$(mktemp) | |
| TMP_COL_ROLES=$(mktemp) | |
| trap 'rm -f "$TMP_MY_GROUPS" "$TMP_COL_GROUPS" "$TMP_MY_ROLES" "$TMP_COL_ROLES"' EXIT | |
| echo "Fetching direct role assignments..." | |
| get_direct_roles "$MY_UPN" "$SUBSCRIPTION" > "$TMP_MY_ROLES" | |
| get_direct_roles "$COL_UPN" "$SUBSCRIPTION" > "$TMP_COL_ROLES" | |
| section "Roles colleague has directly that YOU DON'T" | |
| MISSING_ROLES=$(diff_lists "$TMP_MY_ROLES" "$TMP_COL_ROLES") | |
| if [[ -z "$MISSING_ROLES" ]]; then | |
| echo " (none)" | |
| else | |
| echo "$MISSING_ROLES" | while IFS= read -r r; do echo " + $r"; done | |
| fi | |
| section "Roles YOU have directly that colleague doesn't" | |
| EXTRA_ROLES=$(diff_lists "$TMP_COL_ROLES" "$TMP_MY_ROLES") | |
| if [[ -z "$EXTRA_ROLES" ]]; then | |
| echo " (none)" | |
| else | |
| echo "$EXTRA_ROLES" | while IFS= read -r r; do echo " + $r"; done | |
| fi | |
| # ── 3. Effective roles through missing groups ───────────────────────────────── | |
| header "3. EFFECTIVE RBAC ROLES via MISSING GROUPS (subscription: $SUB_NAME)" | |
| if [[ -z "$MISSING_GROUPS" ]]; then | |
| echo " No missing groups — skipping." | |
| else | |
| echo "Checking RBAC assignments on groups you're missing..." | |
| echo "" | |
| HAS_GROUP_ROLES=false | |
| while IFS= read -r grp; do | |
| GROUP_ROLES=$(get_group_roles "$grp" "$SUBSCRIPTION") | |
| if [[ -n "$GROUP_ROLES" ]]; then | |
| HAS_GROUP_ROLES=true | |
| echo " Group: $grp" | |
| echo "$GROUP_ROLES" | while IFS= read -r r; do echo " -> $r"; done | |
| fi | |
| done <<< "$MISSING_GROUPS" | |
| $HAS_GROUP_ROLES || echo " (none of the missing groups have RBAC assignments on this subscription)" | |
| fi | |
| # ── summary ────────────────────────────────────────────────────────────────── | |
| header "SUMMARY — what your colleague has that you don't" | |
| echo "" | |
| echo "Missing Entra groups:" | |
| if [[ -z "$MISSING_GROUPS" ]]; then | |
| echo " (none)" | |
| else | |
| echo "$MISSING_GROUPS" | while IFS= read -r g; do echo " - $g"; done | |
| fi | |
| echo "" | |
| echo "Missing direct role assignments:" | |
| if [[ -z "$MISSING_ROLES" ]]; then | |
| echo " (none)" | |
| else | |
| echo "$MISSING_ROLES" | while IFS= read -r r; do echo " - $r"; done | |
| fi | |
| echo "" | |
| echo "To request access, share the group names above with your Azure/Entra admin." | |
| echo "Done." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment