Getting started with Terraform and automated access management in Microsoft Entra ID
- Bjørnar Aassveen

- 6 days ago
- 5 min read
Terraform is a tool for Infrastructure as Code (IaC), also for identity and access in Microsoft Entra ID. In this guide, I walk through how you can use Terraform, possibly, to:
Create Entra ID groups
Expose the group through Access Packages
Set up PIM roles that are activated through group membership
Require, or not require, approval
Limit activation duration to four hours
Allow end users to request access themselves
I had never used Terraform myself before this, so this guide is intended to be a practical getting started walkthrough. There may very well be shorter or better ways to reach the same goal.
What are we building?
The target state is as follows:
A user requests access through an Access Package
The user becomes a member of an Entra ID group
The group provides eligible access to multiple Entra ID roles through PIM
The user can activate:
Global Reader with approval
Global Administrator with approval
Teams Administrator without approval
SharePoint Administrator without approval
For all roles, defined per role in the GUI and not currently supported in Terraform:
PIM activation is required
Maximum duration is four hours
A justification is required

Prerequisites
Before you get started, you need:
An Entra ID tenant with a P2 license
Global Administrator permissions
Terraform installed locally
Azure CLI or Microsoft Graph based authentication
1. Install Terraform
macOS
brew install terraformWindows (winget)
winget install Hashicorp.Terraform
Install VS Code‑utvidelser
In VS Code (Extensions):
Terraform (HashiCorp)
Azure Resources
Azure CLI Tools

Create Terraform‑project in VS Code
File → Open Folder
Create folder
entra-terraformCreate files
In VS Code Explorer (left side):
Right click the folder, select New File, and create:
main.tf
providers.tf
variables.tf
terraform.tfvarsConfigure Terraform provider (providers.tf)
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "~> 2.50"
}
}
}
provider "azuread" {
tenant_id = var.tenant_id
}VS Code will now:
Recognize Terraform syntax
Suggest blocks and fields
Display errors directly in the editor
6. Variables (variables.tf + terraform.tfvars)
variable "tenant_id" {
description = "Entra ID Tenant ID"
type = string
}
terraform.tfvars
tenant_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"The foundational Terraform structure is now in place in VS Code.
7. Authenticate Entra ID
Open Terminal in VS Code:
az loginIf you get the message az: The term 'az' is not recognized, it means that Azure CLI is not installed or not available in your PATH.
Install Azure CLI and restart your terminal before running az login.
Now that the basic structure, tools, and access are in place, we can start setting up the Terraform code that builds what we want to build.
Open main.tf and create the access group
resource "azuread_group" "privileged_access_group" {
display_name = "Privileged-Access-Users"
security_enabled = true
mail_enabled = false
assignable_to_role = true
}Open main.tf and fetch the Entra ID roles
First, we need to reference the built in Entra roles. Terraform does not create them, they already exist in the tenant.
# Global Reader
resource "azuread_directory_role" "global_reader" {
display_name = "Global Reader"
}
# Global Administrator
resource "azuread_directory_role" "global_admin" {
display_name = "Global Administrator"
}
# Teams Administrator
resource "azuread_directory_role" "teams_admin" {
display_name = "Teams Administrator"
}
# SharePoint Administrator
resource "azuread_directory_role" "sharepoint_admin" {
display_name = "SharePoint Administrator"
}Make the group eligible for Entra ID roles using PIM
# Global Reader – Eligible
resource "azuread_directory_role_assignment" "global_reader_pim" {
role_id = azuread_directory_role.global_reader.id
principal_object_id = azuread_group.privileged_access_group.object_id
}
# Global Administrator – Eligible
resource "azuread_directory_role_assignment" "global_admin_pim" {
role_id = azuread_directory_role.global_admin.id
principal_object_id = azuread_group.privileged_access_group.object_id
}
# Teams Administrator – Eligible
resource "azuread_directory_role_assignment" "teams_admin_pim" {
role_id = azuread_directory_role.teams_admin.id
principal_object_id = azuread_group.privileged_access_group.object_id
}
# SharePoint Administrator – Eligible
resource "azuread_directory_role_assignment" "sharepoint_admin_pim" {
role_id = azuread_directory_role.sharepoint_admin.id
principal_object_id = azuread_group.privileged_access_group.object_id
}Expose access through Access Packages
In the previous steps, we have:
Created an Entra ID group
Granted the group eligible access to multiple privileged roles through PIM
Configured the PIM policyFour hour duration, with or without approval
Now comes the most important part for the end user experience:
How do users get access to the group without IT having to add them manually?
The answer is Access Packages in Entra ID.
The first time you create an access package, you also need to create an access package catalog. In other words, where does the package belong?
resource "azuread_access_package_catalog" "privileged_catalog" {
display_name = "Privileged Access - Aassveen IT admin"
description = "Tilgang til privilegerte Entra ID-roller via PIM"
}Next, we create the access package itself.
resource "azuread_access_package" "privileged_roles" {
display_name = "Privileged Entra Roles - Aassveen IT admin"
description = "Gir tilgang til privilegert gruppe som brukes for PIM-roller"
catalog_id = azuread_access_package_catalog.privileged_catalog.id
}
Once that is done, the group must be linked to the access package.
Støttes pt. ikke og må gjøres i GUI
As the final step, we need to create an access package policy that defines who can request access and how.
We create a policy that controls:
Who is allowed to request access
Whether access is time limited
Whether approval is required
In my case, I create a policy that does not require approval.
resource "azuread_access_package_assignment_policy" "self_service_policy" {
access_package_id = azuread_access_package.privileged_roles.id
display_name = "Privileged access – self service"
description = "Self-service tilgang til privilegert gruppe"
requestor_settings {
scope_type = "AllExistingDirectoryMemberUsers"
}
}
With the setup defined, we are ready to deploy this to productionTesting is for cowards. Or is it? 🐣
From the terminal in VS Code
terraform init
terraform plan
terraform apply


Now that the code has been executed, the group has been created, the group has been assigned roles, and the access package has been published. I had to configure the package assignment and the duration of the PIM roles, with and without approval, manually in the GUI, as this is not currently supported.
For the end user, the flow will then be:
Click on the access package and request access. Here I have to cheat a bit, since the Easter Bunny ran off with my license, so I add my user to the group directly from Entra ID.

Now that my user account has been granted access to the group, and the group in turn has access to the roles through PIM, I can elevate myself through “My roles”.
In the end, I am left with a solution that is largely Infrastructure as Code, but with a few deliberate manual steps, especially around PIM policies, duration, and certain details in Access Packages. Terraform gives us structure, predictability, and a solid framework, while the most sensitive settings are configured once in the portal. Not perfect, but far better than click, click, forgot why.
And when az login, Graph, and Identity Governance decided to be difficult, I ended up performing a classic Ingrid Espelid maneuver: “a little more of this, and it will work”, in the form of granting myself the Identity Governance Administrator role. Not exactly by the book, but dinner was served. As a first encounter with Terraform and Entra ID? Absolutely approved, and quite educational along the way. Doc: Docs overview | hashicorp/azuread | Terraform | Terraform Registry
Bjørnar&AI



Comments