Onlinedienste mit Microsoft Graph automatisieren (2)
Datenfilter
von Florian Frommherz
Veröffentlicht in Ausgabe 07/2021 - PRAXIS
Im ersten Teil des Workshops haben wir behandelt, wie Abfragen an Microsoft Graph strukturiert sind und wie der Graph Explorer dabei hilft. Im zweiten Teil erstellen wir ein Skript, das Graph zum Abruf von Daten nutzt und diese auch filtern kann. Ein wichtiger Aspekt ist dabei die Sicherheit, aber auch der Umgang mit sehr großen Ergebnismengen will gekonnt sein.
Nachdem wir zunächst Ad-hoc-Anfragen an Graph via Graph Explorer umgesetzt haben, wollen wir im nächsten Schritte ein einfaches Skript erstellen. Dieses soll ohne Benutzerinteraktion oder Abfrage von Benutzername oder Passwort Aktionen gegen die API ausführen. Dabei kommt die PowerShell zum Einsatz, da sie eine Single-Sign-on-Bibliothek mitbringt, die die Herausforderungen in Sachen Token und moderne Authentifizierung für Sie übernimmt.
Das Skript soll nach allen externen Identitäten im Tenant suchen, die via Azure AD B2B hineingekommen sind. Dann sortieren wir die Externen nach denen, die der Einladung schon mal gefolgt sind und sich lange nicht angemeldet haben, und denen, die der Einladung noch gar nicht gefolgt sind. Beide Personenkreise sollen automatisiert in eine Gruppe wandern, die einem entsprechend Verantwortlichen erlaubt, die Personen zu untersuchen.
Die Materialliste für das Beispielskript ist nicht allzu lang: Sie erstellen eine "Application Registration" im Azure AD (AAD), die mit einem Service Principal (SP) daherkommt. Dieser erlaubt, die Anwendung im AAD zu verwalten und API-Berechtigungen in Graph zuzuweisen. Sie erteilen der Anwendung außerdem für den Zugriff Ihren Segen – in der modernen Protokollsprache mit OAuth2.0 "Consent" genannt. Damit das Skript sich mit dem Service Principal als Dienstkonto beim Azure AD anmelden kann, müssen Sie noch ein Zertifikat generieren. Bevor dann die Skriptarbeit beginnen kann, laden Sie noch das PowerShell-Modul "MSAL.PS" herunter, das die Anmeldung mit dem Zertifikat ohne Benutzerinteraktion für Sie erledigt, und installieren es. Dazu zeigen wir später mehr Details.
Nachdem wir zunächst Ad-hoc-Anfragen an Graph via Graph Explorer umgesetzt haben, wollen wir im nächsten Schritte ein einfaches Skript erstellen. Dieses soll ohne Benutzerinteraktion oder Abfrage von Benutzername oder Passwort Aktionen gegen die API ausführen. Dabei kommt die PowerShell zum Einsatz, da sie eine Single-Sign-on-Bibliothek mitbringt, die die Herausforderungen in Sachen Token und moderne Authentifizierung für Sie übernimmt.
Das Skript soll nach allen externen Identitäten im Tenant suchen, die via Azure AD B2B hineingekommen sind. Dann sortieren wir die Externen nach denen, die der Einladung schon mal gefolgt sind und sich lange nicht angemeldet haben, und denen, die der Einladung noch gar nicht gefolgt sind. Beide Personenkreise sollen automatisiert in eine Gruppe wandern, die einem entsprechend Verantwortlichen erlaubt, die Personen zu untersuchen.
Die Materialliste für das Beispielskript ist nicht allzu lang: Sie erstellen eine "Application Registration" im Azure AD (AAD), die mit einem Service Principal (SP) daherkommt. Dieser erlaubt, die Anwendung im AAD zu verwalten und API-Berechtigungen in Graph zuzuweisen. Sie erteilen der Anwendung außerdem für den Zugriff Ihren Segen – in der modernen Protokollsprache mit OAuth2.0 "Consent" genannt. Damit das Skript sich mit dem Service Principal als Dienstkonto beim Azure AD anmelden kann, müssen Sie noch ein Zertifikat generieren. Bevor dann die Skriptarbeit beginnen kann, laden Sie noch das PowerShell-Modul "MSAL.PS" herunter, das die Anmeldung mit dem Zertifikat ohne Benutzerinteraktion für Sie erledigt, und installieren es. Dazu zeigen wir später mehr Details.
Das Skriptkonto erstellen
Damit das Skript selbstständig mit Azure AD für die Anmeldung und Berechtigungen und anschließend mit Graph interagieren kann, benötigen Sie ein Dienstkonto, das schon erwähnte Service Principal. Dieser interagiert entweder im Kontext eines Benutzers mit Cloudressourcen oder ist selbstständig als Daemon in der Cloud unterwegs.
Sie legen das SP für unser Beispiel am besten als Anwendung im AAD an. Als Global Administrator oder Application Administrator im AAD-Portal folgen Sie "App registrations" zu "+ New registration". Als Anzeigename tragen Sie "External User Skript" ein und nutzen die Voreinstellung "Accounts in this organizational directory only". Mit "Register" erstellen Sie die Anwendung. Das Portal wechselt automatisch auf die Übersichtsseite der neuen Anwendung. Klicken Sie nun auf "API permissions" und "+ Add a permission". Unter "Microsoft Graph" und "Application permissions" wählen Sie die folgenden Berechtigungen aus: User.Read.All, AuditLog.Read.All, Organization.Read.All, GroupMem- ber.ReadWrite.All – und sofern Sie das Skript die Gruppe erstellen lassen wollen, ebenfalls Group.Create. Anschließend bestätigen Sie die Berechtigungen und geben die Admin-Zustimmung via "Grant admin consent for <Tenant Name>".
Das Consent-Framework stammt nicht speziell aus dem Azure AD, sondern ist Teil des OAuth2.0-Standards. Wenn Anwendungen im Kontext des Benutzers oder für Automation ohne Benutzeranmeldung mit APIs oder Ressourcen interagieren, benötigen sie Berechtigungen. Diese erhält die Ressource oder API als "Scopes" über das Zugriffstoken, das der Identity Provider der Anwendung ausstellt. Die Anwendung muss nicht nur in das AAD integriert sein, Sie müssen zudem die Scopes bestimmen und akzeptieren. Die API oder Ressource bestimmt dann die Scopes, die Benutzer oder Anwendungen aufrufen können und sind im AAD als Teil des Onboardings hinterlegt.
Müssen Sie Ihre Anwendung einmal anpassen, etwa um auf die Mailbox eines Benutzers zugreifen zu können, lässt sich die App nicht einfach umschreiben. Vielmehr gilt es, auch die Definition der App im AAD zu ändern, damit dort der neue Scope "Mail.Read" hinterlegt ist, selbst wenn bereits andere Zugriffsberechtigungen für beispielsweise die Graph-API vorliegen.
Einige Scopes benötigen einen "Admin Consent", wenn es um Unternehmensdaten oder einen sensitiven Zugriff geht – etwa für "Mail.Read", wenn E-Mails in allen Mailboxen anstelle nur der eigenen gelesen werden sollen. Dies ist auch der Fall, wann immer die API oder Ressource einen Zugriff als "kritisch" definiert, beispielsweise das Lesen von Auditlog-Einträgen oder der Änderung der Dienstkonfiguration in Exchange, SharePoint oder Teams.
Sichere Anmeldung des Service Principal
Damit das Service Principal im Skript funktioniert, muss es
sich am Azure AD anmelden können. Das geht entweder über ein langes, komplexes
Passwort oder über ein Private-Public-Schlüsselpaar. Wir empfehlen eindeutig das
asymmetrische Schlüsselpaar, das Sie auf einem Rechner mit der PowerShell
anlegen und dann den öffentlichen Teil nach AAD hochladen. Den privaten Teil
geben Sie dem Benutzer, der das Skript ausführt, oder importieren es auf die
Maschine im Maschinenkontext, die das Skript ohne Interaktion starten können
soll (Listing 1).
Das Listing erstellt ein Zertifikat mit privatem und öffentlichem Schlüssel, das
ein Jahr gültig ist. Die PFX-Datei, die den privaten und öffentlichen Teil
enthält, landet in "C:\temp". Am Schluss gibt das Listing den Fingerabdruck des
Zertifikats aus, den Sie sich kopieren sollten. Der private Schlüssel bleibt auf
der Maschine, die das Skript später ausführt, den öffentlichen Teil – also die
CER-Datei, die Sie aus dem PFX erhalten – kopieren Sie nach Azure. Klicken Sie
dazu im Anwendungsobjekt für das Skript auf "Certificates & secrets" und danach
auf "Upload certificate". Die CER-Datei extrahieren Sie aus dem PFX, in dem Sie
das PFX im Benutzer- oder Maschinenkontext importieren und dann die Microsoft
Management Console (MMC) aufrufen, das "Certificates"-Applet starten und das Zertifikat ohne privaten Schlüssel exportieren.
Ist das öffentliche Zertifikat erfolgreich im AAD gelandet, importieren Sie das
Zertifikat mit dem privaten Schlüssel auf den Computern in den "Personal"-Store
von Windows, von dem aus Sie das Skript aufrufen wollen. Läuft das Skript im
Maschinenkontext, sollten Sie es im "Personal"-Store des Computers hinterlegen,
andernfalls im Benutzerkontext, wenn jemand das Skript manuell startet.
Graph mit
AAD-Daten verbinden
Sofern nicht schon vorhanden, müssen Sie nun die PowerShell-Library für AAD-SSO
laden. Das funktioniert mit jedem Code-Editor, der die PowerShell unterstützt –
wenn Sie Visual Studio Code besitzen, ist das PowerShell-Modul als
Integrationspaket verfügbar. Nutzen Sie die PowerShell in einem anderen Editor,
laden Sie das Paket über die PowerShell-Gallery wie folgt herunter:
Install-Module MSAL.PS
Sollten Sie eine Warnung nach der Installation erhalten, dass "PowerShellGet"
aktualisiert oder installiert werden muss, starten Sie dessen Installation
ebenfalls:
Install-Module Nuget -Force
Install-Module PowerShellGet -Force
Das MSAL.PS-Modul ist ein Community-Modul, das zwar nicht von Microsoft stammt,
dessen Entwickler es aber regelmäßig mit Aktualisierungen versorgen. Es erspart
Ihnen eine Menge Code, da das Modul mit dem Get-MSALToken-Befehl ein
Zugriffs-Token vom AAD für Graph abholt und die Authentifizierung via Zertifikat
für Sie transparent durchführt. Ist das Modul installiert, importieren Sie es.
Speichern Sie den Fingerabdruck des zuvor erstellten Zertifikats in die
$thumb-Variable und erfragen Sie dann für Graph einen Token, wie in diesem
Listing:
Die Client-ID und die Tenant-ID finden Sie in der Übersichtsseite der Anwendung,
die Sie für die Graph-Aufrufe im Azure-AD-Admin-Portal erstellt haben – beide
Werte sind GUIDs. Wenn Sie längere Skripte schreiben, lohnt es sich, beide Werte
entweder am Anfang des Skripts als Variablen zu hinterlegen oder in eine
Konfigurationsdatei auszulagern, die das Skript dann liest. Beide GUIDs sind
keine Geheimnisse, sondern dienen nur der Identifizierung des Service Principals
im richtigen Tenant – die Authentifizierung findet mit dem Zertifikat statt.
Erhalten Sie keine Fehlermeldungen von "Get-MSALToken", sollte die Variable
"$accessToken" einen Inhalt aufweisen:
Sie können das Access Token nun nutzen, um eine Anfrage an Graph zu stellen.
Dabei übergeben Sie das frisch erhaltene Access Token im Header als
"Authorization"-Teil. Da Graph eine HTTP-basierte API ist, formulieren Sie Ihre
Anfrage in der PowerShell auch als "WebRequest", für den Sie dann eine Antwort
erhalten. Für einen ersten Test fragen Sie nach allen Benutzern – am besten
speichern Sie "$url = https://graph.microsoft.com/v1.0/ users/" als Variable,
wonach Sie eine Reihe von Ergebnissen erhalten:
Für eine Überprüfung geben Sie "$response" aus. Das $users-Objekt enthält Daten
zur HTTPS-Antwort der Anfragen, die Nutzdaten liegen im Property "Content". Sie
sehen zusätzlich zu den Nutzdaten auch weitere Felder, wie den HTTP-Statuscode
oder die Beschreibung dazu. Auch sehen Sie, wie lang die Antwort war. Diese
Felder dienen der Fehlersuche und dem Troubleshooting. Wenn Sie einen
Statuscode von "4XX" oder "5XX" erhalten, ist etwas faul.
Benötigen Sie die Steuerdaten nicht zwingend, können Sie auch das
Invoke-RestRequest-Cmdlet verwenden, das die identische Parametrisierung wie das
Invoke-WebRequest-Kommando nutzt. Doch im Gegensatz zu Letzterem abstrahiert das
Invoke-RestRequest-Cmdlet die Steuerfelder in HTML und liefert Ihnen nur die
relevanten Anfrage- und Antwortdaten.
Graph gibt die angefragten Benutzer als Content im JSON-Format zurück, weshalb
Sie mit der PowerShell einfach die Antwort konvertieren und dann durchlaufen
können:
$users = ConvertFrom-JSON
$response.Content
$users.Value | % {Write-Host
$_.displayName }
Wie üblich in der PowerShell können Sie, wenn die Anfrage mehrere Resultate
beinhaltet, die Resultats-Objekte mit einer Foreach-Schleife durchlaufen lassen.
Damit haben Sie erfolgreich Graph nach Daten gefragt und die Antwort
ausgewertet. Ihnen ist dabei sicher aufgefallen, dass das Setup nicht einmal
nach einem Passwort oder einer Anmeldung fragt. Das Skript oder die Anwendung
läuft in diesem Fall dank Authentifzierung via Zertifikat also autonom und ohne
Benutzer-interaktion. Sie übergeben die zuvor mit dem Graph Explorer erstellte
Graph-Anfrage am besten als Variable an das Invoke-WebRequest-Cmdlet, das dann
die Anfrage auslöst. Mit dem Parameter "Method" steuern Sie, so wie mit dem
Graph Explorer, ob Sie lesen, erstellen, überschreiben oder löschen wollen.
Damit sind die Voraussetzungen für unser Beispielskript gegeben.
Damit das Skript sich am Azure AD anmelden kann, müssen Sie vorher ein Zertifikat erstellen.
Mit Paging umfangreiche
Ergebnisse managen
Erstellen Sie Anfragen, die potentiell viele Ergebnisse liefern, sollten Sie
sich über das Paging Gedanken machen. Damit erlauben Sie Graph, die Ergebnisse
in mehrere Pakete aufzuteilen, die Sie nach und nach abrufen und bearbeiten.
Damit müssen Sie nicht alle Resultate gleichzeitig über das Internet
herunterladen. Dabei gibt Graph sowieso automatisch für alle Anfragen nicht mehr
als 100 Resultate zurück. Wollen Sie über die ersten 100 hinaus, müssen Sie dem
Link in "@odata.nextLink" folgen, den Sie als Teil der Antwort erhalten. Solange
in "@odata.nextLink" ein Link zu finden ist, gibt es mehr Ergebnisse. Um die
nächsten Ergebnisse abzurufen, nutzen Sie Listing 2.
Listing 2: Mehr Graph-Ergebnisse abrufen
while($users.‘@odata.nextLink‘) {
$nextURL = $users.’@odata.nextLink’
$nextResponse = Invoke-WebRequest -UseBasicParsing -headers @{Authorization = "Bearer $($accessToken.AccessToken)" } -Uri $nextUrl -Method Get
$users = ConvertFrom-JSON $nextResponse.Content
<Code für was sonst noch passieren soll.>
<$allResults += $users.Value>
}
Mit diesem Code landen alle Ergebnisse in der Variable "$allResults". Um die
Ergebnisse paketweise zu verarbeiten (oder Sie gar nicht alle zu benötigen),
bauen Sie im "while"-Loop eine Ausstiegsklausel ein, die greift, sobald Sie das
Gesuchte finden.
Um selbst Paging zu nutzen, signalisieren Sie Graph mit dem Kommando "$top=X" in
der Anfrage, wie viele Ergebnisse Sie pro Antwort erhalten möchten. Ersetzen Sie
das X durch eine Zahl, zum Beispiel 10 per "https://graph.microsoft. com/v1.0/
users/?$top=10", erhalten Sie genau zehn Resultate. Benötigen Sie das elfte oder
weitere Resultate, müssen Sie erneut dem Link in "@odata.nextLink" folgen.
Das Skript entwickeln
Den Kopf unseres Skripts haben wir bereits beisammen: Sie importieren zunächst
"MSAL.PS", lesen das Zertifikat ein und setzen Client-ID und Tenant-ID. Nun
rufen Sie zunächst alle externen B2B-Gäste in Ihrem Tenant ab. Um ein paar Bytes
zu sparen und nicht alle Properties für alle gefundenen Benutzer anzufragen,
schränken Sie die benötigten Attribute mit "$select" ein:
Um die Anfrage zunächst zu prüfen und auszuprobieren, welches die richtigen
Attribute sind, nutzen Sie den Graph Explorer. Das Beispiel von eben liefert
Ihnen die externen Identitäten zurück, die Sie dann hinsichtlich des letzten
Anmeldedatums inspizieren. Suchen Sie nach Externen, die sich in den letzten 60
Tagen nicht angemeldet haben, müssen Sie zwei Daten miteinander vergleichen: Ist
das letzte Anmeldedatum neuer oder älter als heute vor 60 Tagen? Die Variable
"$cutoffDate" hält das Datum von vor 60 Tagen:
Das letzte Anmeldedatum ist in "sign-InActivity" in einem Unterattribut
"lastSignInDateTime" zu finden. Sie vergleichen damit in einer Foreach-Schleife
für jeden externen Benutzer das letzte Anmeldedatum. Da das B2B-System im Azure
AD über Einladungen funktioniert, die Externe annehmen müssen, sollten Sie auch
überprüfen, ob noch nie angemeldete Personen gerade erst eingeladen worden sind.
Trifft dies zu, sollten Sie diese von weiteren Maßnahmen vorerst ausnehmen. Wenn
Sie die Codeteile von vorhin weiterverwenden, lassen Sie alle Externen mithilfe
von Paging über eine Foreach-Schleife durchlaufen: foreach($external in
$allresults) {}. Der Code in der Schleife sieht etwa so aus wie in Listing 3.
Listing 3: Externe im AAD sortieren
if(($external.signInActivity.lastSignInDateTime -eq $null) -or ($external.signInActivity.lastSignInDateTime -eq ""))
{
if(($external.externalUserState -eq "PendingAcceptance") -and ($external.externalUserStateChangeDateTime -gt $cutOffDate))
{
##Diese Benutzer haben die Einladung noch nicht akzeptiert. Überspringen…
Continue;
}
#Diese Benutzer haben sich nicht angemeldet. Wir sollten sie in eine Gruppe packen
Add-ExternalHasNotSignedInGroup $external.id
}
else
{
if($external.signInActivity.lastSignInDateTime -lt $cutOffDate)
{
##diese Externen haben sich schon länger nicht mehr angemeldet.
Add-ExternalHasNotSignedInGroup $external.id
}
}
Für die Fälle, in denen ein Administrator tätig werden muss, rufen Sie die
Add-ExternalHasNotSignedInGroup-Funktion mit der Objekt-ID der externen
Identität als Parameter auf. Sie könnten das Hinzufügen der
Gruppenmitgliedschaft auch gleich als Teil der Foreach-Schleife durchführen,
eine eigene Funktion dafür ist aber in der Regel sauberer.
Im Funktionsblock der Add-ExternalHasNotSignedInGroup-Funktion, die die
Objekt-ID des externen Benutzerkontos als Übergabewert erhält, fügen Sie
folgenden Code ein:
Sie schicken einen POST-Request, um die Mitgliedschaft einer Gruppe zu ändern
und den externen Benutzer als neues Gruppenmitglied der Gruppe, die sich hinter
der "$groupID" verbirgt, hinzuzufügen. Im Body der POST-Anfrage steht die
Referenz des Benutzerobjektes, das als Mitglied hinzugefügt werden soll: Es ist
der Graph-Pfad zum externen Benutzerobjekt, repräsentiert durch die Objekt-ID,
die zuvor in der Schleife identifiziert wurde.
Um bei jedem Skript-Durchlauf eine neue Gruppe zu erstellen, in die Sie die
Externen einfügen, modifizieren Sie im obigen Code die Anfrage-URL zu
"https://graph.microsoft.com/v1.0/groups". Zudem muss der Body entsprechend alle
Attribute in einer JSON-Formatierung beinhalten, die für die Erstellung
notwendig sind. Im ersten Teil dieses Artikels haben Sie eine neue Gruppe mit
Mitgliedern und Besitzern mit Graph Explorer erstellt, dessen Request-Body-Teil
und die Einstellungen Sie übernehmen können. Voraussetzung hierfür ist
allerdings, dass Sie in Ihrer Anwendung auch den Scope "Group. Create"
konfiguriert und als Admin zugestimmt haben.
Fazit
Mit ein paar Vorkehrungen und dem richtigen Werkzeug lassen sich blitzschnell
mit ein paar Zeilen Code Aufgaben mit Microsoft Graph automatisieren. Wenn Sie
die MSAL.PS-Library für SSO nutzen, müssen Sie sich um moderne Authentifizierung
ebenfalls keine Sorgen machen. Ein Skript-Editor und der Graph Explorer bilden
ein gutes Duo, Schritt für Schritt aus Adhoc-Anfragen wertvolle und effiziente
Skripte zu erstellen.