Skip to content

Instantly share code, notes, and snippets.

@kell05
Created April 15, 2026 10:58
Show Gist options
  • Select an option

  • Save kell05/1d9eb119eaed96dd3c5ba1fe487e7294 to your computer and use it in GitHub Desktop.

Select an option

Save kell05/1d9eb119eaed96dd3c5ba1fe487e7294 to your computer and use it in GitHub Desktop.
Compare rbac between two users
#!/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