Automatisieren der Benutzer:innen-Bereitstellung in Entra ID mit Microsofts neuer 'Inbound provisioning'-API.

Microsoft Entra ID API-gesteuerte Bereitstellung


Hinweis: Dieser Artikel wurde ursprünglich für evoila unter API-driven Provisioning with Microsoft Entra and Azure Logic Apps veröffentlicht.

Im August letzten Jahres hat Microsoft ein neues Feature “API-gesteuerte Bereitstellung” für ihre “Entra ID” Identity Management Lösung angekündigt1. Diese API ermöglicht das automatisierte Provisionieren von Benutzer:innen-Accounts, in Entra und Active directory, aus allen vertrauenswürdigen Datenquellen wie zum Beispiel einer HCM Software. API-gesteuerte Bereitstellung basiert auf dem SCIM-Standard2 und ist für die Verwendung in Kombination mit Entra Lebenszyklus-Workflows3 gedacht.

Der Bereitstellungsdatenfluss, wie in Abbilung 1 skizziert, beinhaltet:

  • Ein “Identitätsverzeichnis” welches Daten über interne und externe Mitarbeiter:innen beinhaltet. Üblicherweise ein HR/HCM-System.
  • Ein Automatisierungstool wie zum Beispiel PowerShell, Azure Runbooks oder Logic Apps, um Daten aus dem “Identitätsverzeichnis” zu laden, ein SCIM-Dokument zu erstellen und dieses an den API-Endpunkt zu senden.
  • Der Entra “Bereitstellungsservice” verarbeitet das SCIM-Dokument und erstellt, aktualisiert oder entfernt Objekte in Microsoft Entra oder Active Directory.
  • Wenn ein ‘on-premise’ Active Directory im Einsatz ist, ein lokal installierter “Bereitstellungsagent”.

Abbildung 1: Microsoft Entra API-gesteuerte Bereitstellung Datenfluss Abbildung 1: Microsoft Entra API-gesteuerte Bereitstellung Datenfluss

In diesem Artikel beschreiben wir eine einfache Implementierung der API-gesteuerten Bereitstellung mit Logic Apps. Es ist als Beispiel bzw. Ausgangspunkt für eine eigene Implementierung gedacht und basiert lose auf Microsofts “QuickStart with Azure Logic App”.4

Für Workday HCM und SuccessFactors bietet Microsoft eine eigene Integration an, die du nicht selbst implementieren musst:

Voraussetzungen

Lizenzierung

API-gesteuerte Bereitstellung erfordert Microsoft Entra ID P1 (früher “Azure Active Directory P1”) Lizenzen.

Azure Ressourcen

Für unser Beispiel benötigen wir einige Azure Ressourcen:

  • Eine Ressourcengruppe “rg-HcmProvisioning” als logischen Container für unsere Ressourcen.
  • Einen Azure Storage Account “sthcmprovisioning<NNNN>” mit einer storage table5 “employeedemo” für unser Beispieldatenset.
  • Eine Logic App “logic-HcmProvisioning” für unseren Workflow.

Um die Ressourcen anzulegen kannst du die Azure CLI, PowerShell oder das Portal verwenden. Das Erstellen der Logic App mit PowerShell oder der CLI benötigt eine “Workflowdefinition”6, die du im Anhang weiter unten findest.

Wir nutzen ein PowerShell Script, um die Ressourcen anzulegen:

$ProjectName = "HcmProvisioning"
$Location = "westeurope" # Azure region for our resources
$RandomId = Get-Random -Minimum -1000 -Maximum 9999 # We'll use this to make our storage account name globally unique
$StorageName = "st" + $ProjectName.ToLower() + $RandomId
$StorageTableName = "employeedemo"
$ResourceGroupName = "rg-$ProjectName"
$LogicAppName = "logic-$ProjectName"

# Create a resource group for our Azure resources
New-AzResourceGroup -Location $Location -Name $ResourceGroupName

# Create the storage account...
$StorageAccount = New-AzStorageAccount -ResourceGroupName $ResourceGroupName -Name $StorageName -Location $Location -SkuName "Standard_LRS"
# ... and our employee table
$StorageContext = $StorageAccount.Context
New-AzStorageTable -Name $StorageTableName -Context $StorageContext

