Access Customer Azure Subscriptions - Indirect Partners

Access Customer Azure Subscriptions - Indirect Partners

As an indirect Microsoft partner, you don’t automatically receive access to your customer’s Azure subscriptions through Partner Center.
However, you can still gain access  by adding your partner tenant’s Admin Agent group (Or your GDAP security group) to their subscription. This allows you to manage it using your partner login, similar to delegated permissions in Microsoft Admin Center. 


Why This Matters

To use Sync 365’s automated Azure billing feature  as an indirect provider, you must have access to your customers’ Azure subscriptions. Completing the steps below will enable automated billing to run correctly.

Prerequisites - Patner/MSP Tenant

Get the AdminAgents (Or Security group used for GDAP) Object ID from your tenant (The MSP Tenant)

  1. Log into your MSP tenant via https://portal.azure.com using an account with group admin privileges.

  2. Go to Microsoft Entra> Groups.

  3. Search for the group named "AdminAgents". (Or Security group used for GDAP))

  4. Copy the Object ID of that group — you’ll need this for the access script.

   This Object ID allows us to grant your tenant access to customer subscriptions for billing automation.

Permissions granted

  • Cost Management Reader on selected subscription.
  • Reservations Reader at the Microsoft.Capacity provider scope, covering current and future reservations.

This uses substantially less access than Owner.

Important: retail/RRP visibility

The customer Global Administrator cannot enable the CSP cost-visibility policy. It is controlled at the CSP billing account by the direct provider or indirect provider. If cost data is accessible but retail/RRP values are missing, enable Azure Cost Management on their subscription from the partner center or via your Distributor.

Before starting

  1. In the MSP tenant, open Microsoft Entra ID > Groups.
  2. Find AdminAgents, or the security group used for GDAP.
  3. Copy its Object ID.
  4. Open the customer Azure portal as Global Administrator.

