A reusable Terraform module that implements just-in-time access to Microsoft Entra directory roles (e.g. "Application Administrator") using Privileged Identity Management (PIM) for Groups.
The module creates two Entra ID security groups per directory role:
pim-<slug>-eligible ← members are added here (normal group membership)
│
│ PIM eligibility schedule
▼
pim-<slug> ← role-assignable group; holds the permanent directory role assignment
│
│ azuread_directory_role_assignment
▼
Entra directory role (e.g. Application Administrator)
Why two groups?
The privileged group holds the directory role permanently, but its membership is
controlled by PIM. Only users who actively activate their eligibility appear in the
privileged group — and only for the configured time window. Adding or removing
people from the eligible group is a plain Entra group operation, no terraform apply
needed per joiner/leaver.
Group naming is derived from entra_role_display_name. Set override_group_display_name to customise it:
entra_role_display_name = "Application Administrator"→ groupspim-application-administratorandpim-application-administrator-eligibleoverride_group_display_name = "App Admin Team B"→ groupspim-app-admin-team-bandpim-app-admin-team-b-eligible
Approver type inference: passing an object_id without a type field causes the
module to look up the Entra directory object and set groupMembers for groups or
singleUser for users automatically.
Difference to pim-azure-role: this module targets Entra directory roles (tenant-wide,
no Azure resource scope), whereas pim-azure-role targets Azure RBAC roles on
resources, resource groups, subscriptions, or management groups.
| Requirement | Details |
|---|---|
| Terraform | >= 1.9 |
| hashicorp/azuread | >= 3.0 |
| hashicorp/time | >= 0.10 |
| Entra licence | Microsoft Entra ID P2 or Entra ID Governance |
| Terraform principal permissions | Privileged Role Administrator or Global Administrator (required to create role-assignable groups and assign directory roles) |
The Terraform SPN requires the following Application permissions on the Microsoft Graph API:
| Permission | Reason |
|---|---|
Application.ReadWrite.OwnedBy |
Update owned app registrations (e.g. adding group owners) |
Group.Create |
Create the Eligible and Privileged security groups |
Group.Read.All |
Read existing groups to detect duplicates and resolve members |
RoleManagement.ReadWrite.Directory |
Manage PIM eligibility schedules, role management policies, and directory role assignments |
User.ReadBasic.All |
Resolve user objects for approver type inference |
module "app_admin_pim" {
source = "CloudverveGmbH/pim-entra-role/azuread"
version = "~> 0.1"
entra_role_display_name = "Application Administrator"
members = [
{ object_id = data.azuread_user.alice.object_id, display_name = "Alice" },
]
}module "privileged_role_admin_pim" {
source = "CloudverveGmbH/pim-entra-role/azuread"
version = "~> 0.1"
entra_role_display_name = "Privileged Role Administrator"
override_group_display_name = "Privileged Role Admin" # optional: shorten the auto-derived slug
maximum_activation_duration = "PT2H"
members = [
{ object_id = data.azuread_user.bob.object_id, display_name = "Bob" },
]
# type is inferred automatically – no need to specify "singleUser"
approvers = [
{ object_id = data.azuread_user.alice.object_id },
]
}approvers = [
{ object_id = azuread_group.security_team.object_id } # type "groupMembers" inferred automatically
]Leave members = [] (the default) and add users directly to the
pim-<slug>-eligible group in the Entra admin center. This avoids a
terraform apply every time the team changes.
module "app_admin_pim" {
source = "CloudverveGmbH/pim-entra-role/azuread"
version = "~> 0.1"
entra_role_display_name = "Application Administrator"
# members = [] ← default; manage via Entra admin center
}| Name | Type | Default | Description |
|---|---|---|---|
override_group_display_name |
string |
derived from role name | Optional override for the group base name. When omitted, the slug is derived from entra_role_display_name automatically |
entra_role_display_name |
string |
— | Display name of the Entra directory role to assign (e.g. "Application Administrator") |
group_owners |
list(string) |
[] |
Additional owner object IDs (Terraform SPN is always added) |
members |
list(object) |
[] |
Initial members of the Eligible group (object_id, optional display_name) |
approvers |
list(object) |
[] |
PIM approvers (object_id, optional type); when set, approval is required on activation |
require_justification |
bool |
true |
Require a business justification on activation (independent of approval) |
maximum_activation_duration |
string |
"PT4H" |
ISO 8601 duration; shorter windows recommended for high-privilege directory roles |
eligibility_years |
number |
1 |
Validity of the eligibility schedule in years |
| Name | Description |
|---|---|
eligible_group_id |
Object ID of the Eligible group |
eligible_group_name |
Display name of the Eligible group (pim-<slug>-eligible) |
privileged_group_id |
Object ID of the Privileged group (role-assignable) |
privileged_group_name |
Display name of the Privileged group (pim-<slug>) |
group_id |
Alias for privileged_group_id (backward-compatible) |
principal_id |
Alias for privileged_group_id |
resolved_approvers |
Approvers with their resolved PIM type after auto-inference |
directory_role_assignment_id |
Resource ID of the Entra directory role assignment |
While the architecture and design decisions were made by a human, the code, tests, and documentation were mostly written with AI assistance. All technical content should be reviewed before use in production or critical environments.
Wiederverwendbares Terraform-Modul für Just-in-Time-Zugriff auf Microsoft-Entra-Verzeichnisrollen (z. B. „Application Administrator") mit Privileged Identity Management (PIM) for Groups.
Das Modul erstellt pro Verzeichnisrolle zwei Entra-ID-Sicherheitsgruppen:
pim-<slug>-eligible ← Mitglieder werden hier eingetragen (normale Gruppenmitgliedschaft)
│
│ PIM-Eligibility-Schedule
▼
pim-<slug> ← rollenberechtigte Gruppe; hält die dauerhafte Verzeichnisrollenzuweisung
│
│ azuread_directory_role_assignment
▼
Entra-Verzeichnisrolle (z. B. Application Administrator)
Warum zwei Gruppen?
Die privilegierte Gruppe hält die Verzeichnisrolle dauerhaft, ihre Mitgliedschaft
wird aber durch PIM gesteuert. Nur User, die ihre Eligibility aktiv aktiviert haben,
erscheinen in der privilegierten Gruppe — und nur für das konfigurierte Zeitfenster.
Personen in der Eligible-Gruppe hinzuzufügen oder zu entfernen ist eine einfache
Entra-Gruppenoperation, kein terraform apply pro Neueinsteiger oder Ausscheider.
Gruppennamensgebung wird aus entra_role_display_name abgeleitet. Mit override_group_display_name kann sie überschrieben werden:
entra_role_display_name = "Application Administrator"→ Gruppenpim-application-administratorundpim-application-administrator-eligibleoverride_group_display_name = "App Admin Team B"→ Gruppenpim-app-admin-team-bundpim-app-admin-team-b-eligible
Approver-Typ-Inferenz: Wird ein object_id ohne type übergeben, schaut das Modul
das Entra-Directory-Objekt nach und setzt automatisch groupMembers für Gruppen bzw.
singleUser für Einzelpersonen.
Unterschied zu pim-azure-role: Dieses Modul zielt auf Entra-Verzeichnisrollen
(mandantenweit, kein Azure-Ressourcen-Scope), während pim-azure-role auf
Azure-RBAC-Rollen auf Ressourcen, Ressourcengruppen, Subscriptions oder
Management Groups abzielt.
| Anforderung | Details |
|---|---|
| Terraform | >= 1.9 |
| hashicorp/azuread | >= 3.0 |
| hashicorp/time | >= 0.10 |
| Entra-Lizenz | Microsoft Entra ID P2 oder Entra ID Governance |
| Terraform-Principal-Rechte | Privileged Role Administrator oder Global Administrator (erforderlich zum Erstellen rollenberechtigter Gruppen und Zuweisen von Verzeichnisrollen) |
Der Terraform-SPN benötigt folgende Application-Berechtigungen auf der Microsoft Graph API:
| Berechtigung | Grund |
|---|---|
Application.ReadWrite.OwnedBy |
Eigene App-Registrierungen aktualisieren (z. B. Gruppenbesitzer hinzufügen) |
Group.Create |
Eligible- und Privileged-Sicherheitsgruppen erstellen |
Group.Read.All |
Vorhandene Gruppen lesen (Duplikaterkennung, Member-Auflösung) |
RoleManagement.ReadWrite.Directory |
PIM-Eligibility-Schedules, Rollenmanagement-Richtlinien und Verzeichnisrollenzuweisungen verwalten |
User.ReadBasic.All |
User-Objekte für die automatische Approver-Typ-Erkennung auflösen |
module "app_admin_pim" {
source = "CloudverveGmbH/pim-entra-role/azuread"
version = "~> 0.1"
entra_role_display_name = "Application Administrator"
members = [
{ object_id = data.azuread_user.alice.object_id, display_name = "Alice" },
]
}approvers = [
{ object_id = data.azuread_user.alice.object_id } # Typ wird automatisch als "singleUser" erkannt
]approvers = [
{ object_id = azuread_group.security_team.object_id } # Typ wird automatisch als "groupMembers" erkannt
]members = [] (Standard) lassen und User direkt im Entra Admin Center zur
pim-<slug>-eligible-Gruppe hinzufügen. Vermeidet terraform apply bei
jeder Teamänderung.
| Name | Typ | Standard | Beschreibung |
|---|---|---|---|
override_group_display_name |
string |
aus Rollenname abgeleitet | Optionaler Override für den Gruppenbasisnamen. Wenn weggelassen, wird der Slug automatisch aus entra_role_display_name abgeleitet |
entra_role_display_name |
string |
— | Anzeigename der Entra-Verzeichnisrolle (z. B. "Application Administrator") |
group_owners |
list(string) |
[] |
Zusätzliche Owner-Object-IDs (Terraform-SPN wird immer ergänzt) |
members |
list(object) |
[] |
Initiale Mitglieder der Eligible-Gruppe (object_id, optionaler display_name) |
approvers |
list(object) |
[] |
PIM-Genehmiger (object_id, optionaler type); wenn gesetzt, ist Genehmigung bei Aktivierung Pflicht |
require_justification |
bool |
true |
Begründung bei Aktivierung erforderlich (unabhängig von Genehmigung) |
maximum_activation_duration |
string |
"PT4H" |
ISO-8601-Dauer; kürzere Fenster für hochprivilegierte Verzeichnisrollen empfohlen |
eligibility_years |
number |
1 |
Gültigkeit des Eligibility-Schedules in Jahren |
| Name | Beschreibung |
|---|---|
eligible_group_id |
Object-ID der Eligible-Gruppe |
eligible_group_name |
Anzeigename der Eligible-Gruppe (pim-<slug>-eligible) |
privileged_group_id |
Object-ID der Privileged-Gruppe (rollenberechtigend) |
privileged_group_name |
Anzeigename der Privileged-Gruppe (pim-<slug>) |
group_id |
Alias für privileged_group_id (abwärtskompatibel) |
principal_id |
Alias für privileged_group_id |
resolved_approvers |
Genehmiger mit aufgelöstem PIM-Typ nach Auto-Inferenz |
directory_role_assignment_id |
Ressourcen-ID der Entra-Verzeichnisrollenzuweisung |
Siehe CONTRIBUTING.md für den PR-, Changelog- und Release-Prozess.
Architektur und Design-Entscheidungen stammen von einem Menschen; Code, Tests und Dokumentation wurden überwiegend mit KI-Unterstützung erstellt. Alle technischen Inhalte sollten vor produktivem Einsatz geprüft werden.