Руководство. Создание настраиваемых отчетов в Azure Data Explorer с помощью данных из Microsoft Entra
В этом руководстве вы узнаете, как создавать настраиваемые отчеты в Azure Data Explorer с помощью данных из служб управления идентификаторами Microsoft Entra и Microsoft Entra ID Management.
В этом руководстве рассматриваются такие варианты подготовки отчетов, как архивация и отчеты с помощью Azure Monitor и управление правами доступа, которые сосредоточены на экспорте журнала аудита в Azure Monitor для хранения и анализа. По сравнению с экспортом данных идентификатора Microsoft Entra в Azure Data Explorer предоставляется гибкость для создания пользовательских отчетов для объектов Microsoft Entra, включая исторические и удаленные объекты.
Использование Azure Data Explorer также позволяет агрегирование данных из дополнительных источников с массовой масштабируемостью, гибкими схемами и политиками хранения. Azure Data Explorer особенно полезно, если необходимо хранить данные доступа пользователей в течение многих лет, выполнять нерегламентированные исследования или запускать пользовательские запросы к данным доступа.
В этом руководстве показано, как показать конфигурацию, пользователей и права доступа, экспортированные из Microsoft Entra вместе с данными, экспортируемыми из других источников, например приложениями с правами доступа в собственных базах данных SQL. Затем вы можете использовать язык запросов Kusto (KQL) в Azure Data Explorer для создания пользовательских отчетов на основе требований вашей организации.
Изучив это руководство, вы:
- Настройте Azure Data Explorer в подписке Azure или создайте бесплатный кластер.
- Извлеките данные из идентификатора Microsoft Entra с помощью сценариев PowerShell и Microsoft Graph.
- Создайте таблицы и импортируйте данные из идентификатора Microsoft Entra в Azure Data Explorer.
- Извлеките данные из системы управления идентификаторами Microsoft Entra ID.
- Создайте таблицы и импортируйте данные из системы управления идентификаторами Microsoft Entra в Azure Data Explorer.
- Создание пользовательского запроса с помощью KQL.
- Запрос данных в Azure Monitor.
В конце этого руководства вы сможете разрабатывать настраиваемые представления прав доступа и разрешений пользователей. Эти представления охватывают несколько приложений с помощью поддерживаемых Корпорацией Майкрософт средств. Вы также можете передавать данные из баз данных или приложений, отличных от Майкрософт, чтобы сообщить об этих правах доступа и разрешениях.
Необходимые условия
Если вы не знакомы с Azure Data Explorer и хотите узнать о сценариях, приведенных в этом руководстве, вы можете получить бесплатный кластер Azure Data Explorer. Для использования в рабочей среде с соглашением об уровне обслуживания для Azure Data Explorer требуется подписка Azure для размещения полного кластера Azure Data Explorer.
Определите, какие данные необходимо включить в отчеты. Скрипты в этом руководстве содержат примеры с определенными данными пользователей, групп и приложений из Microsoft Entra. Эти примеры иллюстрируют типы отчетов, которые можно создать с помощью этого подхода, но конкретные потребности в отчетах могут отличаться и требовать разные или дополнительные данные. Вы можете начать с этих объектов и с течением времени использовать другие виды объектов Microsoft Entra.
В этом руководстве показано получение данных из Microsoft Entra в качестве пользователя, вошедшего в систему. Для этого убедитесь, что у вас есть необходимые назначения ролей. Вам нужны роли с правильными разрешениями для экспорта типов данных Microsoft Entra, с которыми вы хотите работать:
- Пользовательские данные: глобальный администратор, администратор привилегированных ролей, администратор пользователей
- Групповые данные: глобальный администратор, администратор привилегированных ролей, администратор группы
- Назначения ролей приложений и приложений: глобальный администратор, администратор привилегированных ролей, администратор приложений, администратор облачных приложений
Microsoft Graph PowerShell требует согласия на получение объектов Microsoft Entra через Microsoft Graph. В примерах этого руководства требуются делегированные разрешения
User.Read.All
,Group.Read.All
,Application.Read.All
иDirectory.Read.All
. Если вы планируете получить данные с помощью автоматизации без вошедшего пользователя, то вместо этого получите согласие на соответствующие разрешения приложения. Для получения дополнительной информации см. Справочник по разрешениям Microsoft Graph.Если вы еще не предоставили microsoft Graph PowerShell согласие на эти разрешения, необходимо быть глобальным администратором для выполнения этой операции согласия.
В этом руководстве не показаны настраиваемые атрибуты безопасности. По умолчанию глобальный администратор и другие роли администратора не включают разрешения на чтение настраиваемых атрибутов безопасности от пользователей Microsoft Entra. Если вы планируете получить настраиваемые атрибуты безопасности, может потребоваться больше ролей и разрешений.
На компьютере, на котором установлен Microsoft Graph PowerShell, убедитесь, что у вас есть доступ на запись в каталог файловой системы. В этом каталоге устанавливаются необходимые модули Microsoft Graph PowerShell и где сохраняются экспортированные данные Microsoft Entra.
Убедитесь, что у вас есть разрешения на получение данных из других источников данных за пределами Microsoft Entra, если вы также хотите включить эти данные в Azure Data Explorer.
Настройка Azure Data Explorer
Если вы ранее не использовали Azure Data Explorer, необходимо сначала настроить его. Вы можете создать бесплатный кластер без подписки Azure или кредитной карты. Вы также можете создать полный кластер, для которого требуется подписка Azure. Чтобы приступить к работе, см. Краткое руководство: Создание кластера и базы данных Azure Data Explorer.
Извлечение данных идентификатора Microsoft Entra с помощью PowerShell
В этом разделе вы устанавливаете модули Microsoft Graph PowerShell. В PowerShell подключиться к Microsoft Graph для извлечения данных идентификатора Microsoft Entra.
При первом использовании этих модулей в этом сценарии для вашей организации необходимо иметь роль глобального администратора, чтобы разрешить Microsoft Graph PowerShell предоставить согласие на использование в вашем клиенте. Последующие взаимодействия могут использовать роль с более низким уровнем привилегий.
Откройте PowerShell.
Если у вас нет всех модулей Microsoft Graph PowerShell, уже установлены, установите необходимые модули Microsoft 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. В этом разделе руководства показано чтение пользователей, групп и приложений, поэтому требуются области прав доступа
User.Read.All
,Group.Read.All
,Application.Read.All
иDirectory.Read.All
. Дополнительные сведения о разрешениях см. в справочнике по разрешениям Microsoft Graph.Connect-MgGraph -Scopes "User.Read.All", "Group.Read.All", "Application.Read.All", "Directory.Read.All" -ContextScope Process -NoWelcome
Эта команда запрашивает вход с помощью учетных данных Microsoft Entra. После входа может потребоваться согласие на необходимые разрешения, если это первый раз при подключении, или если требуются новые разрешения.
Запросы PowerShell для извлечения данных идентификатора Microsoft Entra для пользовательских отчетов
Следующие запросы извлекают данные идентификатора Microsoft Entra из Microsoft Graph с помощью PowerShell и экспортируют данные в JSON-файлы. Эти файлы импортируются в Azure Data Explorer в последующем разделе этого руководства.
Ниже приведены сценарии создания отчетов с таким типом данных:
- Аудитор хочет увидеть отчет, в котором перечислены участники 10 групп, сгруппированные по отделам.
- Аудитор хочет просмотреть отчет всех пользователей, имеющих доступ к приложению между двумя датами.
Вы также можете перенести данные в Azure Data Explorer из источников за пределами Microsoft Entra. Сценарий для этой возможности может быть следующим:
- Администратор хочет просмотреть всех пользователей, добавленных в приложение, из идентификатора Microsoft Entra и их прав доступа в собственном репозитории приложения, например базы данных SQL.
Эти типы отчетов не встроены в идентификатор Microsoft Entra. Однако эти отчеты можно создавать самостоятельно, извлекая данные из идентификатора Microsoft Entra и объединяя данные с помощью пользовательских запросов в Azure Data Explorer. В этом руководстве описан этот процесс далее в разделе "Перенос данных из других источников".
В этом руководстве вы извлекаете данные идентификатора Microsoft Entra из следующих областей:
- Сведения о пользователе, такие как отображаемое имя, основное имя пользователя (UPN) и сведения о должности.
- Сведения о группах, включая их членство
- Приложения и назначения ролей приложений
Этот набор данных позволяет выполнять широкий набор запросов вокруг того, кто получил доступ к приложению, со сведениями о роли приложения и связанным временем. Помните, что это примеры запросов, и данные и конкретные требования могут отличаться от того, что показано здесь.
Примечание.
Более крупные клиенты могут столкнуться с ограничением скорости и ошибками 429, которые обрабатывает модуль Microsoft Graph. Azure Data Explorer также может ограничить размер отправки файлов.
В этих скриптах PowerShell вы экспортируете выбранные свойства из объектов Microsoft Entra в JSON-файлы. Данные из этих экспортированных свойств используются для создания пользовательских отчетов в Azure Data Explorer.
Следующие свойства включены в эти примеры, так как мы используем эти данные для иллюстрации типов отчетов, которые можно создать в Azure Data Explorer. Так как конкретные потребности в отчетах могут отличаться от того, что показано в этом руководстве, следует включить в эти скрипты определенные свойства, которые вы хотите просмотреть в отчетах. Однако вы можете следовать тому же шаблону, который показан для создания ваших скриптов.
Выберите дату снимка
Жёстко заданный моментальный снимок идентифицирует данные в JSON-файле с фиксированной датой. Его можно использовать для отслеживания аналогичных наборов данных с течением времени в Azure Data Explorer. Дата моментального снимка также полезна для сравнения изменений данных между двумя датами моментального снимка.
$SnapshotDate = Get-Date -AsUTC -Format "yyyy-MM-dd"
Получение пользовательских данных Microsoft Entra
Этот скрипт экспортирует выбранные свойства из пользовательского объекта Microsoft Entra в JSON-файл. Этот файл и дополнительные данные из других JSON-файлов импортируются в Azure Data Explorer в последующем разделе этого руководства.
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"] = $SnapshotDate
[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 Data Explorer. Пример включает все группы, но при необходимости можно включить дополнительную фильтрацию. Если вы фильтруете только определенные группы, может потребоваться включить логику в скрипт, чтобы проверить наличие вложенных групп.
# 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 Data Explorer. Пример включает все группы, но при необходимости можно включить дополнительную фильтрацию.
# Retrieve all groups from Microsoft Entra ID
$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
SnapshotDate = $SnapshotDate
}
# 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 Data Explorer в следующем разделе этого руководства, чтобы создать пользовательские отчеты, относящиеся к приложениям на основе этих данных.
# Fetch applications and their corresponding service principals, and 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
SnapshotDate = $SnapshotDate
}
} | ConvertTo-Json -Depth 10 | Set-Content "Applications.json"
Получение данных роли приложения
Создайте JSON-файл всех ролей приложений для корпоративных приложений в Microsoft Entra. После импорта этих данных в Azure Data Explorer вы будете использовать его для создания отчетов, включающих назначения ролей приложения для пользователей.
# Get a list of all applications, and 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 by using its app ID
$spFilter = "appId eq '$($app.AppId)'"
$sp = Get-MgServicePrincipal -Filter $spFilter | Select-Object -First 1
# Process app roles, 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
SnapshotDate = $SnapshotDate
}
}
# Export the results to a JSON file
$results | ConvertTo-Json -Depth 4 | Out-File 'AppRoles.json'
Получение данных о назначении ролей приложения
Создайте 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 necessary
$result += [PSCustomObject]@{
AppRoleId = $_.AppRoleId
CreatedDateTime = $createdDateTime
PrincipalDisplayName = $_.PrincipalDisplayName
PrincipalId = $_.PrincipalId
ResourceDisplayName = $_.ResourceDisplayName
ResourceId = $_.ResourceId
SnapshotDate = $SnapshotDate
}
}
}
$result | ConvertTo-Json -Depth 10 | Out-File "AppRoleAssignments.json"
Создание таблиц и импорт JSON-файлов с данными из идентификатора Microsoft Entra в Azure Data Explorer
В этом разделе вы импортируете только что созданные JSON-файлы для служб идентификатора Microsoft Entra в виде таблиц в Azure Data Explorer для дальнейшего анализа. При первом импорте через веб-интерфейс Azure Data Explorer вы создаете таблицы на основе схем, которые веб-интерфейс предлагает из каждого JSON-файла.
Войдите в пользовательский веб-интерфейс Azure Data Explorer.
Перейдите в базу данных, настроенную в кластере Azure Data Explorer или бесплатном кластере, приведенном ранее в этом руководстве.
В меню слева выберите запрос.
Выполните следующие действия для каждого экспортированного JSON-файла, чтобы получить экспортированные данные в базу данных Azure Data Explorer в качестве новой таблицы:
Щелкните правой кнопкой мыши имя базы данных, в которую необходимо загрузить данные. Затем выберите Получить данные.
Выберите источник данных из доступного списка. В этом руководстве вы загружаете данные из локального файла, поэтому выберите Локальный файл.
Выберите + Создать таблицу и введите имя таблицы в зависимости от имени импортируемого ФАЙЛА JSON. Например, если вы импортируете EntraUsers.json, назовите таблицу EntraUsers. После первого импорта таблица уже существует, и ее можно выбрать в качестве целевой таблицы для последующего импорта.
Выберите Обзор файлов, выберите JSON-файл и выберите Далее.
Azure Data Explorer автоматически обнаруживает схему и предоставляет предварительный просмотр на вкладке "Проверка". Выберите "Готово", чтобы создать таблицу и импортировать данные из этого файла. После приема данных выберите Закрыть.
Повторите предыдущие шаги для каждого из json-файлов, созданных в предыдущем разделе.
В конце этих действий таблицы EntraUsers
, EntraGroups
, EntraGroupMembership
, Applications
, AppRoles
и AppRoleAssignments
в базе данных.
Извлечение данных управления идентификаторами Microsoft Entra с помощью PowerShell
В этом разделе описано, как использовать PowerShell для извлечения данных из служб управления идентификаторами Майкрософт. Если у вас нет системы управления идентификаторами Microsoft Entra, Microsoft Entra ID P2 или Microsoft Entra Suite, перейдите в раздел Использование Azure Data Explorer для создания пользовательских отчетов.
Для выполнения следующих действий может потребоваться установить модули Microsoft Graph PowerShell для извлечения данных системы управления идентификаторами Microsoft Entra. При первом использовании этих модулей в вашей организации необходимо иметь роль Глобального администратора, чтобы Microsoft Graph PowerShell мог предоставить согласие на использование в вашей аренде. Последующие взаимодействия могут использовать роль с более низким уровнем привилегий.
Откройте PowerShell.
Если у вас нет всех модулей Microsoft Graph PowerShell, уже установлены, установите необходимые модули Microsoft Graph. Для этого раздела учебника требуется следующий модуль:
Microsoft.Graph.Identity.Governance
. Если у вас уже установлены модули, перейдите к следующему шагу.$modules = @('Microsoft.Graph.Identity.Governance') foreach ($module in $modules) { Install-Module -Name $module -Scope CurrentUser -AllowClobber -Force }
Импорт модулей в текущий сеанс PowerShell:
$modules = @('Microsoft.Graph.Identity.Governance') foreach ($module in $modules) { Import-Module -Name $module }
Подключение к Microsoft Graph. В этом разделе руководства показано, как получать данные из управления правами доступа и обзоров доступа, поэтому требуется наличие области разрешений
AccessReview.Read.All
иEntitlementManagement.Read.All
. Для других случаев использования отчетов, например, для управления привилегированными идентификациями (PIM) или рабочих процессов жизненного цикла, обновите параметрScopes
с необходимыми разрешениями. Дополнительные сведения о разрешениях см. в справочнике по разрешениям Microsoft Graph.Connect-MgGraph -Scopes "AccessReview.Read.All, EntitlementManagement.Read.All" -ContextScope Process -NoWelcome
Эта команда запрашивает вход с помощью учетных данных Microsoft Entra. После входа может потребоваться согласие на необходимые разрешения, если это первый раз при подключении, или если требуются новые разрешения.
Запросы PowerShell для извлечения данных управления идентификаторами Microsoft Entra для пользовательских отчетов
Следующие запросы извлекают данные системы управления идентификаторами Microsoft Entra из Microsoft Graph с помощью PowerShell и экспортируют данные в JSON-файлы. Эти файлы импортируются в Azure Data Explorer в последующем разделе этого руководства.
Ниже приведены сценарии создания отчетов с таким типом данных:
- Отчеты об исторических проверках доступа.
- Отчеты о назначениях через управление правами доступа.
Получение данных определения расписания проверки доступа
Создайте JSON-файл с именами и идентификаторами определений обзора доступа, которые используются для создания пользовательских представлений в Azure Data Explorer. В этом примере содержатся все проверки доступа, но при необходимости можно включить дополнительную фильтрацию. Дополнительные сведения см. в разделе Использование параметра запроса фильтра.
$allsched = Get-MgIdentityGovernanceAccessReviewDefinition -All
$definitions = @()
# Iterate over each definition
foreach ($definition in $allsched) {
$definitions += [PSCustomObject]@{
Id = $definition.Id
DisplayName = $definition.DisplayName
SnapshotDate = $SnapshotDate
}
}
$definitions | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessReviewDefinitions.json"
Получение данных экземпляра для проверок доступа
Чтобы экспортировать все определения проверки доступа, экземпляры и решения в структурированный формат папок с помощью PowerShell, можно использовать API Microsoft Graph. Этот подход гарантирует, что данные упорядочены иерархически и соответствуют указанной структуре папок.
Прежде чем начать, помните о следующих аспектах:
- Убедитесь, что у вас есть необходимые разрешения для доступа к данным проверки в Microsoft Graph.
- В зависимости от объема данных время выполнения скрипта может отличаться. Отслеживайте процесс и настраивайте параметры по мере необходимости.
Чтобы получить данные экземпляра, выполните следующую команду.
Скачайте скрипт Export_Access_Reviews.ps1 и сохраните его локально.
В проводнике разблокируйте скрипт, чтобы его можно было запустить в PowerShell.
Выполните следующую команду. Результат помещает все данные в три папки:
ReviewInstances
,ReviewInstanceDecisionItems
иReviewInstanceContactedReviewers
..\ExportAccessReviews.ps1 -InstanceStartDate "11/15/2024" -InstanceEndDate "12/15/2024" -ExportFolder "C:\AccessReviewsExport\11_15_to_12_15"
Получение данных пакета доступа для управления правами
Создайте JSON-файл с именами пакетов доступа и идентификаторами, которые используются для создания пользовательских представлений в Azure Data Explorer. В этом примере содержатся все пакеты доступа, но при необходимости можно включить дополнительную фильтрацию.
$accesspackages1 = Get-MgEntitlementManagementAccessPackage -All
$accesspackages2 = @()
# Iterate over each access package
foreach ($accesspackage in $accesspackages1) {
$accesspackages2 += [PSCustomObject]@{
Id = $accesspackage.Id
DisplayName = $accesspackage.DisplayName
SnapshotDate = $SnapshotDate
}
}
$accesspackages2 | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessPackages.json"
Получение данных назначения пакета доступа для управления правами
Создайте JSON-файл с назначениями для доступа к пакетам, которые используются для создания пользовательских представлений в Azure Data Explorer. Пример включает все задания, которые доставлены, но если необходимо, можно включить дополнительную фильтрацию.
$apassignments1 = Get-MgEntitlementManagementAssignment -ExpandProperty target,accessPackage -filter "state eq 'Delivered'" -all
$apassignments2 = @()
# Iterate over each access package assignment
foreach ($assignment in $apassignments1) {
$apassignments2 += [PSCustomObject]@{
Id = $assignment.Id
ScheduleStartDateTime = $assignment.Schedule.StartDateTime -replace "\\/Date\((\d+)\)\\/", '$1'
AccessPackageId = $assignment.AccessPackage.Id
AccessPackageDisplayName = $assignment.AccessPackage.DisplayName
TargetId = $assignment.Target.Id
TargetDisplayName = $assignment.Target.DisplayName
TargetEmail = $assignment.Target.Email
TargetObjectId = $assignment.Target.ObjectId
TargetPrincipalName = $assignment.Target.PrincipalName
TargetSubjectType = $assignment.Target.SubjectType
SnapshotDate = $SnapshotDate
}
}
$apassignments2 | ConvertTo-Json -Depth 10 | Set-Content "EntraAccessPackageAssignments.json"
Создание таблиц и импорт JSON-файлов с данными из системы управления идентификаторами Microsoft Entra в Azure Data Explorer
В этом разделе вы импортируете только что созданные JSON-файлы для служб управления идентификаторами Microsoft Entra в Azure Data Explorer для дальнейшего анализа. Эти файлы присоединяются к данным, которые вы уже импортировали для служб идентификатора Microsoft Entra. При первом импорте через веб-интерфейс Azure Data Explorer вы создаете таблицы на основе схем, которые веб-интерфейс предлагает из каждого JSON-файла.
Войдите в пользовательский веб-интерфейс Azure Data Explorer.
В вашем кластере Azure Data Explorer или бесплатном кластере перейдите в базу данных, содержащую данные Microsoft Entra ID.
В меню слева выберите запрос .
Выполните следующие действия для каждого экспортированного JSON-файла, чтобы получить экспортированные данные в базу данных Azure Data Explorer в качестве новой таблицы:
Щелкните правой кнопкой мыши имя базы данных, в которую необходимо загрузить данные. Затем выберите Получить данные.
Выберите источник данных из доступного списка. В этом руководстве вы загружаете данные из локального файла, поэтому выберите Локальный файл.
Выберите + Создать таблицу и введите имя таблицы в зависимости от имени импортируемого ФАЙЛА JSON. После первого импорта таблица уже существует, и ее можно выбрать в качестве целевой таблицы для последующего импорта.
Выберите Обзор файлов, выберите JSON-файл и выберите Далее.
Azure Data Explorer автоматически обнаруживает схему и предоставляет предварительный просмотр на вкладке "Проверка ". Выберите Готово, чтобы создать таблицу и импортировать данные из этого файла. После приема данных выберите Закрыть.
Повторите предыдущие шаги для каждого из файлов JSON, созданных в предыдущем разделе, для каждой папки.
Если в папке много файлов, вы можете использовать
lightingest
для импорта остальных файлов после создания таблицы.
В итоге выполнения этих шагов у вас в базе данных появляются таблицы EntraAccessReviewDefinitions
, EntraAccessPackages
, EntraAccessPackageAssignments
, ReviewInstances
, ReviewInstanceDecisionItems
и ReviewInstanceContactedReviewers
, в дополнение к тем, которые вы создавали ранее.
Создание пользовательских отчетов с помощью Azure Data Explorer
С данными, теперь доступными в Azure Data Explorer, вы готовы начать создание адаптированных отчетов на основе бизнес-требований.
Войдите в пользовательский веб-интерфейс Azure Data Explorer.
В меню слева выберите запрос.
В следующих запросах приведены примеры общих отчетов, но эти отчеты можно настроить в соответствии с потребностями и создать дополнительные отчеты.
Вы также можете просматривать отчеты в Excel, выбрав вкладку Экспорт и выбрав Открыть в Excel.
Пример: Создание назначений ролей в приложении для прямых и групповых назначений на определенную дату моментального снимка
В этом отчете представлено представление о том, кто имел доступ к целевому приложению и когда. Его можно использовать для аудита безопасности, проверки соответствия требованиям и понимания шаблонов доступа в организации.
Следующий запрос предназначен для конкретного приложения в идентификаторе Microsoft Entra и анализирует назначения ролей по определенной дате. Запрос извлекает как прямые, так и групповые назначения ролей. Он объединяет эти данные с сведениями о пользователе из таблицы EntraUsers
и ролей из таблицы AppRoles
. В запросе задайте переменной targetSnapshotDate
значение snapshotDate
, которое вы использовали при загрузке данных.
/// 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 a string for easier comparison
// Prepare user data from the EntraUsers table
let users = EntraUsers
| project ObjectID, UserPrincipalName, DisplayName, ObjectIDStr = tostring(ObjectID); // Include ObjectID as string for joining
// Prepare role data from the 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 the member ID to a 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
Пример. Создание базового отчета аудитора с помощью данных Microsoft Entra, показывающих, кто имел доступ к приложению между двумя датами
В этом отчете представлено представление о том, кто имел доступ к целевому приложению между двумя датами. Его можно использовать для аудита безопасности, проверки соответствия требованиям и понимания шаблонов доступа в организации.
Следующий запрос предназначен для конкретного приложения в идентификаторе Microsoft Entra и анализирует назначения ролей между двумя датами. Запрос извлекает прямые назначения ролей из таблицы 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()
Пример: Просмотр пользователей, добавленных в приложение между двумя датами снимка данных.
Эти отчеты предоставляют информацию о том, какие пользователи получили назначение роли приложения целевому приложению в период между двумя датами. Эти отчеты можно использовать для отслеживания изменений в доступе к приложению с течением времени.
Этот запрос предназначен для конкретного приложения в идентификаторе Microsoft Entra и изменения назначений ролей между датами начала и окончания:
// 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"
Пример. Различные запросы, использующие проверки доступа
Просмотр сведений о завершении проверки доступа и сроках выполнения.
После отправки данных используйте следующие запросы Kusto для просмотра:
Когда был завершен последний цикл проверки доступа? Сколько времени это займет?
ReviewInstances | summarize LastCompletedDate = max(ReviewInstanceEndDateTime), ReviewDuration = datetime_diff('minute', max(ReviewInstanceEndDateTime), min(ReviewInstanceStartDateTime))
Выполняется ли процесс проверки доступа вовремя (например, ежеквартально)?
ReviewInstances | extend ExpectedFrequency = "Quarterly" // Replace with the organization's frequency | summarize ReviewsCompleted = count(), LastReviewEndDate = max(ReviewInstanceEndDateTime) | extend CurrentDate = now(), TimeSinceLastReview = datetime_diff('day', now(), LastReviewEndDate) | extend IsOnSchedule = iff(TimeSinceLastReview <= 90, "Yes", "No") // Assuming quarterly = 90 days
Просмотр участия и вовлеченности в проверку доступа
Кто были назначенными рецензентами?
ReviewInstanceContactedReviewers | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = DisplayName, ReviewerUserPrincipalName = UserPrincipalName, CreatedDateTime
Какие рецензенты активно участвовали и предоставили ответы?
ReviewInstanceDecisionItems | where ReviewedBy_DisplayName != "AAD Access Reviews" | where Decision in ("Approve", "Deny") | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = ReviewedBy_DisplayName, ReviewerUserPrincipalName = ReviewedBy_UserPrincipalName, Decision, ReviewedDateTime | distinct AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName, ReviewerUserPrincipalName, Decision
Какой процент рецензентов ответил на запрос проверки доступа?
let TotalReviewers = ReviewInstanceContactedReviewers | summarize Total = dcount(Id) by AccessReviewDefinitionId, AccessReviewInstanceId; let RespondedReviewers = ReviewInstanceDecisionItems | where ReviewedBy_DisplayName != "AAD Access Reviews" | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" | where Decision in ("Approve", "Deny") | summarize Responded = dcount(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId; TotalReviewers | join kind=leftouter RespondedReviewers on AccessReviewDefinitionId, AccessReviewInstanceId | extend Responded = coalesce(Responded, 0) // Replace null with 0 for Responded | extend NotResponded = Total - Responded // Calculate the number of nonresponders | extend ResponsePercentage = (Responded * 100.0) / Total // Percentage of those who responded | extend NonResponsePercentage = (NotResponded * 100.0) / Total // Percentage of those who didn't respond | project AccessReviewDefinitionId, AccessReviewInstanceId, Total, Responded, ResponsePercentage, NotResponded, NonResponsePercentage
Когда каждый рецензент выполнил свои задачи?
ReviewInstanceDecisionItems | where Decision in ("Approve", "Deny") | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerName = ReviewedBy_DisplayName, ReviewerUserPrincipalName = ReviewedBy_UserPrincipalName, ReviewedDateTime
Какие рецензенты не принимали никаких решений?
let AllReviewers = ReviewInstanceContactedReviewers | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerId = Id, ReviewerUserPrincipalName = UserPrincipalName, ReviewerName = DisplayName; let ActiveReviewers = ReviewInstanceDecisionItems | where Decision in ("Approve", "Deny") | where ReviewedBy_DisplayName != "AAD Access Reviews" | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" | summarize ActiveReviewers = make_set(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId; AllReviewers | extend ReviewerId = tostring(ReviewerId) // Ensure ReviewerId is a string | join kind=leftanti ( ActiveReviewers | mv-expand ActiveReviewers | extend ActiveReviewers = tostring(ActiveReviewers) // Cast ActiveReviewers to a string ) on $left.ReviewerId == $right.ActiveReviewers | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerUserPrincipalName, ReviewerName
Какой процент рецензентов не взаимодействовал?
let TotalReviewers = ReviewInstanceContactedReviewers | summarize Total = dcount(Id) by AccessReviewDefinitionId, AccessReviewInstanceId; let RespondedReviewers = ReviewInstanceDecisionItems | where ReviewedBy_DisplayName != "AAD Access Reviews" | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" | where Decision in ("Approve", "Deny") | summarize Responded = dcount(ReviewedBy_Id) by AccessReviewDefinitionId, AccessReviewInstanceId; TotalReviewers | join kind=leftouter RespondedReviewers on AccessReviewDefinitionId, AccessReviewInstanceId | extend Responded = coalesce(Responded, 0) // Replace null with 0 for Responded | extend NotResponded = Total - Responded // Calculate the number of nonresponders | extend ResponsePercentage = (Responded * 100.0) / Total // Percentage of those who responded | extend NonResponsePercentage = (NotResponded * 100.0) / Total // Percentage of those who didn't respond | project AccessReviewDefinitionId, AccessReviewInstanceId, Total, Responded, ResponsePercentage, NotResponded, NonResponsePercentage
Были ли напоминания отправлены для рецензентов, не отвечающих, или ожидающих решений?
// Step 1: Get the list of all reviewers let TotalReviewers = ReviewInstanceContactedReviewers | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerId = Id, ReviewerUserPrincipalName = UserPrincipalName, ReviewerName = DisplayName; // Step 2: Get the list of reviewers who responded let RespondedReviewers = ReviewInstanceDecisionItems | where ReviewedBy_DisplayName != "AAD Access Reviews" | where ReviewedBy_Id != "00000000-0000-0000-0000-000000000000" | where Decision in ("Approve", "Deny") | project AccessReviewDefinitionId, AccessReviewInstanceId, RespondedReviewerId = ReviewedBy_Id; // Step 3: Get the list of review instances let ReviewInstancesWithDetails = ReviewInstances | project AccessReviewDefinitionId = ReviewDefinitionId, AccessReviewInstanceId = ReviewInstanceId, RemindersSent = ReviewDefinitionSettings_ReminderNotificationsEnabled, StartDate = todatetime(ReviewInstanceStartDateTime), EndDate = todatetime(ReviewInstanceEndDateTime) | extend ReminderSentDate = iif(RemindersSent, StartDate + (EndDate - StartDate) / 2, datetime(null)); // Step 4: Identify nonresponsive reviewers and join with review instance details TotalReviewers | join kind=leftouter (ReviewInstancesWithDetails) on AccessReviewDefinitionId, AccessReviewInstanceId | join kind=leftanti RespondedReviewers on $left.ReviewerId == $right.RespondedReviewerId | project AccessReviewDefinitionId, AccessReviewInstanceId, ReviewerUserPrincipalName, ReviewerName, RemindersSent, ReminderSentDate
Просмотр пользователей и изменений доступа, которые являются результатом проверок доступа
Кто потерял доступ к определенным ресурсам во время проверки доступа?
ReviewInstanceDecisionItems | where Decision == "Deny" | project User = Principal_DisplayName, Resource = Resource_DisplayName, Decision, Justification
Были ли пользователи помечены из-за неактивности?
ReviewInstanceDecisionItems | where Insights contains "inactive" | project User = Principal_DisplayName, Resource = Resource_DisplayName, Insights, Decision
Какова дата удаления доступа и причина потери доступа?
ReviewInstanceDecisionItems | where Decision == "Deny" | project User = Principal_DisplayName, Resource=Resource_DisplayName, AccessRemovalDate = AppliedDateTime, Reason = Justification
Какие пользователи не приняли решений?
ReviewInstanceDecisionItems | where Decision == "NotReviewed" | project User = Principal_DisplayName, Resource=Resource_DisplayName
Какие обзоры не имели рецензентов?
ReviewInstances | join kind=leftanti ( ReviewInstanceContactedReviewers | summarize by AccessReviewInstanceId ) on $left.ReviewInstanceId == $right.AccessReviewInstanceId
Какие отзывы не имели пользователей?
ReviewInstances | join kind=leftanti ( ReviewInstanceDecisionItems | summarize by AccessReviewInstanceId ) on $left.ReviewInstanceId == $right.AccessReviewInstanceId
Сводка решений о проверке доступа
Какие решения были приняты пользователями: утверждены, отклонены или не изменены?
ReviewInstanceDecisionItems | summarize count() by Decision
Какое количество пользователей было утверждено или отказано в доступе?
ReviewInstanceDecisionItems | summarize ApprovedCount = countif(Decision == "Approve"), DeniedCount = countif(Decision == "Deny")
Были ли задокументированы причины утверждения?
ReviewInstanceDecisionItems | where Decision == "Approve" and isnotempty(Justification) | summarize count() by ReviewedBy_DisplayName
Подтверждение качества проверки доступа и проверка полноты
Рассматривались ли отмены доступа для неактивных пользователей?
ReviewInstanceDecisionItems | where Insights contains "inactive" and Decision == "Deny" | project User = Principal_DisplayName, Decision
Был ли доступ, который не был должным образом удален?
ReviewInstanceDecisionItems | where ApplyResult != "New" and ApplyResult != "AppliedSuccessfully"
Задокументировали ли рецензенты свои решения?
ReviewInstanceDecisionItems | where isnotempty(Justification) | summarize count() by ReviewedBy_DisplayName
Были ли примечания записаны для каждого пользователя?
ReviewInstanceDecisionItems | where isnotempty(Justification) | project User = Principal_DisplayName, Resource = Resource_DisplayName, Comments = Justification
Настройка текущих импортов
В этом руководстве иллюстрируется процесс однократного извлечения, преобразования и загрузки данных (ETL) для заполнения Azure Data Explorer единственным снимком для целей отчетности. Для текущих отчетов или сравнения изменений с течением времени можно автоматизировать процесс заполнения Azure Data Explorer из Microsoft Entra, чтобы база данных продолжала иметь текущие данные.
Вы можете использовать Azure Automation, облачную службу Azure, для размещения скриптов PowerShell, необходимых для извлечения данных из Microsoft Entra ID и Microsoft Entra ID Governance. Дополнительные сведения см. в статье Автоматизация задач управления идентификаторами Майкрософт с помощью службы автоматизации Azure и Microsoft Graph.
Вы также можете использовать функции Azure или средства командной строки, такие как lightingest
для привлечения данных и заполнения существующей таблицы. Дополнительные сведения см. в статье Использование LightIngest для приема данных в Azure Data Explorer.
Например, чтобы загрузить файл EntraAccessPackages.json
в текущем каталоге в таблицу EntraAccessPackages
в качестве пользователя, вошедшего в систему, используйте следующую команду:
az login
LightIngest.exe "https://ingest-CLUSTERHOSTNAME;Fed=True" -database:"DATABASE" -table:EntraAccessPackages -sourcepath:"." -pattern:"EntraAccessPackages.json" -format:multijson -azcli:true
Запрос данных в Azure Monitor
Если вы отправляете журналы аудита, входа или другие журналы Microsoft Entra в Azure Monitor, вы можете включить эти журналы из рабочей области Azure Monitor Log Analytics в ваши запросы. Дополнительные сведения о связи между Azure Monitor и Azure Data Explorer см. в статье Запрос данных в Azure Monitor с помощью Azure Data Explorer.
Войдите в Центр администрирования Microsoft Entra.
Выберите Параметры диагностики.
Выберите рабочую область Log Analytics, в которой вы отправляете журналы.
В обзоре рабочей области Log Analytics запишите идентификатор подписки, имя ресурса и имя рабочей области.
Войдите на портал Azure.
Перейдите к веб-интерфейсу Azure Data Explorer.
Убедитесь, что в списке указан кластер Azure Data Explorer.
Выберите + Добавить>подключение.
В окне Добавление подключения введите URL-адрес в рабочей области Log Analytics. URL-адрес формируется из имени узла, идентификатора подписки, имени группы ресурсов и имени рабочей области Azure Monitor Log Analytics, как описано в разделе Добавление рабочей области Log Analytics.
После установки подключения рабочая область Log Analytics появится на левой панели с собственным кластером Azure Data Explorer.
Выберите запрос, а затем выберите кластер Azure Data Explorer.
На панели запросов используйте таблицы Azure Monitor, содержащие журналы Microsoft Entra, в ваших запросах Azure Data Explorer. Например:
let CL1 = 'https://ade.loganalytics.io/subscriptions/*subscriptionid*/resourcegroups/*resourcegroupname*/providers/microsoft.operationalinsights/workspaces/*workspacename*'; cluster(CL1).database('*workspacename*').AuditLogs | where Category == "EntitlementManagement" and OperationName == "Fulfill access package assignment request" | mv-expand TargetResources | where TargetResources.type == 'AccessPackage' | project ActivityDateTime,APID = toguid(TargetResources.id) | join EntraAccessPackage on $left.APID == $right.Id | limit 100
Перенос данных из других источников
Вы можете создавать дополнительные таблицы в Azure Data Explorer для приема данных из других источников. Если данные хранятся в JSON-файле (аналогично приведенным выше примерам) или CSV-файле, можно создать таблицу во время первого получения данных из файла. После создания таблицы можно также использовать LightIngest для приема данных в Azure Data Explorer из JSON или CSV-файла.
Дополнительные сведения о приеме данных см. в обзоре приема данных Azure Data Explorer.
Например, объедините назначения приложений из Microsoft Entra и из второго источника, чтобы создать отчет обо всех пользователях, имевших доступ к приложению между двумя датами.
В этом отчете показано, как объединить данные из двух отдельных систем для создания пользовательских отчетов в Azure Data Explorer. Он объединяет данные о пользователях, их ролях и других атрибутах из двух систем в унифицированном формате для анализа или отчетности.
В следующем примере предполагается, что таблица с именем salesforceAssignments
была заполнена данными, поступающими из другого приложения. Таблица содержит столбцы UserName
, Name
, EmployeeId
, Department
, JobTitle
, AppName
, Role
и CreatedDateTime
.
// 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()
)