Last active
March 5, 2026 17:11
-
-
Save richeney/60f487bf788cfd52c89cfd7800d5adef to your computer and use it in GitHub Desktop.
Azure CLI commands to show tenant, user, and partner ID for Partner Admin Link.
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 | |
| # [GitHub Gist - richeney/pal_attestation.sh](https://gist.github.com/richeney/60f487bf788cfd52c89cfd7800d5adef) | |
| # Usage: curl -sSL https://aka.ms/pal/attestation | bash | |
| error() { echo "ERROR: $@" >&2; exit 1; } | |
| usage() { | |
| cat <<EOF | |
| Usage: $(basename "${BASH_SOURCE[0]:-$0}") [-a|--assignments] [--object-id <id>] [-h|--help] | |
| Outputs a JSON attestation object for the current Azure login and Partner Admin Link (PAL). | |
| Options: | |
| -a, --assignments Include direct RBAC role assignments in the output | |
| --object-id <id> Query assignments for a specific object ID instead of the current login | |
| (PAL fields will be N/A when using this option) | |
| -h, --help Show this help message | |
| Examples: | |
| # Standard usage - pipe from URL | |
| curl -sSL https://aka.ms/pal/attestation | bash | |
| # Include role assignments | |
| curl -sSL https://aka.ms/pal/attestation | bash -s -- --assignments | |
| # Query a specific object ID | |
| curl -sSL https://aka.ms/pal/attestation | bash -s -- --assignments --object-id <objectId> | |
| # Local usage | |
| ./pal_attestation.sh | |
| ./pal_attestation.sh --assignments | |
| ./pal_attestation.sh --assignments --object-id <objectId> | |
| EOF | |
| exit 0 | |
| } | |
| # Parse arguments | |
| includeAssignments=false | |
| targetObjectId="" | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| -a|--assignments) includeAssignments=true ;; | |
| --object-id) targetObjectId="$2"; shift ;; | |
| -h|--help|-\?) usage ;; | |
| esac | |
| shift | |
| done | |
| # Prerequisites | |
| command -v jq &>/dev/null || error "jq is required but not installed" | |
| command -v az &>/dev/null || error "az is required but not installed" | |
| command -v base64 &>/dev/null || error "base64 is required but not installed" | |
| account=$(az account show --output json) | |
| token=$(az account get-access-token --query accessToken -o tsv) | |
| tenantId=$(jq -r '.tenantId' <<< "$account") | |
| callerObjectId=$(echo "$token" | cut -d. -f2 | base64 -d 2>/dev/null | jq -r '.oid') | |
| type=$(jq -r '.user.type' <<< "$account") | |
| # Determine target objectId and PAL fields | |
| if [[ -n "$targetObjectId" ]]; then | |
| objectId="$targetObjectId" | |
| palId="Unavailable" | |
| partnerId="Unavailable" | |
| partnerName="Unavailable" | |
| echo "INFO: PAL information is not available for a specified object ID - authenticate as that identity to retrieve it." >&2 | |
| domain=$(jq -r '.tenantDefaultDomain' <<< "$account") | |
| tenantName=$(jq -r '.tenantDisplayName' <<< "$account") | |
| type="unknown" | |
| # Try resolving as a service principal (accepts appId or SP objectId) | |
| spJson=$(az ad sp show --id "$targetObjectId" --output json 2>/dev/null) | |
| if [[ -n "$spJson" ]]; then | |
| spObjectId=$(jq -r '.id' <<< "$spJson") | |
| spAppId=$(jq -r '.appId' <<< "$spJson") | |
| if [[ "$targetObjectId" == "$spAppId" ]]; then | |
| echo "INFO: Resolved appId '$targetObjectId' to service principal objectId '$spObjectId'" >&2 | |
| objectId="$spObjectId" | |
| elif [[ "$targetObjectId" != "$spObjectId" ]]; then | |
| echo "INFO: Resolved to service principal objectId '$spObjectId'" >&2 | |
| objectId="$spObjectId" | |
| fi | |
| name=$(jq -r '.displayName' <<< "$spJson") | |
| appId="$spAppId" | |
| upn=null | |
| type="servicePrincipal" | |
| else | |
| # Try resolving as an app registration objectId | |
| appJson=$(az ad app show --id "$targetObjectId" --output json 2>/dev/null) | |
| if [[ -n "$appJson" ]]; then | |
| resolvedAppId=$(jq -r '.appId' <<< "$appJson") | |
| spJson=$(az ad sp show --id "$resolvedAppId" --output json 2>/dev/null) | |
| if [[ -n "$spJson" ]]; then | |
| spObjectId=$(jq -r '.id' <<< "$spJson") | |
| echo "INFO: Resolved app registration objectId '$targetObjectId' to service principal objectId '$spObjectId'" >&2 | |
| objectId="$spObjectId" | |
| name=$(jq -r '.displayName' <<< "$spJson") | |
| appId="$resolvedAppId" | |
| upn=null | |
| type="servicePrincipal" | |
| fi | |
| else | |
| # Fall back to user lookup | |
| userJson=$(az ad user show --id "$targetObjectId" --output json 2>/dev/null) | |
| if [[ -n "$userJson" ]]; then | |
| name=$(jq -r '.displayName' <<< "$userJson") | |
| upn=$(jq -r '.userPrincipalName' <<< "$userJson") | |
| appId=null | |
| type="user" | |
| else | |
| name="$targetObjectId" | |
| upn=null | |
| appId=null | |
| fi | |
| fi | |
| fi | |
| else | |
| objectId="$callerObjectId" | |
| pal=$(az rest --uri "https://management.azure.com/providers/microsoft.managementpartner/partners?api-version=2018-02-01" --output json) || error "No Partner Admin Link for this tenant?" | |
| palId=$(jq -r .id <<< $pal) | |
| partnerId=$(jq -r .properties.partnerId <<< $pal) | |
| partnerName=$(jq -r .properties.partnerName <<< $pal) | |
| if [[ "$type" == "user" ]]; then | |
| upn=$(jq -r '.user.name' <<< "$account") | |
| name=$(az ad signed-in-user show --query displayName --output tsv) || error "Stale token issue?" | |
| appId=null | |
| domain=$(jq -r '.tenantDefaultDomain' <<< "$account") | |
| tenantName=$(jq -r '.tenantDisplayName' <<< "$account") | |
| else | |
| appId=$(jq -r '.user.name' <<< "$account") | |
| name=$(az ad sp show --id "$appId" --query displayName --output tsv 2>/dev/null || echo "$appId") | |
| upn=null | |
| domain=null | |
| tenantName=null | |
| fi | |
| [[ "$tenantId" == "$(jq -r .properties.tenantId <<< $pal)" ]] || error "Tenant IDs do not match." | |
| [[ "$objectId" == "$(jq -r .properties.objectId <<< $pal)" ]] || error "Object IDs do not match." | |
| fi | |
| # Build role definition lookup and fetch assignments if requested | |
| assignmentsArray=null | |
| if [[ "$includeAssignments" == "true" ]]; then | |
| tmpfile=$(mktemp) | |
| az role definition list \ | |
| --scope "/providers/Microsoft.Management/managementGroups/$tenantId" \ | |
| --query '[].{id:name, roleName:roleName, roleType:roleType}' \ | |
| -o json > "$tmpfile" | |
| assignments=$(az graph query \ | |
| --management-groups "$tenantId" \ | |
| --first 1000 \ | |
| -q "authorizationresources | |
| | where type =~ 'microsoft.authorization/roleassignments' | |
| | where properties.principalId == '$objectId' | |
| | extend scope = tostring(properties.scope) | |
| | extend principalType = tostring(properties.principalType) | |
| | extend roleDefGuid = tostring(split(properties.roleDefinitionId, '/')[4]) | |
| | project scope, principalType, roleDefGuid" \ | |
| -o json) | |
| assignmentsArray=$(echo "$assignments" | jq --slurpfile defs "$tmpfile" ' | |
| ($defs[0] | INDEX(.id)) as $lookup | | |
| [ .data[] | . as $a | | |
| ($lookup[$a.roleDefGuid] // {roleName: "unknown", roleType: "unknown"}) as $def | | |
| { | |
| scope: $a.scope, | |
| id: $a.roleDefGuid, | |
| roleName: $def.roleName, | |
| roleType: (if $def.roleType == "BuiltInRole" then "BuiltIn" else "Custom" end) | |
| } | |
| ]') | |
| rm "$tmpfile" | |
| fi | |
| jq -n --arg tenantId "$tenantId" --arg tenantName "$tenantName" --arg domain "$domain" \ | |
| --arg type "$type" --arg upn "$upn" --arg appId "$appId" --arg objectId "$objectId" --arg name "$name" \ | |
| --arg palId "$palId" --arg partnerId "$partnerId" --arg partnerName "$partnerName" \ | |
| --argjson assignments "$assignmentsArray" \ | |
| '{ | |
| "tenantId": $tenantId, | |
| "tenantDisplayName": (if $tenantName == "null" then null else $tenantName end), | |
| "tenantDefaultDomain": (if $domain == "null" then null else $domain end), | |
| "type": $type, | |
| "displayName": $name, | |
| "userPrincipalName": (if $upn == "null" then null else $upn end), | |
| "appId": (if $appId == "null" then null else $appId end), | |
| "objectId": $objectId, | |
| "partnerAdminLink": $palId, | |
| "partnerName": $partnerName, | |
| "partnerId": $partnerId, | |
| "assignments": $assignments | |
| }' | |
| exit |
Author
Author
Added usage section and also direct RBAC role assignments. Thank goodness for GitHub Copilot CLI.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note that tenant display name and default domain are not easily available for service principals.