# Create a Logic App, based on our "empty" workflow definition
$LogicApp = New-AzLogicApp -ResourceGroupName $ResourceGroupName -Name $LogicAppName -Location $Location -DefinitionFilePath "./workflow-definition.json"

HCM Datenquelle

Für das Beispiel verwenden wir die Azure Speichertabelle “employeedemo”, die wir eben im Azure Storage Account erstellt haben. Wir laden Microsofts Beispieldatenset von GitHub herunter und importieren es mit Azure Storage Explorer in die Tabelle.

Der Tabelleninhalt sieht nach dem Import so aus (reduziert auf die relevanten Spalten):

CompanyCostCenterDepartmentDevisionFirstNameFullNameLastName
WoodgroveCC3035SalesElectronicsTalyaTalya FleetaFleeta
WoodgroveCC3035SalesElectronicsMiriamMiriam DunwareDunware
WoodgroveCC3035Product EngineeringLogisticsGinnieGinnie FadimanFadiman
FabrikamCC5081SalesSalesGenevraGenevra MelonyMelony
WoodgroveCC3035ManufacturingPharmaNyssaNyssa RoscoeRoscoe

Microsoft Entra API-driven provisioning App

Um die “API-gesteuerte Bereitstellung” in einem Entra Tenant zu aktivieren muss eine “Enterprise Application” aus der Gallerie hinzugefügt werden. Melde dich im Microsoft Entra Admin Center an und Navigiere zu Identity > Applications > Enterprise applications.

Abbildung 2: Enterprise application hinzufügen Abbildung 2: Enterprise application hinzufügen

Wähle “New application”, gib “API-driven” ins Suchfeld ein und wähle anschließend die Applikation “API-driven provisioning to Microsoft Entra ID” aus. Behalte die Standardeinstellungen bei und klicke auf “Create”.

Abbildung 3: API-driven provisioning App suchen Abbildung 3: API-driven provisioning App suchen

Öffne die neu erstellte App und wechsle ins Menü “Provisioning” (Abb. 4). Klicke auf “Get started” in der “Provision User Accounts”-Kachel, setze den “Provisioning Mode” auf “Automatic” und speichere die Einstellungen.

Abbildung 4: Bereitstellung aktivieren Abbildung 4: Bereitstellung aktivieren

Nachdem die initiale Konfiguation erstellt wurde kannst links im Menü unter “Provisioning” (Abb. 5) zusätzliche Optionen sehen:

  • Mappings steuern die Zuordnung von Datenfeldern zwischen der Datenquelle und Microsoft Entra. Für unser Demo genügen die Standardeinstellungen.
  • Unter Settings sollte eine gültige E-Mail-Adresse für Benachrichtigungen eingetragen werden. Zusätzlich kann hier ein Grenzwert gesetzt werden, um unbeabsichtigtes Löschen vieler Benutzerkonten zu verhindern.

Abbildung 5: Bereitstellungseinstellungen Abbildung 5: Bereitstellungseinstellungen

Logic App Managed Identity

Die Azure Logic App wird ein “Service Principal” nutzen, um sich bei Microsoft Entra zu authentifizieren. Dazu aktivieren wir die “System-assigned Managed Identity”7 in unserer Logic App. Anschließend nutzen wir PowerShell, um der “Managed Identity” die nötigen MS Graph Berechtigungen zu erteilen.

Öffne die Logic App Ressource im Azure Portal und navigiere zu “Identity” (Abb. 6). Ändere den Status auf “On” und klicke auf “Save”, um eine Managed Identity zu erstellen. Die Managed Identity erhält denselben Namen wie die Logic App (“logic-HcmProvisioning”) in Entra.

Abbildung 6: Managed Identity aktivieren Abbildung 6: Managed Identity aktivieren

