Pobieranie danych z Azure Active Directory przy pomocy Data Factory i Microsoft Graph

Bywają przypadki w których chcemy wyciągnąć dane z Active Directory. Może się to zdarzyć z wielu różnych powodów np. chcemy stworzyć zestawienie czy też raport na podstawie określonych danych lub  zaimplementować coś takiego jak Dynamic Security w Power BI tzn. filtrować dane na podstawie tabeli security zawierającej użytkowników i ich przypisanie do konkretnej grupy. Tego typu scenariusze powtarzają się dosyć często zarówno w środowisku on-premise jak i w chmurze, dlatego też w ramach dzisiejszego artykułu chciałbym pokazać Wam jak przy pomocy Azure Data Factory pobrać dane z Azure Active Directory przy pomocy Microsoft Graph API. Zapraszam do lektury!

O tym czym jest AAD oraz ADF raczej nie muszę mówić jednakże warto wspomnieć czym jest Microsoft Graph. W skrócie jest to interfejs umożliwiający dostanie się do różnych zasobów w ramach ekosystemu Microsoft  365. Poprzez REST API oraz udostępnione biblioteki jesteśmy w stanie się dostać do danych następujących usług dokumentacja link):

  • Microsoft 365 core services: Bookings, Calendar, Delve, Excel, Microsoft 365 compliance eDiscovery, Microsoft Search, OneDrive, OneNote, Outlook/Exchange, People (Outlook contacts), Planner, SharePoint, Teams, To Do, Workplace Analytics.
  • Enterprise Mobility and Security services: Advanced Threat Analytics, Advanced Threat Protection, Azure Active Directory, Identity Manager, and Intune.
  • Windows 10 services: activities, devices, notifications, Universal Print.
  • Dynamics 365 Business Central.

Mając te informacje możemy spróbować podłączyć się do enpointa Microsoft Graph znajdującego się pod adresem https://graph.microsoft.com z poziomu ADF. Zanim jednak to zrobimy warto przygotować sobie odpowiednie zapytanie pod postacią adresu URL w dedykowanym do tego celu narzędziu (link), które w przyjazny sposób jest w stanie pomóc nam przetestować konkretne zapytania bez konieczności pisania dodatkowego kodu albo użycia narzędzi trzecich takich jak Postman:

Jak możecie zauważyć na powyższym zrzucie ekranowym do narzędzia zalogowałem się własnym kontem AAD i mogłem odpytywać informacje zawarte właśnie w Active Directory. Bardzo pomocne narzędzie szczególnie, że ilość parametrów i opcji dostępnych przez Graph API jest naprawdę spora i zanim dojdziemy do pożądanego rezultatu z całą pewnością wykonamy mnóstwo prób.

Zanim wykonam próbę podłączenia od strony Data Factory muszę stworzyć sobie zestaw testowy po którym będę się poruszał. Moim zadaniem jest pobranie wszystkich użytkowników będących członkami określonej grupy. Moja grupa nazywa się “StandardUsers” i stworzyłem ją używając interfejsu graficznego AAD:

Do tej grupy będę chciał dodać konta użytkowników. Chciałbym żeby było ich trochę więcej bo aż tysiąc dlatego też z oczywistych powodów nie mogę posłużyć się interfejsem graficznym. Użyjemy zatem Powershella gdzie najpierw wygenerujemy sobie użytkowników, a potem dodamy ich do powyższej grupy:

Pierwszym krokiem jest instalacja modułu AzureAD:

Install-Module AzureAD

Następnie zalogujemy się w sposób interaktywny do mojego tenanta używając Connect-AzureAD (identyfikator swojego tenanta znajdziecie na zakładce Overview Azure Active Directory):

Connect-AzureAD -TenantId "deca7fa5-bef9-4134-9a26-e211fb2fe978"

W dalszej kolejności tworzymy zmienną, która jest obiektem typu PasswordProfile, a następnie do jej właściwości Password przypisujemy hasło :

 $PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
$PasswordProfile.Password = "Password987!!@#>"

Nasi użytkownicy będą mieli to samo hasło ale nie jest to żaden problem w opisywanym scenariuszu testowym. Dalej mamy pętle, która poprzez wykorzystanie komendy New-AzureADUser doda tysiąc użytkowników do mojego AAD:

$counter = 1
Do
{

Write-Host("testuser"+$counter+"@adrianchodkowskihotmail.onmicrosoft.com")

$DisplayName = "testuser"+$counter
$UserPrincipalName = "testuser"+$counter+"@adrianchodkowskihotmail.onmicrosoft.com"

New-AzureADUser -DisplayName $DisplayName -PasswordProfile $PasswordProfile -UserPrincipalName $UserPrincipalName -AccountEnabled $true -MailNickName $DisplayName

$counter = $counter +1

} While ($counter -le 1000)

Jeśli wszystko przebiegło zgodnie z planem to powinniśmy zobaczyć następujący widok:

To jeszcze nie wszystko bo Ci użytkownicy mają być członkiem grupy StandardUsers, mogłem oczywiście odpowiednio zmodyfikować powyższą pętlę aby odrazu dodać nowego użytkownika do grupy jednakże ja chciałem zrobić to w dwóch krokach. Pętla dodająca użytkowników do grupy wygląda następująco:

