教程:Azure 数据资源管理器 (ADX) 中使用 Microsoft Entra ID 中的数据的自定义报告

本教程介绍如何使用 Microsoft Entra ID 中的数据在 Azure 数据资源管理器 (ADX) 中创建自定义报告。 本教程补充了其他报告选项,例如使用 Azure Monitor 和权利管理进行存档和报告,其侧重于导出审核日志数据以保留更长时间和进行分析。 相比之下,将 Microsoft Entra ID 数据导出到 Azure 数据资源管理器为创建自定义报告提供了更大灵活性,因为它允许从具有巨大可伸缩性的多个源、灵活架构和保留策略进行数据聚合。

此报告说明了如何显示从 Microsoft Entra 导出的配置、用户和访问权限,以及从其他来源导出的数据,例如具有 SQL 数据库的应用程序。 然后,可以使用 Kusto 查询语言 (KQL) 根据组织的要求生成自定义报告。 如果需要在较长时间内保留访问数据、执行临时调查或需要对用户访问数据运行自定义查询,则在 Azure 数据资源管理器中生成这些类型的报告可能会特别有用。

请执行以下步骤来创建这些报告:

  1. 在 Azure 订阅中设置 Azure 数据资源管理器
  2. 使用 PowerShell 脚本和 MS Graph 从 Microsoft Entra 和第三方数据库或应用程序中提取数据
  3. 将数据导入 Azure 数据资源管理器,这是一项快速且可缩放的数据分析服务。
  4. 使用 Kusto 查询语言生成自定义查询

在本教程结束时,你将掌握使用 Microsoft 支持的工具跨不同应用程序开发用户访问权限的自定义视图的技能。

先决条件

  • 确保你拥有所需的权限。 需要适当的权限才能导出要使用的 Entra 数据类型,并且需要保存导出的 JSON 文件的权限。

    • 用户数据:全局管理员、特权角色管理员、用户管理员
    • 组数据:全局管理员、特权角色管理员、组管理员
    • 应用程序/应用角色分配:全局管理员、特权角色管理员、应用程序管理员、云应用程序管理员
  • 必须将 PowerShell 设置为允许 User.Read.All、Group.Read.All、Application.Read.All 和 Directory.Read.All。 有关其他信息,请参阅 Microsoft Graph 权限参考

  • 确保对要在其中安装所需 MS Graph PowerShell 模块的目录以及导出的 Entra 数据的保存位置具有写入访问权限。

  • 确定要在报告中包含的数据。 本文中的脚本提供了一些示例,其中包含来自 Entra 的用户、组和应用程序的特定数据。 这些示例旨在说明可以使用此方法生成的报告类型,但具体报告需求可能会有所不同,需要不同或额外的数据。

步骤 1:设置 Azure 数据资源管理器

如果以前尚未使用过 Azure 数据资源管理器,则需要先进行设置。 无需 Azure 订阅或信用卡或者需要 Azure 订阅的完整群集即可创建免费群集。 请参阅快速入门:创建 Azure 数据资源管理器群集和数据库以开始使用。

步骤 2:使用 PowerShell 连接到 MS Graph 并提取 Entra 数据

安装 MS Graph PowerShell 模块连接到 MS Graph

  1. 安装所需的 MS Graph 模块。 本教程需要以下模块: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
   } 
  1. 导入模块:
  $modules = @('Microsoft.Graph.Users', 'Microsoft.Graph.Groups', 'Microsoft.Graph.Applications', 'Microsoft.Graph.DirectoryObjects') 
  foreach ($module in $modules) { 
  Import-Module -Name $module 
  } 
  1. 连接到 Microsoft Graph
  Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All" 

此命令将提示使用 MS Graph 凭据登录。 选择所需权限:登录后,如果是第一次连接或需要新权限,则可能需要同意所需权限。

用于提取在 ADX 中生成自定义报告所需的数据的 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 文件。 我们将在步骤 3 将此数据导入 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 

获取组数据

生成包含组名称和 ID 的 JSON 文件,该文件将用于在 ADX 中创建自定义视图。 此示例将包含所有组,但如果需要,可以包含其他筛选。 如果要筛选以仅包含某些组,则可能需要在脚本中包含逻辑来检查嵌套组。

    # 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 文件,该文件将用于在 ADX 中创建自定义视图。

    # 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 文件。 我们将在步骤 3 将此数据导入 ADX,这样我们便能够基于此数据生成与应用程序相关的自定义报告。

    # 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 文件。 导入 ADX 后,我们将利用此数据为用户生成涉及应用角色分配的报告。

    # 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 数据资源管理器

在步骤 3 中,我们将导入新创建的 JSON 文件以供进一步分析。 如果尚未设置 Azure 数据资源管理器,请参阅上面的步骤 1。

Azure 数据资源管理器是一种功能强大的数据分析工具,高度可缩放且灵活,为生成自定义的用户访问报告提供了理想的环境。 ADX 使用 Kusto 查询语言 (KQL)。

设置数据库后,请按照以下步骤将数据导出到 ADX。

  1. 右键单击数据库名称并选择“获取数据
  2. 选择“新表”并输入要导入的 JSON 文件的名称。例如,如果要导入 EntraUsers.json,请将表命名为 EntraUsers。 第一次导入后,该表将已存在,可以将其选择为导入的目标表。
  3. 选择 JSON 文件。
  4. ADX 会自动检测架构并提供预览。 单击“完成”以创建表并导入数据。
  5. 针对在步骤 1 中生成的每个 JSON 文件执行步骤 1-4。

步骤 4:使用 ADX 生成自定义报告

借助 ADX 中现有的数据,可以根据业务需求开始创建自定义报告。 以下查询提供了常见报告的示例,但你可以自定义这些报告来满足需求并创建其他报告。

示例 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 分配和本地分配)

此报告说明了如何合并两个单独系统中的数据来在 ADX 中创建自定义报告。 它将有关用户、其角色和其他属性的数据从两个系统聚合为统一格式进行分析或报告。

// 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() 
) 

后续步骤