Im nächsten Schritt verwenden wir ein PowerShell Script und die MS Graph API, um dem Service Principal die nötigen Berechtigungen8 zu geben:

  • SynchronizationData-User.Upload erlaubt der Logic App, Daten an den Identity Synchronization Service hochzuladen.
  • AuditLog.Read.All erlaubt der Logic App, Logeinträge im AuditLog zu lesen.
Install-Module -Name "Microsoft.Graph" -Scope AllUsers

Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All,RoleManagement.ReadWrite.Directory"
$GraphApp = Get-MgServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"

# Find our Logic App's managed service principal
$ManagedSp = Get-MgServicePrincipal -Filter "DisplayName eq '$LogicAppName'" # the service principal has the same name as the logic app we created earlier

# Search for app role permission "SynchronizationData-User.Upload" and assign it to our service principal
$PermissionName = "SynchronizationData-User.Upload"
$AppRole = $GraphApp.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}
New-MgServicePrincipalAppRoleAssignment -PrincipalId $ManagedSp.Id -ServicePrincipalId $ManagedSp.Id -ResourceId $GraphApp.Id -AppRoleId $AppRole.Id

# Search for app role permission "AuditLog.Read.All" and assign it to our service principal
$PermissionName = "AuditLog.Read.All"
$AppRole = $GraphApp.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}
New-MgServicePrincipalAppRoleAssignment -PrincipalId $ManagedSp.Id -ServicePrincipalId $ManagedSp.Id -ResourceId $GraphApp.Id -AppRoleId $AppRole.Id

Zusätzlich erlauben wir der Logic App, unsere Datenquelle (Azure Storage Table) zu lesen:

New-AzRoleAssignment -PrincipalId $ManagedSp.Id -RoleDefinitionName "Storage Table Data Reader" -ResourceName $StorageName -ResourceType "Microsoft.Storage/storageAccounts" -ResourceGroupName $ResourceGroupName 

Du kannst die MS Graph Berechtigungen des Service Principals in Entra prüfen, indem du die App “logic-HcmProvisioning” unter “Enterprise Application” auswählst (ggf. den Filter “Application type == Enterprise application” entfernen):

Abbildung 7: Service Principal Berechtigungen Abbildung 7: Service Principal Berechtigungen

Die “Storage Table Data Reader” Berechtigung siehst du im Azure Portal in der “Storage Account” Ressource unter “Access Control (IAM) > Role Assignments”:

Abbildung 8: Service Principal Rollen Abbildung 8: Service Principal Rollen

Logic App Workflow konfigurieren

Logic App Designer öffnen

Nachdem wir alle Voraussetzungen erfüllt bzw. konfiguriert haben können wir endlich mit der Entwicklung unseres Logic App Workflows beginnen. Navigiere im Azure Portal zur Logic App und öffne den “Logic App Designer”. Derzeit besteht die Logic App nur aus zwei Schritten:

  • Ein Trigger (Auslöser) “Recurrence 0500am every day”, welcher den Workflow jeden Tag um 05:00 UTC+1 startet
  • und eine Action “Compose ProvisioningAPI Settings”, die ein später benötigtes JSON-Objekt erzeugt.

API-Eigenschaften definieren

Bearbeite die “Compose ProvisioningAPI Settings” Action und ersetze den “Uri”-Eintrag mit deinem eigenen “Provisioning API Endpoint”. Du findest diese Information in der “API-drive provisioning to Microsoft Entra ID” Enterprise application, unter Provisioning > Overview > View technical information.

Die Action sollte nun ungefähr so aussehen:

{
  "Audience": "https://graph.microsoft.com",
  "Uri": "https://graph.microsoft.com/beta/servicePrincipals/abdefghi-1234-5678-jklm-nopqrstuvwxy/synchronization/jobs/API2AAD.b649b126ed68488484d86210336bb33f.21803b3e-5c34-4571-b37a-bc1ce9ec3556/bulkUpload"
}

Datenquelle auslesen

Füge eine weitere Action “Get entities (V2)” vom Typ “Azure Table Storage” hinzu. Gib einen Namen für das Connection-Objekt an (z.B. “conn-sthcmprovisioning”), wähle “Logic Apps Managed Identity” als Authentication Type und klicke anschließend auf “Create.”