$counter = 1
Do
{

Write-Host("testuser"+$counter)

$UserName = "testuser"+$counter
$UserPrincipalName = "testuser"+$counter+"@adrianchodkowskihotmail.onmicrosoft.com"

$objectId = (Get-AzureAdUser -All $true |where DisplayName -EQ $UserName | select objectId ).objectId

Write-Host "$objectId"
Add-AzureADGroupMember -ObjectId "b5820545-4031-4ef0-80ac-49b1d66ca28b" -RefObjectId $objectId

$counter = $counter +1

} While ($counter -le 1000)

Weryfikacja nie powinna sprawić problemu zarówno z poziomu kodu jak i interfejsu graficznego:

Mamy nasz zbiór testowy dlatego też przejdźmy dalej i powiedzmy sobie dwa słowa o uprawnieniach. Chcę aby mój Data Factory mógł odpytać API używając swojego Managed Identity. Dlatego też w AAD odnalazłem grupę Security Reader, która daje uprawnienia umożliwiające odpytywanie AAD i dodałem tam ADF:

Oczywiście w razie potrzeby możemy nadać nieco bardziej granularne uprawnienia jednak na moje potrzeby powyższe podejscie jest w porządku. Nasz testowy ADF składa się z trzech elementów:

  • Get Group info – Web Request, który pobierze informację o grupie StandardUsers jak np. id
  • Set request URL – zadanie, które zbuduje URL pobierający członków grupy StandardUsers na podstawie informacji zwróconych w poprzednim kroku,
  • Copy AAD data to SQL – copy activity kopiujący dane z MS Graph do bazy Azure SQL

Konfiguracja pierwszego kroku wygląda następująco:

Z powyższych opcji mamy trzy najważniejsze:

  • URL, który jest budowany na podstawie poniższego wyrażenia, które odpytuje MS Graph o odpowiednią grupę, a następnie wybiera dwie właściwości tj. id oraz displayName: https://graph.microsoft.com/v1.0/groups?$filter=startswith(displayName,’StandardUsers’)&$select=id,displayName
  • Authentication, którym w tym przypadku jest wspomniany wcześniej Managed Identity
  • Resource czyli adres endpointa Microsoft graph tj. https://graph.microsoft.com

Kolejny krok czyli budowa URL do odpytania członków danej grupy składa się z następującego wyrażenia:

@concat('https://graph.microsoft.com/v1.0/groups/',
activity('Get Group info').output.value[0].id,
'/members')

Mamy zatem zwykły adres URL z wrzuceniem identyfikatora grupy pobranego w poprzednim kroku. Ostatni najciekawszy krok do poraz kolejny podłączenie się do MS graph przez Rest API. Jako źródło przekazujemy zmienną z przypisaną wartością w poprzednim kroku:

Jako Sink ustawiamy Azure SQL – zwróćcie uwagę na opcję Auto create table – chce aby w miejscu docelowym tabela została utworzona:

W tym konkretnym przypadku ustawiłem również mapowanie ze względu na to, że zwracany JSON z REST API zawiera wiele głównych węzłów, a ja potrzebuje jedynie tych zawartych w węźle “value”:

Co do tego w jaki sposób stworzyłem poszczególne datasety oraz Linked Service nie będę tego tutaj pokazywał – zainteresowanych odsyłam do wcześniejszych wpisów na moim blogu. Mając tego typu konfigurację mogę uruchomić przepływ:

Efekt odczytamy z bazy prostym SELECT * FROM tabela:

Jak widać powyżej dane trafiły do bazy – wiele z atrybutów jest pustych jednakże wynika to po prostu z tego, że nie wypełniłem ich w moim skrypcie powershellowym. Ciekawą informacją jest to, że gdy zliczymy ilość wierszy jakie zostały wstawione to dostaniemy tylko 100:

Dzieje się tak dlatego, że zwracany do nas rezultat jest stronicowany. Jak podejrzymy surowego JSONa to zobaczymy, że mamy w nim wskazanie kolejnego adresu URL z kolejną porcją danych do pobrania:

Dlatego też w naszym Copy Activity musimy wskazać, że mamy do czynienia ze stronicowanym rezultatem. Zrobimy to dodając w sekcji Pagination rules następujący wpis:

W atrybucie AbsoluteUrl wskazujemy, że będziemy podawać adres URL, a w Value wskazujemy wyrażenie odnoszące się do węzła JSONa którego znaleźliśmy wyżej. Po wyczyszczeniu tabeli i uruchomieniu jej po raz kolejny dostaliśmy następujący rezultat:

Tysiąc wierszy czyli dokładnie tyle danych ile się spodziewaliśmy. Jak widzicie cały proces nie jest bardzo skomplikowany aczkolwiek ma kilka niuansów na które należy zwrócić uwagę. Powyższy przykład można oczywiście odpowiednio dostosować aby pobierać dane z wielu grup itp. jednakże dla kogoś kto płynnie porusza się w ADF nie powinno to stanowić problemu. Polecam zapoznać się z samymi możliwościami poprzez parametry requestów wysyłanych do Microsoft Graph – ich pełny opis znajdziecie tutaj.  Polecam również pobawić się Graph Explorerem bo jest to najlepsze narzędzie do rozpoczęcia pracy z MS Graph. Pozdrawiam!

 

 

Leave a Reply