﻿# 資訊安全筆記：盤點 Azure 訂用帳戶與角色指派的方法

在 Azure 所有 **使用者** (User) 與 **服務** (Service) 都代表著一個 **主體** (Principal)，而 Azure 的 RBAC (以角色為主的存取控制) 主要就是針對 **主體** (Principal) 來進行角色指派 (授權)，因此我們可以對 **使用者主體** (User Principal) 或 **服務主體** (Service Principal) 設定在**不同資源**所擁有的**角色**。本篇文章我打算來分享幾個方法，教你如何在特定 **訂用帳戶** (Subscription) 下查出有**哪些主體**被設定了**哪些角色**，以及**特定主體**已經被授權到了**哪些訂用帳戶**、**哪些資源**的**哪些角色**，藉此用來盤點是否有**不當授權**的狀況。

![Security](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/3e3bdb61d8fac6a6a7b9-209446570-2e6adba7-b415-41f6-adb3-41140ecd51f9.png)

### 使用 Azure CLI 的準備工作

1.  [安裝 Azure CLI 命令列工具](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?WT.mc_id=DT-MVP-4015686)
    
2.  清空帳戶快取資料 (🔥 **重要** 🔥)
    
    ```bash
    az account clear
    ```
    
    > 檢查角色指派必須要確認清除本機 Azure CLI 的所有快取資料，否則取得的訂用帳戶可能有權限不足的問題。
    
3.  登入 Azure CLI
    
    ```bash
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    az login --tenant $tenantId
    ```
    
    > 請務必指定 `tenantId`，一次針對一個 Tenant (租戶) 進行管理是比較建議的用法！
    
    想知道你目前登入的使用者帳戶加入了哪幾個 Tenants 可以用以下命令查詢 (只能查到 `TenantId` 而已)：
    
    ```bash
    az account tenant list
    ```
    
    因為 `az account tenant list` 太弱了，可以查出的資訊太少，建議直接呼叫 Graph API 取得完整的 Tenants 清單：
    
    ```bash
    az rest --method get --url https://management.azure.com/tenants?api-version=2021-01-01 --query 'value' -o json
    ```
    
    想知道你目前登入了哪個**使用者帳戶**，可以用以下命令查詢：
    
    ```bash
    az ad signed-in-user show -o json
    az ad signed-in-user show --query 'userPrincipalName' -o tsv
    ```
    
    若想查詢目前選用 Tenant 的網域名稱，可以考慮以下任何一種命令：
    
    ```powershell
    (az ad signed-in-user show --query 'userPrincipalName' -o tsv).Split('@')[1]
    
    az rest --method get --url https://graph.microsoft.com/v1.0/domains --query 'value[?isDefault].id'
    ```
    
4.  切換 **訂用帳戶** (Subscription)
    
    ```bash
    az account set --subscription 'SubscriptionId'
    ```
    
    你也可以使用 `az account show` 取得當前選取的 `id` 屬性：
    
    ```bash
    az account show -o json
    ```
    