Unter Parameters

  • für Storage Account Name wähle “enter custom value” und trage den Namen deines Azure Storage Account ein (z.B. “sthcmprovisioning1234”)
  • für Table wähle “employeedemo” im Drow-Down aus.

Um den Workflow besser lesbar zu machen klicke auf den Titel der Action und ändere den Namen zu “Get employee data from storage table”.

SCIM-Objekte erstellen

Wir müssen ein SCIM/JSON-Objekt für jeden Eintrag in der Datenquelle erstellen. Um das zu tun,

  • erstelle eine “For each” Action vom Typ “Control”,
  • wähle “Get entities result / List of Entities” als Parameter,
  • ändere den Namen der “For each” Action auf “For each employee”.

Dann erstelle eine weiter Action innerhalb des “For each employee” Blocks:

  • Diesmal benötigen wir eine “Compose” Action vom Typ “Data Operations”.
  • Ändere den Namen auf “Compose employee SCIM”.
  • Kopiere den JSON-Code unten in das Feld “Inputs”. Der Code enthält ein SCIM-Schema zur Zuordnung der Felder unserer Datenquelle zu Microsoft Entra (z.B. FullName => displayName). Außerdem wird der active Status basierend auf dem Feld WorkerStatus in unserer Datenquelle gesetzt.
{
  "bulkId": "@{guid()}",
  "data": {
    "schemas": [
      "urn:ietf:params:scim:schemas:core:2.0:User",
      "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
    ],
    "userName": "@{items('For_each_employee')?['UserID']}",
    "displayName": "@{items('For_each_employee')?['FullName']}",
    "externalId": "@{items('For_each_employee')?['WorkerID']}",
    "name": {
      "familyName": "@{items('For_each_employee')?['LastName']}",
      "givenName": "@{items('For_each_employee')?['FirstName']}"
    },
    "active": @{if(equals(items('For_each_employee')?['WorkerStatus'],'Active'),true,false)}
  },
  "method": "POST",
  "path": "/Users"
}

Die Action sollte nun so aussehen:

Abbildung 9: SCIM-Objekt erstellen

Abbildung 9: SCIM-Objekt erstellen

SCIM-Objekte kombinieren

Nun müssen wir die einzelnen SCIM-Objekte zu einem gesamten bulk SCIM-Dokument welches die Daten aller Mitarbeiter:innen beinhaltet zusammenfassen

Dazu erstelle eine neue “Join” Action vom Typ “Data Operations”,

  • klicke auf den “From” Parameter und wähle insert expression
  • füge outputs('Compose_employee_SCIM') in das Textfeld ein und klicke auf “Update”.
  • Verwende ein Komma (”,”) für den “Join With” Parameter.
  • Ändere den Namen der Action auf “Join employee SCIM”.

Anschließend erstellst du eine weitere “Compose” Action. Hier verwenden wir eine Kombination aus JSON syntax und Logic App Expressions, um unser SCIM-Dokument zu erstellen.

  • Benenne die Action “Compose SCIM bulk payload”
  • und Kopiere den folgenden JSON-code in das Feld Inputs:
{
  "schemas": [
    "urn:ietf:params:scim:api:messages:2.0:BulkRequest"
  ],
  "Operations": @{json(concat('[',body('Join_employee_SCIM'),']'))},
  "failOnErrors": null
}

SCIM-Dokument an API übermitteln

Im letzten Schritt unseres Workflows senden wir unser SCIM-Dokument an den API-Endpunkt.

Füge eine neue “HTTP” Action hinzu und

  • verwende die Expression outputs('Compose_ProvisioningAPI_Settings')?['Uri'] als URI,
  • wähle POST als Methode,
  • füge im Abschnitt “Headers” einen neuen Eintrag Content-Type mit dem Wert application/scim+json ein,
  • verwende die “Compose SCIM bulk payload” Action als Body,
  • wähle “Managed Identity” als Authentication Type
  • und füge die Expression outputs('Compose_ProvisioningAPI_Settings')?['Audience'] im Feld Audience ein.

Ändere den Namen der Action auf “POST SCIM payload to API”.

