教學課程:使用來自 Microsoft Entra 識別符的數據,在 Azure 數據總管中自定義報表
在本教學課程中,您將瞭解如何使用來自 Microsoft Entra ID 和 Microsoft Entra ID Governance Services 的數據,在 Azure 數據總管 (ADX) 中建立自定義報表。 這個教學課程補充了其他報告選擇,例如使用 Azure Monitor 和特權管理
本文說明如何顯示從 Microsoft Entra 導出的設定、使用者和訪問許可權,以及從其他來源匯出的數據,例如具有 SQL 資料庫的應用程式。 然後,您可以使用 Azure 數據總管中的 Kusto 查詢語言 (KQL),根據組織的需求來建置自定義報表。
您將採取下列步驟來建立這些報告:
- 在 Azure 訂用帳戶中設定 Azure 數據總管,或建立免費的叢集。
- 使用 PowerShell 指令碼和 MS Graph,以從 Microsoft entra 和第三方資料庫或應用程式擷取資料。
- 將資料匯入至 Azure 資料總管,這是快速且可調整的資料分析服務。
- 使用 Kusto 查詢語言來建置自訂查詢。
本教學課程結束時,您將已建置技能,以使用 Microsoft 所支援的工具跨不同的應用程式來開發使用者存取權限和權限的自訂檢視。
必要條件
如果您不熟悉 Azure 數據總管,並想要瞭解本文所示的案例,您可以取得 免費的 Azure 數據總管叢集。 為了在具備服務等級協定的生產環境中使用 Azure 數據總管,您需要 Azure 訂用帳戶來裝載完整的 Azure 數據總管叢集。
判斷您想要在報告中包括的資料。 本文中的指令碼提供具有 Entra 使用者、群組和應用程式特定資料的範例。 這些範例旨在說明您可以使用此方式產生的報告類型,但您的特定報告需求可能會不同,而且需要不同或其他資料。 您可以從這些對象開始,並在一段時間內引進更多種類的Microsoft Entra 物件。
- 本文說明如何以已登入的使用者身分,從 Microsoft Entra 擷取數據。 若要這樣做,請確定您有必要的角色指派,以便從 Microsoft Entra 取得資料。 您需要具有適當許可權的角色,才能匯出您想要使用之 Entra 數據類型。
- 使用者資料:全域系統管理員、特殊權限角色系統管理員、使用者系統管理員
- 群組資料:全域系統管理員、特殊權限角色系統管理員、群組系統管理員
- 應用程式角色指派:全域系統管理員、特殊權限角色系統管理員、應用程式系統管理員、雲端應用程式系統管理員
- Microsoft Graph PowerShell 必須同意允許透過 Microsoft Graph 擷取 Microsoft Entra 物件。 本教學課程中的範例需要委派的 User.Read.All、Group.Read.All、Application.Read.All 和 Directory.Read.All 許可權。 如果您打算在沒有登入用戶的情況下使用自動化來擷取數據,請改為同意對應的應用程式許可權。 如需其他資訊,請參閱 Microsoft Graph 權限參考。 如果您尚未同意 Microsoft Graph PowerShell 的這些許可權,您必須是全域系統管理員,才能執行此同意作業。
- 本教學課程不會說明自定義安全性屬性。 根據預設,全域管理員和其他系統管理角色,不具備從 Microsoft Entra 使用者讀取自訂安全性屬性的權限。 如果您打算擷取自定義安全性屬性,可能需要更多角色和許可權。
- 在安裝 Microsoft Graph PowerShell 的電腦上,請確定您有檔案系統目錄的寫入許可權,以便您可以在那裡安裝必要的 MS Graph PowerShell 模組,並將導出的 Entra 數據儲存在那裡。
- 請確定您有權從Microsoft Entra 以外的其他數據源擷取數據。
1:設定 Azure 數據總管
如果您先前尚未使用過 Azure 資料總管,則需要先進行此設定。 您可以建立 免費叢集,而不需要 Azure 訂用帳戶或信用卡 或需要 Azure 訂用帳戶的完整叢集。 請參閱快速入門:建立 Azure 資料總管叢集和資料庫,以開始使用。
2:使用 PowerShell 連線到 MS Graph 和擷取 Entra 數據
在本節中,您將 安裝 MS Graph PowerShell 模組, 並 連接到 MS Graph。
貴組織第一次針對此案例使用這些模組時,您必須具備全域管理員角色,才能授權在您的租戶中使用 Microsoft Graph PowerShell。 後續的互動可以使用低權限的角色。
- 開啟 PowerShell。
- 如果您尚未安裝所有 Microsoft Graph PowerShell 模組,請安裝必要的 MS Graph 模組。 本教學課程需要下列課程模組:
Microsoft.Graph.Authentication
、Microsoft.Graph.Users
、Microsoft.Graph.Groups
、Microsoft.Graph.Applications
、Microsoft.Graph.DirectoryObjects
。
$modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects')
foreach ($module in $modules) {
Install-Module -Name $module -Scope CurrentUser -AllowClobber -Force
}
- 將模組匯入目前的 PowerShell 工作階段。
$modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects')
foreach ($module in $modules) {
Import-Module -Name $module
}
- 聯機至 Microsoft Graph。
Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All" -ContextScope Process
此命令會提示您使用 Microsoft Entra 認證登入。 登入之後,如果您是第一次連線,或需要新的許可權,您可能需要同意所需的許可權。
為在 Azure 數據總管中建立自定義報表所需的資料提取 PowerShell 查詢
下列查詢會使用 PowerShell 從 MS Graph 擷取 Entra 數據,並將數據匯出至 JSON 檔案,這會在後續第 3 節中匯入至 Azure 數據總管。 產生具有此資料類型的報告時,可能會有多個案例:
- 稽核者所想要看到的報告是列出 10 個依成員部門所組織的群組的群組成員。
- 稽核者想要查看已在兩個日期之間存取應用程式的所有使用者的報告。
- 系統管理員想要從 Microsoft Entra ID 和 SQL 資料庫檢視所有新增至應用程式的使用者。
這些類型的報告未內建至 Microsoft Entra ID,但您可以從 Entra 擷取資料,並在 Azure 資料總管中使用自訂查詢將其合併,以自行建立這些報告。
在本教學課程中,我們將從數個區域擷取 Entra 資料:
- 使用者資訊,例如顯示名稱、UPN 和工作詳細資料
- 群組資訊
- 應用程式和角色指派
此資料集可讓我們針對獲授與存取應用程式、角色資訊和相關聯時間範圍的人員執行一組廣泛的查詢。 請注意,這些是範例查詢,而您的資料和特定需求可能會與此處所顯示的內容不同。
注意
較大的租用戶可能會遇到 MS Graph 模組將會處理的節流/429 錯誤。
在這些 PowerShell 指令碼中,我們會將所選取的屬性從 Entra 物件匯出至 JSON 檔案。 接著將會使用這些所匯出屬性中的資料,以在 Azure 資料總管中產生自訂報告。 下面的特定屬性之前包括在這些範例中,因為我們要使用此資料來說明您可以在 Azure 資料總管中建立的報告類型。 因為您的特定報告需求可能會與下面所顯示的內容不同,所以您應該在這些指令碼中包括您想要在報告中檢視的特定屬性,不過,您可以遵循下面所顯示的相同模式來協助建置指令碼。
我們也包括下面的硬式編碼「快照集日期」,其會識別 JSON 檔案中具有特定日期的資料,並將允許我們在 Azure 資料總管中追蹤一段時間的類似資料集。 快照集日期也有助於比較兩個快照日期之間的資料變更。
取得 Entra 使用者資料
此指令碼會將所選取的屬性從 Entra 使用者物件匯出至 JSON 檔案。 我們將在本教學課程的後續一節 ,將這項數據匯入 Azure 數據總管。
function Export-EntraUsersToJson {
# Define a hash table for property mappings
$propertyMappings = @{
"Id" = "ObjectID"
"DisplayName" = "DisplayName"
"UserPrincipalName" = "UserPrincipalName"
"EmployeeId" = "EmployeeId"
"UserType" = "UserType"
"CreatedDateTime" = "CreatedDateTime"
"JobTitle" = "JobTitle"
"Department" = "Department"
"AccountEnabled" = "AccountEnabled"
# Add custom properties as needed
"custom_extension" = "CustomExtension"
}
# Retrieve users with specified properties and create custom objects directly
$users = Get-MgUser -Select ($propertyMappings.Keys) -All | ForEach-Object {
$userObject = @{}
foreach ($key in $propertyMappings.Keys) {
if ($key -eq "CreatedDateTime") {
# Convert date string directly to DateTime and format it
$date = [datetime]::Parse($_.$key)
$userObject[$propertyMappings[$key]] = $date.ToString("yyyy-MM-dd")
} else {
$userObject[$propertyMappings[$key]] = $_.$key
}
}
# Additional properties or transformations
$userObject["SnapshotDate"] = "2024-01-11"
[pscustomobject]$userObject
}
# Convert the user data to JSON and save it to a file
$users | ConvertTo-Json -Depth 2 | Set-Content ".\EntraUsers.json"
}
# Execute the function
Export-EntraUsersToJson
取得群組資料
使用組名和標識碼產生 JSON 檔案,以用來在 Azure 數據總管中建立自定義檢視。 此範例將會包括所有群組,但如有需要,也可以包括其他篩選。 若要篩選成只包括特定群組,則可能會想要在指令碼中包括邏輯來檢查巢狀群組。
# Get all groups and select Id and DisplayName
$groups = Get-MgGroup -All | Select-Object Id,DisplayName
# Export the groups to a JSON file
$groups | ConvertTo-Json | Set-Content ".\EntraGroups.json"
取得群組成員資格資料
產生具有群組成員資格的 JSON 檔案,以用來在 Azure 數據總管中建立自定義檢視。
# Retrieve all groups from Microsoft Entra (Azure AD)
$groups = Get-MgGroup -All
# Initialize an array to store results
$results = @()
# Iterate over each group
foreach ($group in $groups) {
# Extract the group ID
$groupId = $group.Id
# Get members of the current group and select their IDs
$members = Get-MgGroupMember -GroupId $groupId | Select-Object -ExpandProperty Id
# Add a custom object with group ID and member IDs to the results array
$results += [PSCustomObject]@{
GroupId = $groupId
Members = $members
}
# Pause for a short time to avoid rate limits
Start-Sleep -Milliseconds 200
}
# Convert the results array to JSON format and save it to a file
$results | ConvertTo-Json | Set-Content "EntraGroupMembership.json"
取得應用程式和服務主體資料
產生 JSON 檔案,而此檔案包含租用戶中的所有應用程式和對應的服務主體。 我們將在本教學課程的後續章節 將這項數據匯入 Azure 數據總管,以讓我們根據此數據產生與應用程式相關的自定義報告。
# Fetch applications and their corresponding service principals, then export to JSON
Get-MgApplication -All | ForEach-Object {
$app = $_
$sp = Get-MgServicePrincipal -Filter "appId eq '$($app.AppId)'"
[pscustomobject]@{
Name = $app.DisplayName
ApplicationId = $app.AppId
ServicePrincipalId = $sp.Id
}
} | ConvertTo-Json -Depth 10 | Set-Content "Applications.json"
取得 AppRole 資料
針對 Entra 中的企業應用程式產生所有 appRoles 的 JSON 檔案。 匯入 Azure 資料總管之後,我們將使用此資料來產生包含使用者應用程式角色指派的報告。
# Get a list of all applications, handle pagination manually if necessary
$apps = Get-MgApplication -All
# Loop through each application to gather the desired information
$results = foreach ($app in $apps) {
# Get the service principal for the application using its appId
$spFilter = "appId eq '$($app.AppId)'"
$sp = Get-MgServicePrincipal -Filter $spFilter | Select-Object -First 1
# Process AppRoles, if any, for the application
$appRoles = if ($app.AppRoles) {
$app.AppRoles | Where-Object { $_.AllowedMemberTypes -contains "User" } |
Select-Object Id, Value, DisplayName
}
# Construct a custom object with application and service principal details
[PSCustomObject]@{
ApplicationId = $app.AppId
DisplayName = $app.DisplayName
ServicePrincipalId = $sp.Id
AppRoles = $appRoles
}
}
# Export the results to a JSON file
$results | ConvertTo-Json -Depth 4 | Out-File 'AppRoles.json'
取得 AppRole 指派資料
產生租用戶中所有應用程式角色指派的 JSON 檔案。
$users = Get-MgUser -All
$result = @()
foreach ($user in $users) {
Get-MgUserAppRoleAssignment -UserId $user.Id | ForEach-Object {
# Use the same date formatting approach
$createdDateTime = $_.CreatedDateTime -replace "\\/Date\((\d+)\)\\/", '$1'
# Convert the milliseconds timestamp to a readable date format if needed
$result += [PSCustomObject]@{
AppRoleId = $_.AppRoleId
CreatedDateTime = $createdDateTime
PrincipalDisplayName = $_.PrincipalDisplayName
PrincipalId = $_.PrincipalId
ResourceDisplayName = $_.ResourceDisplayName
ResourceId = $_.ResourceId
SnapshotDate = "2024-03-13" # Hard-coded date
}
}
}
$result | ConvertTo-Json -Depth 10 | Out-File "AppRoleAssignments.json"
3:將 JSON 檔案數據匯入 Azure 數據總管
在本節中,我們將匯入新建立的 JSON 檔案,以進行進一步分析。
在 Azure 數據總管叢集中設定資料庫或免費叢集之後,如本檔第一節所述,請流覽至該資料庫。
- 登入 Azure 資料總管的 Web 使用者介面。
- 從左側功能表中,選取 [查詢]。
接下來,請針對每個導出的 JSON 檔案遵循下列步驟,將匯出的數據放入該 Azure 數據總管資料庫。
以滑鼠右鍵按下您要內嵌資料之資料庫的資料庫名稱。 選擇 取得資料。
從可用的清單中選取資料來源。 在本教學課程中,您會從 本機檔案匯入資料。
選取 [+ 新增資料表],然後根據您要匯入的 JSON 檔名輸入資料表名稱,例如,如果您要匯入 EntraUsers.json,請將數據表命名 EntraUsers。 第一次匯入之後,數據表就已經存在,而且您可以選取它做為後續匯入的目標數據表。
選取 [瀏覽檔案],選取 JSON 檔案,然後選取 [下一步]。
Azure 數據總管會自動偵測架構,並在 [檢查] 索引標籤中提供預覽。按兩下 [完成] 即可建立數據表,並從該檔案匯入數據。
針對您在第一個區段中產生的每個 JSON 檔案重複上述每個步驟。
4:使用 Azure 數據總管建置自定義報告
有了 Azure 數據總管中現在可用的數據,您就可以根據業務需求開始建立自定義報表。
Azure 資料總管是一種功能強大的資料分析工具,可進行高度調整且具有彈性,以提供產生自訂使用者存取報告的理想環境。 Azure 資料瀏覽器使用 Kusto 查詢語言(KQL)。
下列查詢提供一般報告的範例,但您可以自訂這些報告以符合您的需求,並建立其他報告。
範例 1:針對特定快照集日期產生直接和群組指派的應用程式角色指派
此報告提供誰具有目標應用程式的什麼存取權和存取時間的檢視,並可用於安全性稽核、合規性驗證,以及瞭解組織內的存取模式。
此查詢會以 Entra AD 內的特定應用程式為目標,並分析特定日期之前的角色指派。 查詢會同時擷取直接和群組型角色指派,並將此資料與 EntraUsers 資料表中的使用者詳細資料和 AppRoles 資料表中的角色資訊合併。
/// Define constants
let targetServicePrincipalId = "<your service principal-id>"; // Target Service Principal ID
let targetSnapshotDate = datetime("2024-01-13"); // Target Snapshot Date for the data
// Extract role assignments for the target Service Principal and Snapshot Date
let roleAssignments = AppRoleAssignments
| where ResourceId == targetServicePrincipalId and startofday(SnapshotDate) == targetSnapshotDate
| extend AppRoleIdStr = tostring(AppRoleId); // Convert AppRoleId to string for easier comparison
// Prepare user data from EntraUsers table
let users = EntraUsers
| project ObjectID, UserPrincipalName, DisplayName, ObjectIDStr = tostring(ObjectID); // Include ObjectID as string for joining
// Prepare role data from AppRoles table
let roles = AppRoles
| mvexpand AppRoles // Expand AppRoles to handle multiple roles
| extend RoleName = AppRoles.DisplayName, RoleId = tostring(AppRoles.Id) // Extract Role Name and ID
| project RoleId, RoleName;
// Process direct assignments
let directAssignments = roleAssignments
| join kind=inner users on $left.PrincipalId == $right.ObjectID // Join with EntraUsers on PrincipalId
| join kind=inner roles on $left.AppRoleIdStr == $right.RoleId // Join with roles to get Role Names
| project UserPrincipalName, DisplayName, CreatedDateTime, RoleName, AssignmentType = "Direct", SnapshotDate;
// Process group-based assignments
let groupAssignments = roleAssignments
| join kind=inner EntraGroupMembership on $left.PrincipalId == $right.GroupId // Join with Group Membership
| mvexpand Members // Expand group members
| extend MembersStr = tostring(Members) // Convert member ID to string
| distinct MembersStr, CreatedDateTime, AppRoleIdStr, SnapshotDate // Get distinct values
| join kind=inner users on $left.MembersStr == $right.ObjectIDStr // Join with EntraUsers for user details
| join kind=inner roles on $left.AppRoleIdStr == $right.RoleId // Join with roles for role names
| project UserPrincipalName, DisplayName, CreatedDateTime, RoleName, AssignmentType = "Group", SnapshotDate;
// Combine results from direct and group-based assignments
directAssignments
| union groupAssignments
範例 2:使用 Entra 資料來建置基本稽核者報告,其中顯示誰可在這兩個日期之間存取應用程式
此報告提供誰在兩個日期之間對目標應用程式進行過什麼存取和存取時間的檢視,並可用於安全性稽核、合規性驗證,以及瞭解組織內的存取模式。
此查詢會以 Microsoft Entra ID 內的特定應用程式為目標,並分析兩個日期之間的角色指派。 查詢會從 AppRoleAssignments 資料表擷取直接角色指派,並將此資料與 EntraUsers 資料表中的使用者詳細資料和 AppRoles 資料表中的角色資訊合併。
// Set the date range and service principal ID for the query
let startDate = datetime('2024-01-01');
let endDate = datetime('2024-03-14');
let servicePrincipalId = "<your service principal-id>";
// Query AppRoleAssignments for the specified service principal within the date range
AppRoleAssignments
| where ResourceId == servicePrincipalId and
todatetime(CreatedDateTime) between (startDate .. endDate)
// Extend AppRoleId to a string for joining
| extend AppRoleIdStr = tostring(AppRoleId)
// Project the necessary fields for the join with EntraUsers and AppRoles
| project PrincipalId, AppRoleIdStr, CreatedDateTime
// Join with EntraUsers to get user details
| join kind=inner (EntraUsers | project UserPrincipalName, DisplayName, ObjectID) on $left.PrincipalId == $right.ObjectID
// Join with AppRoles to get the role display names
| join kind=inner (
AppRoles | mvexpand AppRoles | project RoleIdStr = tostring(AppRoles.Id), RoleDisplayName = tostring(AppRoles.DisplayName)
) on $left.AppRoleIdStr == $right.RoleIdStr
// Final projection of the report with the current date and time
| project UserPrincipalName, DisplayName, RoleDisplayName, CreatedDateTime, ReportDate = now()
範例 3:在兩個資料快照集日期之間,將使用者新增至應用程式
這些報告所提供的檢視是有關哪些使用者獲給定目標應用程式在兩個日期之間的應用程式角色指派。 這些報告可以用來追蹤一段時間的應用程式存取變更。
此查詢會以 Microsoft Entra ID 內的特定應用程式為目標,並變更為開始與結束日期之間的角色指派。
// Define the date range and service principal ID for the query
let startDate = datetime("2024-03-01");
let endDate = datetime("2024-03-14");
let servicePrincipalId = "<your service principal-id>";
let earlierDate = startDate; // Update this to your specific earlier date
AppRoleAssignments
| where SnapshotDate < endDate and ResourceId == servicePrincipalId
| project PrincipalId, AppRoleId2 = tostring(AppRoleId), CreatedDateTime
| join kind=anti (
AppRoleAssignments
| where SnapshotDate < earlierDate and ResourceId == servicePrincipalId
| project PrincipalId, AppRoleId1 = tostring(AppRoleId)
) on PrincipalId
| join kind=inner (EntraUsers) on $left.PrincipalId == $right.ObjectID
| join kind=inner (AppRoles
| mvexpand AppRoles
| project AppRoleId=tostring(AppRoles.Id), RoleDisplayName=tostring(AppRoles.DisplayName)
) on $left.AppRoleId2 == $right.AppRoleId
| project UserPrincipalName, DisplayName, RoleDisplayName, CreatedDateTime, PrincipalId, Change = "Added"
範例 4:合併 Entra 與第二個來源 (例如 SQL 匯出) 中的應用程式指派,以建立已在兩個日期之間存取 Salesforce 之所有使用者的報告 (Entra 指派和本機指派)
此報告說明如何結合兩個不同的系統的數據,以在 Azure 數據總管中建立自定義報表。 其會將使用者、其角色和其他屬性的相關資料從兩個系統彙總成統一格式,以進行分析或報告。
// Define the date range and service principal ID for the query
let startDate = datetime("2023-06-01");
let endDate = datetime("2024-03-13");
let servicePrincipalId = "<your service principal-id>";
// Pre-process AppRoleAssignments with specific filters and projections
let processedAppRoleAssignments = AppRoleAssignments
| where ResourceId == servicePrincipalId and todatetime(CreatedDateTime) between (startDate .. endDate)
| extend AppRoleId = tostring(AppRoleId)
| project PrincipalId, AppRoleId, CreatedDateTime, ResourceDisplayName; // Exclude DeletedDateTime and keep ResourceDisplayName
// Pre-process AppRoles to get RoleDisplayName for each role
let processedAppRoles = AppRoles
| mvexpand AppRoles
| project AppRoleId = tostring(AppRoles.Id), RoleDisplayName = tostring(AppRoles.DisplayName);
// Main query: Process EntraUsers by joining with processed role assignments and roles
EntraUsers
| join kind=inner processedAppRoleAssignments on $left.ObjectID == $right.PrincipalId // Join with role assignments
| join kind=inner processedAppRoles on $left.AppRoleId == $right.AppRoleId // Join with roles to get display names
// Summarize to get the latest record for each unique combination of user and role attributes
| summarize arg_max(AccountEnabled, *) by UserPrincipalName, DisplayName, tostring(EmployeeId), Department, JobTitle, ResourceDisplayName, RoleDisplayName, CreatedDateTime
// Final projection of relevant fields including source indicator and report date
| project UserPrincipalName, DisplayName, EmployeeId=tostring(EmployeeId), Department, JobTitle, AccountEnabled=tostring(AccountEnabled), ResourceDisplayName, RoleDisplayName, CreatedDateTime, Source="EntraUsers", ReportDate = now()
// Union with processed salesforceAssignments to create a combined report
| union (
salesforceAssignments
// Project fields from salesforceAssignments to align with the EntraUsers data structure
| project UserPrincipalName = UserName, DisplayName = Name, EmployeeId = tostring(EmployeeId), Department, JobTitle, AccountEnabled = "N/A", ResourceDisplayName = AppName, RoleDisplayName = Role, CreatedDateTime, Source = "salesforceAssignments", ReportDate = now()
)