5.  取得所有 **訂用帳戶** (Subscription) 清單
    
    如果你在執行 `az login` 登入時有加上 `--tenant` 參數，那麼這裡只會列出單一租戶下的訂用帳戶而已！
    
    ```bash
    az account list
    ```
    
    你也可以使用以下 [JMESPath](https://jmespath.org) 查詢語法篩選出特定 `tenantId` 的訂用帳戶
    
    ```bash
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    az account list --query "[?tenantId=='${tenantId}']" -o json
    ```
    
    你也可以使用以下命令，查詢目前指定 Tenant 下的所有訂用帳戶清單
    
    ```bash
    az account subscription list
    ```
    

### 使用 Azure Az PowerShell 的準備工作

1.  [安裝 Az 模組](https://learn.microsoft.com/zh-tw/powershell/azure/install-az-ps?view=azps-7.0.0&WT.mc_id=DT-MVP-4015686)
    
    ```powershell
    Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
    ```
    
    > 如果執行此命令有問題，請關閉終端機，重開 Windows PowerShell 執行，不要用 PowerShell (Core) 喔！🔥
    
2.  匯入所有 `Az` 模組
    
    ```powershell
    Import-Module Az
    ```
    
    這個步驟會匯入一堆 `Az.` 開頭的 PowerShell 模組
    
    ```powershell
    Get-Module 'Az*'
    ```
    
    ```
    ModuleType Version   Name
    ---------- -------   ----
        Script 9.2.0     Az
        Script 2.10.4    Az.Accounts
        Script 2.0.0     Az.Advisor
        Script 5.1.0     Az.Aks
        Script 1.1.4     Az.AnalysisServices
        Script 4.0.1     Az.ApiManagement
        Script 1.2.0     Az.AppConfiguration
        Script 2.2.0     Az.ApplicationInsights
        Script 2.0.0     Az.Attestation
        Script 1.8.0     Az.Automation
        Script 3.2.1     Az.Batch
        Script 2.0.0     Az.Billing
        Script 2.1.0     Az.Cdn
        Script 1.1.0     Az.CloudService
        Script 1.12.0    Az.CognitiveServices
        Script 5.2.0     Az.Compute
        Script 1.0.0     Az.ConfidentialLedger
        Script 3.1.0     Az.ContainerInstance
        Script 3.0.0     Az.ContainerRegistry
        Script 1.9.0     Az.CosmosDB
        Script 1.1.0     Az.DataBoxEdge
        Script 1.4.0     Az.Databricks
        Script 1.16.11   Az.DataFactory
        Script 1.0.2     Az.DataLakeAnalytics
        Script 1.3.0     Az.DataLakeStore
        Script 1.0.1     Az.DataProtection
        Script 1.0.1     Az.DataShare
        Script 1.1.0     Az.DeploymentManager
        Script 3.1.1     Az.DesktopVirtualization
        Script 1.0.2     Az.DevTestLabs
        Script 1.1.2     Az.Dns
        Script 1.5.0     Az.EventGrid
        Script 3.2.0     Az.EventHub
        Script 1.9.0     Az.FrontDoor
        Script 4.0.6     Az.Functions
        Script 5.0.1     Az.HDInsight
        Script 2.0.0     Az.HealthcareApis
        Script 2.7.4     Az.IotHub
        Script 4.9.1     Az.KeyVault
        Script 2.1.0     Az.Kusto
        Script 1.5.0     Az.LogicApp
        Script 1.1.3     Az.MachineLearning
        Script 1.2.1     Az.Maintenance
        Script 1.1.0     Az.ManagedServiceIdentity
        Script 3.0.0     Az.ManagedServices
        Script 2.0.0     Az.MarketplaceOrdering
        Script 1.1.1     Az.Media
        Script 2.1.0     Az.Migrate
        Script 4.3.0     Az.Monitor
        Script 1.1.0     Az.MySql
        Script 5.2.0     Az.Network
        Script 1.1.1     Az.NotificationHubs
        Script 3.2.0     Az.OperationalInsights
        Script 1.5.1     Az.PolicyInsights
        Script 1.1.0     Az.PostgreSql
        Script 1.2.0     Az.PowerBIEmbedded
        Script 1.0.3     Az.PrivateDns
        Script 6.1.2     Az.RecoveryServices
        Script 1.6.0     Az.RedisCache
        Script 1.1.0     Az.RedisEnterpriseCache
        Script 1.0.3     Az.Relay
        Script 1.1.0     Az.ResourceMover
        Script 6.5.0     Az.Resources
        Script 1.3.0     Az.Security
        Script 3.0.0     Az.SecurityInsights
        Script 2.1.0     Az.ServiceBus
        Script 3.1.0     Az.ServiceFabric
        Script 1.5.0     Az.SignalR
        Script 4.1.0     Az.Sql
        Script 1.1.0     Az.SqlVirtualMachine
        Script 1.4.0     Az.StackHCI
        Script 5.2.0     Az.Storage
        Script 1.7.0     Az.StorageSync
        Script 2.0.0     Az.StreamAnalytics
        Script 1.0.0     Az.Support
        Script 2.2.0     Az.Synapse
        Script 1.1.0     Az.TrafficManager
        Script 2.12.0    Az.Websites
        Binary 2.0.2.140 AzureAD
    ```
    
3.  登出 Azure 帳戶
    
    ```powershell
    Logout-AzAccount
    ```
    
4.  連接 Azure 帳戶
    
    ```powershell
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    Connect-AzAccount -Tenant $tenantId
    ```
    
    想知道你目前登入的使用者帳戶加入了哪幾個 Tenants 可以用以下命令查詢 (可以查到相當完整的資訊)：
    
    ```bash
    Get-AzTenant
    ```
    
5.  取得所有 **訂用帳戶** (Subscription) 清單
    
    即便你在連接 Azure 帳戶時有加上 `-Tenant` 參數，這裡依然會列出你的**登入帳號**中隸屬的**所有租戶**下的所有**訂用帳戶**喔！
    
    ```bash
    Get-AzSubscription
    ```
    
    建議使用 `-TenantId` 參數篩選出特定 `tenantId` 的**訂用帳戶**
    
    ```bash
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    Get-AzSubscription -TenantId $tenantId
    ```
    

### 盤點一個 **訂用帳戶** (Subscription) 下有多少 **角色指派** (Role Assignments)

-   透過 Azure Portal 查詢
    
    進入 **訂用帳戶** (Subscription) 的 **Access Control (IAM)** 就可以查詢到該訂用帳戶下所有的角色指派！
    
    > 這裡要注意 `Scope` 欄位，從該欄位可以看出這個角色被套用在哪個範圍，可能是 **Root**, **Subscription**, **Resource Group** 或 **Resource** 等等。
    
-   透過 Azure CLI 查詢
    
    列出所有角色授權清單 (包含所有**服務主體**在內)
    
    ```bash
    az role assignment list
    ```
    
    列出所有角色授權清單 (包含**繼承的主體**)
    
    ```bash
    az role assignment list --include-inherited
    ```
    
    列出所有角色授權清單 (包含**繼承的主體**與**傳統管理員**)
    
    ```bash
    az role assignment list --include-inherited --include-classic-administrators
    ```
    
    列出所有角色授權清單 (包含**繼承的主體**、**傳統管理員**與**資源群組**的角色授權)
    
    ```bash
    az role assignment list --include-inherited --include-classic-administrators --include-groups
    ```
    
    列出所有角色授權清單 (包含**繼承的主體**、**傳統管理員**、**資源群組**與**所有資源**的角色授權)
    
    ```bash
    az role assignment list --include-inherited --include-classic-administrators --include-groups --all
    ```
    
    列出所有**角色指派**清單中無法找到的**主體**的無效項目
    
    ```bash
    az role assignment list --include-groups --all
    ```
    
-   Azure Az PowerShell
    
    列出所有角色授權清單 (包含**繼承的主體**、**資源群組**與**所有資源**的角色授權)
    
    ```powershell
    $subscriptionId = '95970ee4-4d92-4f5f-b253-395c986c5fca'
    Get-AzRoleAssignment -Scope "/subscriptions/${subscriptionId}" -WarningAction Ignore
    ```
    
    列出所有角色授權清單 (包含**繼承的主體**、**資源群組**、**所有資源**與**傳統管理員**的角色授權)
    
    ```powershell
    $subscriptionId = '95970ee4-4d92-4f5f-b253-395c986c5fca'
    Get-AzRoleAssignment -Scope "/subscriptions/${subscriptionId}" -IncludeClassicAdministrators -WarningAction Ignore
    ```
    
    列出所有**角色指派**清單中無法找到的**主體**的無效項目
    
    ```bash
    $subscriptionId = '95970ee4-4d92-4f5f-b253-395c986c5fca'
    Get-AzRoleAssignment -Scope "/subscriptions/${subscriptionId}" -WarningAction Ignore | Where-Object { $_.DisplayName -eq $null }
    ```
    

### 盤點一個 **使用者主體** (User Principal) 有多少 **角色指派** (Role Assignments)

-   透過 Azure Portal 查詢
    
    進入 `Azure Active Directory` > `Users` > `選取使用者` > `Azure role assignments` 之後，這裡有個下拉選單可以讓你切換不同的訂用帳戶，此時你就可以看到該使用者在此訂用帳戶被指派的角色。
    
-   透過 Azure Az PowerShell 查詢每一個 **訂用帳戶** (Subscription) 下特定**使用者主體**的**角色指派**
    
    先把所有**訂用帳戶**的**角色指派**都顯示在畫面上，然後搜尋關鍵字，然後看看是否有不正常的授權狀況！
    
    ```powershell
    $signInName = 'user@example.com'
    $subs | foreach {
        "💡 $($_.Name) ($($_.Id))"
        # 注意: 使用 -SignInName 查不到 CoAdministrator 的使用者！
        Get-AzRoleAssignment -Scope "/subscriptions/$($_.Id)" -IncludeClassicAdministrators -WarningAction Ignore | select Scope,DisplayName,SignInName,RoleDefinitionName | where { $_.SignInName -eq $signInName } | ft
    }
    ```
    
-   透過 Azure Az PowerShell 查詢每一個 **訂用帳戶** (Subscription) 下的**角色指派**
    
    把所有**訂用帳戶**的**角色指派**都顯示在畫面上，然後搜尋關鍵字，然後看看是否有不正常的授權狀況！
    
    ```powershell
    $subs = Get-AzSubscription -TenantId $tenantId
    $subs | foreach {
      "💡 $($_.Name) ($($_.Id))"
      Get-AzRoleAssignment -Scope "/subscriptions/$($_.Id)" -IncludeClassicAdministrators -WarningAction Ignore | select Scope,DisplayName,SignInName,RoleDefinitionName | ft
    }
    ```
    

### 盤點一個 **服務主體** (Service Principal) 有多少 **角色指派** (Role Assignments)

-   建立 **服務主體** (Service Principal) 的方法
    
    最簡單的方法，預設會建立名為 `azure-cli-當天UTC日期時間` 的 **服務主體** (Service Principal)，並自動將該 **服務主體** (Service Principal) 指派 **目前選用訂用帳戶** 的 `Contributor` 角色：
    
    ```bash
    az ad sp create-for-rbac -o json
    ```
    
    你也可以明確指定預設要在**目前訂用帳戶**的**角色指派**
    
    ```bash
    az ad sp create-for-rbac --role Contributor -o json
    ```
    
    你也可以指定自訂的 **服務主體** (Service Principal) 名稱
    
    ```bash
    az ad sp create-for-rbac --name 'my-service-principal' --role Contributor -o json
    ```
    
-   透過 Azure CLI 查詢 **服務主體** (Service Principal) 指派了多少角色
    
    ```powershell
    $subscriptions = (az account list -o json | ConvertFrom-Json)
    $subscriptions | ForEach-Object {
        echo "正在列出訂用帳戶 $($_.name) 的角色指派清單"
        az role assignment list --subscription $_.id --query "[?principalType=='ServicePrincipal']" -o json
    }
    ```
    
    > 目前 Azure CLI 取得任何資料若有包含 non-English 字元會遇到字元無法轉換成 Unicode 的問題，這個問題目前[有三種解決方案](https://github.com/Azure/azure-cli/issues/14426#issuecomment-670826850)，但是都不完美，所以在 Windows 應該是無解了，建議改用 Linux 或 Azure Az PowerShell 來解決此問題。
    
-   透過 Azure Az PowerShell 查詢 **服務主體** (Service Principal) 指派了多少角色
    
    ```powershell
    # 取得所有訂用帳戶
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    $subscriptions = Get-AzSubscription -TenantId $tenantId
    
    # 取得所有角色指派
    $subscriptions | ForEach-Object {
        echo "正在列出訂用帳戶 $($_.Name) 的角色指派清單"
        Get-AzRoleAssignment -Scope "/subscriptions/$($_.Id)" -WarningAction Ignore | Where-Object { $_.ObjectType -eq 'ServicePrincipal' }
    }
    ```
    
-   列出所有**訂用帳戶**中所有的**角色指派**中無法找到的**主體**的無效項目
    
    Azure Az PowerShell
    
    ```powershell
    # 取得所有訂用帳戶
    $tenantId = '4b972557-b992-4f77-b743-41f0d251f741'
    $subscriptions = Get-AzSubscription -TenantId $tenantId
    
    # 取得所有角色指派
    $subscriptions | ForEach-Object {
        echo "正在列出訂用帳戶 $($_.Name) 的無效角色指派清單"
        Get-AzRoleAssignment -Scope "/subscriptions/$($_.Id)" -WarningAction Ignore | Where-Object { $_.DisplayName -eq $null }
    }
    ```
    
    Azure CLI
    
    ```powershell
    $subscriptions = (az account list -o json | ConvertFrom-Json)
    $subscriptions | ForEach-Object {
        echo "正在列出訂用帳戶 $($_.name) 的角色指派清單"
        az role assignment list --subscription $_.id --query "[?principalName=='']" -o json
    }
    ```
    

### 使用 Azure CLI 刪除角色指派的方法

1.  先找出訂用帳戶中的 **role assignment ids**
    
    ```bash
    az role assignment list --include-inherited --include-classic-administrators --include-groups --all -o json
    ```
    
    > 注意: 請找出角色指派的 `id` 屬性，該屬性長相很像**網址路徑** (`PATH_INFO`)。
    
2.  刪除角色指派
    
    ```powershell
    $subscriptionId = '90418710-4b33-401d-bfe2-5141bda70b64'
    $roleAssignmentsId = '0f2ce15f-1c21-4842-a84f-3884cc49258a'
    az role assignment delete --ids "/subscriptions/${subscriptionId}/providers/Microsoft.Authorization/roleAssignments/${roleAssignmentsId}"
    ```
    
    如果要一次刪除兩個角色指派，可以這樣用：
    
    ```powershell
    az role assignment delete --ids "/subscriptions/90418710-4b33-401d-bfe2-5141bda70b64/providers/Microsoft.Authorization/roleAssignments/78ff8eb5-1adc-4a61-a1a3-0cca6a25d652" "/subscriptions/90418710-4b33-401d-bfe2-5141bda70b64/providers/Microsoft.Authorization/roleAssignments/4449d6f8-7781-49c1-a4e2-249232220a30"
    ```
    

### 相關連結

-   [Azure RBAC documentation](https://learn.microsoft.com/en-us/azure/role-based-access-control/?WT.mc_id=DT-MVP-4015686)
-   [Azure role-based access control](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/considerations/roles?WT.mc_id=DT-MVP-4015686)
-   [List Azure role definitions](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-definitions-list?WT.mc_id=DT-MVP-4015686)
    -   [List Azure role assignments using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-portal?WT.mc_id=DT-MVP-4015686)
    -   [List Azure role assignments using Azure CLI](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-cli?WT.mc_id=DT-MVP-4015686)
    -   [List Azure role assignments using Azure PowerShell](https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-powershell?WT.mc_id=DT-MVP-4015686)
-   [Azure AD built-in roles](https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference?WT.mc_id=DT-MVP-4015686)
-   [Unable to encode the output with cp1252 encoding. Unsupported characters are discarded. · Issue #14426 · Azure/azure-cli](https://github.com/Azure/azure-cli/issues/14426#issuecomment-670826850)
-   [JMESPath Tutorial](https://jmespath.org/tutorial.html)