Du findest die vollständige Workflowdefinition im Anhang dieses Artikels.

Bereitstellungs-Logdateien

Wir sind jetzt bereit, den Logic App Workflow auszuführen. Klicke auf “Run” und warte, bis der Workflow das SCIM-Dokument an den API-Endpunkt gesendet hat. Als Ergebnis der “POST SCIM payload to API” Action solltest du “HTTP Status 202 (Accepted)” sehen. Das bedeutet, die API hat den Request erhalten und startet in Kürze mit der Verarbeitung.

Navigiere in der Zwischenzeit zur API-driven inbound provisioning Applikation in Microsoft Entra. Klicke auf “Provisioning” und öffne anschließend die “Provisioning Logs”, um Details zu den Bereitstellungsvorgängen anzuzeigen, wie in Abbildung 10 dargestellt:

Abbildung 10: SCIM-Objekt erstellen Abbildung 10: SCIM-Objekt erstellen

Nächste Schritte

Jetzt, da du unsere beispielhafte Implementierung gesehen hast fragst du dich vielleicht, wie du deine eigenen HR-Daten verwenden und dein eigenes “Identitätsverzeichnis” anbinden kannst…

Durch die Flexibilität von Azure Logic Apps und die umfangreiche Zahl an verfügbaren Konnektoren bist du nicht darauf beschränkt, Azure Tables als Datenquelle zu verwenden. Hier ein paar alternative Szenarien für Datenquellen, die du in deiner eigenen Umgebung anbinden könntest:

Das hier gezeigte Beispiel kann als Grundlage für weitere Entra Lebenszyklus-Workflows9 (“joiner-mover-leaver” Prozesse) dienen.

Quellen

  1. Microsoft Tech Community: Introducing a New Flexible Way of Bringing Identities from Any Source into Microsoft Entra ID!
  2. System for Cross-domain Identity Management: SCIM 2, the open API for managing identities is now complete and published under the IETF.
  3. Microsoft Entra ID Governance: What are lifecycle workflows?
  4. API-driven inbound provisioning tutorials: Quickstart with Azure Logic App
  5. Azure Storage: What is Azure Table storage?
  6. Logic Apps: Workflow definition language
  7. Managed identities for Azure resources: What are managed identities for Azure resources?
  8. Permissions for API-driven provisioning: Grant access to the inbound provisioning API
  9. Microsoft Entra ID Governance: Understanding lifecycle workflows

Anhang

Workflowdefinition (leer)

Um eine Azure Logic App mit PowerShell oder der Azure CLI zu erstellen muss eine “Workflowdefinition” angegeben werden. Du kannst die folgende “leere” Workflowdefinition verwenden, um einen einfachen Workflow mit einem Trigger und einer Action zu erstellen:

{
  "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
  "actions": {
    "Compose_ProvisioningAPI_Settings": {
      "inputs": {
        "Audience": "https://graph.microsoft.com",
        "Uri": "<Your API-driven inbound provisioning URI>"
      },
      "runAfter": {},
      "type": "Compose"
    }
  },
  "contentVersion": "1.0.0.0",
  "outputs": {},
  "parameters": {
    "$connections": {
      "defaultValue": {},
      "type": "Object"
    }
  },
  "triggers": {
    "Recurrence_0500am_every_day": {
      "evaluatedRecurrence": {
        "frequency": "Day",
        "interval": 1,
        "schedule": {
          "hours": ["5"]
        },
        "timeZone": "W. Europe Standard Time"
      },
      "recurrence": {
        "frequency": "Day",
        "interval": 1,
        "schedule": {
          "hours": ["5"]
        },
        "timeZone": "W. Europe Standard Time"
      },
      "type": "Recurrence"
    }
  }
}

Workflowdefinition (komplett)

Du kannst die folgende (anonymisierte) Workflowdefinition als Referenz für deinen eigenen Workflow verwenden:

{
  "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#",
  "actions": {
    "Compose_ProvisioningAPI_Settings": {
      "inputs": {
        "Audience": "https://graph.microsoft.com",
        "Uri": "https://graph.microsoft.com/beta/servicePrincipals/abdefghi-1234-5678-jklm-nopqrstuvwxy/synchronization/jobs/API2AAD.b649b126ed68488484d86210336bb33f.21803b3e-5c34-4571-b37a-bc1ce9ec3556/bulkUpload"
      },
      "runAfter": {},
      "type": "Compose"
    },
    "Compose_SCIM_bulk_payload": {
      "inputs": {
        "Operations": "@json(\r\n  concat('[',body('Join_employee_SCIM'),']')\r\n)",
        "failOnErrors": null,
        "schemas": ["urn:ietf:params:scim:api:messages:2.0:BulkRequest"]
      },
      "runAfter": {
        "Join_employee_SCIM": ["Succeeded"]
      },
      "type": "Compose"
    },
    "For_each_employee": {
      "actions": {
        "Compose_employee_SCIM": {
          "inputs": {
            "bulkId": "@{guid()}",
            "data": {
              "active": "@if(equals(items('For_each_employee')?['WorkerStatus'],'Active'),true,false)",
              "displayName": "@{items('For_each_employee')?['FullName']}",
              "externalId": "@{items('For_each_employee')?['WorkerID']}",
              "name": {
                "familyName": "@{items('For_each_employee')?['LastName']}",
                "givenName": "@{items('For_each_employee')?['FirstName']}"
              },
              "schemas": [
                "urn:ietf:params:scim:schemas:core:2.0:User",
                "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
              ],
              "userName": "@{items('For_each_employee')?['UserID']}"
            },
            "method": "POST",
            "path": "/Users"
          },
          "type": "Compose"
        }
      },
      "foreach": "@body('Get_employee_data_from_storage_table')?['value']",
      "runAfter": {
        "Get_employee_data_from_storage_table": ["Succeeded"]
      },
      "type": "Foreach"
    },
    "Get_employee_data_from_storage_table": {
      "inputs": {
        "host": {
          "connection": {
            "name": "@parameters('$connections')['azuretables']['connectionId']"
          }
        },
        "method": "get",
        "path": "/v2/storageAccounts/@{encodeURIComponent(encodeURIComponent('sthcmprovisioning1234'))}/tables/@{encodeURIComponent('employeedemo')}/entities"
      },
      "runAfter": {
        "Compose_ProvisioningAPI_Settings": ["Succeeded"]
      },
      "type": "ApiConnection"
    },
    "Join_employee_SCIM": {
      "inputs": {
        "from": "@outputs('Compose_employee_SCIM')",
        "joinWith": ","
      },
      "runAfter": {
        "For_each_employee": ["Succeeded"]
      },
      "type": "Join"
    },
    "POST_SCIM_payload_to_API": {
      "inputs": {
        "authentication": {
          "audience": "@{outputs('Compose_ProvisioningAPI_Settings')?['Audience']}",
          "type": "ManagedServiceIdentity"
        },
        "body": "@outputs('Compose_SCIM_bulk_payload')",
        "headers": {
          "Content-Type": "application/scim+json"
        },
        "method": "POST",
        "uri": "@outputs('Compose_ProvisioningAPI_Settings')?['Uri']"
      },
      "runAfter": {
        "Compose_SCIM_bulk_payload": ["Succeeded"]
      },
      "type": "Http"
    }
  },
  "contentVersion": "1.0.0.0",
  "outputs": {},
  "parameters": {
    "$connections": {
      "defaultValue": {},
      "type": "Object"
    }
  },
  "triggers": {
    "Recurrence_0500am_every_day": {
      "evaluatedRecurrence": {
        "frequency": "Day",
        "interval": 1,
        "schedule": {
          "hours": ["5"]
        },
        "timeZone": "W. Europe Standard Time"
      },
      "recurrence": {
        "frequency": "Day",
        "interval": 1,
        "schedule": {
          "hours": ["5"]
        },
        "timeZone": "W. Europe Standard Time"
      },
      "type": "Recurrence"
    }
  }
}

Im grafischen Editor sollte der Workflow so aussehen:

Abbildung 11: vollständiger Logic App Workflow Abbildung 11: vollständiger Logic App Workflow