Azure Cloud Shell - Bash

  1. Log into https://portal.azure.com as the global administrator for the client tenant.
  2. Click on the Azure CLI button 
  3. Accept defaults to create a storage account for its use if required.
  4. Ensure it says "Switch to Powershell" to confirm you are in BASH
  5. Paste the entire script and hit enter
  6. Enter the  group Object ID from above when prompted.
    1. Hit Y to confirm the object ID
  7. Hit Y to elevate and check for all subscriptions
  8. Select 0 to apply to all subscriptions or select a specific subscription to apply to
  9. Hit Y to apply to reservations as well
  10. Wait for the script to complete
  1. #!/usr/bin/env bash
    set -euo pipefail

    if ! command -v az >/dev/null 2>&1; then
    echo "Azure CLI is required. Run this in Azure Cloud Shell Bash or install Azure CLI locally." >&2
    exit 1
    fi

    if ! az account show >/dev/null 2>&1; then
    echo "No Azure CLI session found. Starting az login..."
    az login >/dev/null
    fi

    read -r -p "Enter the AdminAgents or GDAP group Object ID: " partnerId
    if [[ ! "$partnerId" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]]; then
    echo "The value is not a valid Object ID GUID." >&2
    exit 1
    fi

    read -r -p "Confirm this Object ID was copied from the intended AdminAgents/GDAP group [Y/N]: " confirm
    if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
    echo "Cancelled." >&2
    exit 1
    fi

    read -r -p "Attempt Azure elevateAccess first? Only needed for Global Admins without subscription RBAC [Y/N]: " elevate
    if [[ "$elevate" =~ ^[Yy]$ ]]; then
    az rest \
    --method post \
    --url "https://management.azure.com/providers/Microsoft.Authorization/elevateAccess?api-version=2015-07-01" \
    >/dev/null
    fi

    mapfile -t subs < <(az account list --query "[?state=='Enabled'].[id,name]" -o tsv)
    if (( ${#subs[@]} == 0 )); then
    echo "No enabled subscriptions were found for this account." >&2
    exit 1
    fi

    echo ""
    echo "Select the subscription scope to apply Cost Management Reader access:"
    echo " [0] All enabled subscriptions"
    for i in "${!subs[@]}"; do
    subId="${subs[$i]%%$'\t'*}"
    subName="${subs[$i]#*$'\t'}"
    echo " [$((i + 1))] ${subName} (${subId})"
    done

    read -r -p "Enter 0 for all subscriptions, or enter a subscription number: " selection
    selectedIndexes=()
    if [[ "$selection" == "0" ]]; then
    for i in "${!subs[@]}"; do
    selectedIndexes+=("$i")
    done
    elif [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#subs[@]} )); then
    selectedIndexes+=("$((selection - 1))")
    else
    echo "Invalid subscription selection. Enter 0 for all subscriptions, or a number from 1 to ${#subs[@]}." >&2
    exit 1
    fi

    for i in "${selectedIndexes[@]}"; do
    subId="${subs[$i]%%$'\t'*}"
    subName="${subs[$i]#*$'\t'}"
    scope="/subscriptions/${subId}"

    az account set --subscription "$subId"

    existingCount=$(az role assignment list \
    --scope "$scope" \
    --role "Cost Management Reader" \
    --query "[?principalId=='${partnerId}'] | length(@)" \
    -o tsv)

    if [[ "$existingCount" == "0" ]]; then
    az role assignment create \
    --assignee-object-id "$partnerId" \
    --assignee-principal-type ForeignGroup \
    --role "Cost Management Reader" \
    --scope "$scope" \
    >/dev/null
    fi

    echo "Cost access verified: ${subName}"
    done

    resScope="/providers/Microsoft.Capacity"
    echo ""
    echo "Reservations Reader is applied to all reservations"
    read -r -p "Apply or verify Reservations Reader has been applied for this group [Y/N]: " applyReservations

    if [[ "$applyReservations" =~ ^[Yy]$ ]]; then
    existingReservationCount=$(az role assignment list \
    --scope "$resScope" \
    --role "Reservations Reader" \
    --query "[?principalId=='${partnerId}'] | length(@)" \
    -o tsv)

    if [[ "$existingReservationCount" == "0" ]]; then
    az role assignment create \
    --assignee-object-id "$partnerId" \
    --assignee-principal-type ForeignGroup \
    --role "Reservations Reader" \
    --scope "$resScope" \
    >/dev/null

    echo "Reservations access assigned: Reservations Reader at ${resScope}"
    else
    echo "Reservations access already existed: Reservations Reader at ${resScope}"
    fi

    echo ""
    echo "Reservations role assignment confirmation:"
    az role assignment list \
    --scope "$resScope" \
    --role "Reservations Reader" \
    --query "[?principalId=='${partnerId}'].{Role:roleDefinitionName,Scope:scope,PrincipalType:principalType,PrincipalId:principalId}" \
    -o table
    else
    echo "Reservations Reader was skipped by user selection."
    fi

    az role assignment list \
    --query "[?principalId=='${partnerId}'].{Role:roleDefinitionName,Scope:scope,PrincipalType:principalType}" \
    -o table

    echo "Completed. Confirm PrincipalType is showing as a ForeignGroup."


Notes

  • Run the script again after adding new subscriptions. Reservation access at the provider scope covers new reservations automatically.
  • Global Administrator alone does not normally manage Azure resources. The script elevates the signed-in administrator to User Access Administrator at the tenant root so it can create the assignments.
  • The foreign group cannot be looked up by name from the customer tenant. The scripts therefore validate the GUID, require confirmation and verify the resulting ForeignGroup assignments.
    • Related Articles

    • Azure Automated Billing - Initial Setup (Indirect CSP)

      Overview As an Indirect CSP partner, you work through a distributor to access Microsoft services. This guide covers initial setup including vendor markup configuration to start using our Azure Automated Billing. Prerequisites PSA configured in Sync ...
    • Azure Automated Billing - Overview

      What is Azure Automated Billing? Azure Automated Billing is a feature that streamlines the process of billing your customers for their Azure consumption. Instead of manually uploading CSV files, the system automatically retrieves consumption data ...
    • Azure billing reconciliation breakdown

      Azure billing reconciliation breakdown This article explains how to review an Azure billing period in Sync 365 and understand the values used to calculate the amount sent to your PSA. Azure billing can include Microsoft retail pricing, distributor ...
    • Azure Automated Billing - Initial Setup (Direct CSP)

      Overview As a Direct CSP partner, you have direct access to Microsoft Partner Center and can automatically retrieve Azure consumption data for all your customers. This guide walks you through the initial setup process to start using Azure Automated ...
    • Azure Automated Billing - Managing Reserved Instances

      Overview For Indirect CSP's, Azure Reserved Instances (RIs) require manual configuration of cost and sell prices before they can be billed automatically. What Are Reserved Instances? Reserved Instances: Pre-purchased Azure capacity at discounted rate ...