Manage Entra Privileged Identity Management with Terraform

Introduction
There is no surprise to anyone who has seen my content before that I kind of love Infrastructure as Code (IaC), I really do. However, there seem to be a lot of people who think only about Azure resources when IaC is introduced into the conversation but that is not the case. We have the ability to also create service principals, users and groups in Microsoft Entra.
This is probably quite known by a lot of people but maybe you are not aware that you can also work the the Entra Privileged Identity Management (PIM) API and configure PIM workflows using Terraform as well.
PIM is a great and in my opinion a mandatory tool for any serious organization using Azure. You have the ability to create just-in-time access and control approval flows and much more using this technology which increases the security and level of control in your environment, reducing your overall attack surface.
It is important to note that there is a way to deal with PIM both in the azurerm
provider which deals with PIM and Azure RBAC roles, as well as with the azuread
provider which deals with Entra roles and groups. In this post we will focus on the first mentioned provider and look more at azuread
provider in a later post.
You can find all the source code for this post in the Github repo HERE
Setting up Terraform
The objective is to create a resource group, assign myself as reader which will be an active assignment as well as assign myself as contributor via PIM where I need to provide justification for activating that role.
We'll setup our module with some pre-requisites. I will create a new folder where I will store my Terraform config: mkdir -p ~/github/azure-terraform-pim
We will go ahead and configure the provider with the following code block inside a new main.tf
file which we will create alongside a variables.tf
file
In main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.8.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
And in variables.tf
variable "subscription_id" {
type = string
}
variable "environment" {
type = string
default = "dev"
}
variable "location" {
type = string
default = "swedencentral"
}
variable "location_short" {
type = string
default = "se"
}
I have added some defaults, you can skip them if you wish
Once you login to Azure via the AZ CLI we are ready to actually start configuring resources.
Example with the azurerm provider
The resource we are interested in is azurerm_pim_eligible_role_assignment
which you can find the documentation on here:
Some common questions you should have answers for when setting up this resource is:
- Which role should we assign eligible permissions to?
- At which scope is the permission applied, on the subscription or on a resource group?
- Who gets the permission?
There are more things we can do such as require MFA, approval, justification and so on, refer to the docs in the link for all the possibilities that exist.
I will update my main.tf
file to create:
- A resource group that I will create permissions for
- A data block for the current
azurerm_client_config
andazurerm_subscription
- Data block for role definition
Reader
which will be an active permanent assignment, I will use the role definition ID from the data block - Data block for role definition
Contributor
which will be an eligible assignment, I will use the role definition ID from the data block - A
azurerm_role_management_policy
for theContributor
role where we will control how the role may be activated - A resource block for the active role assignment of
Reader
on the resource group scope - A resource block for the eligible role assignment of
Contributor
on the resource group scope
This gives us a new updated main.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "4.8.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
data "azurerm_client_config" "current" {}
data "azurerm_subscription" "primary" {}
resource "azurerm_resource_group" "this" {
name = "rg-${var.environment}-${var.location_short}-pim"
location = var.location
}
data "azurerm_role_definition" "reader" {
name = "Reader"
}
data "azurerm_role_definition" "contributor" {
name = "contributor"
}
resource "azurerm_role_assignment" "reader" {
scope = azurerm_resource_group.this.id
role_definition_name = "Reader"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_role_management_policy" "contributor" {
scope = azurerm_resource_group.this.id
role_definition_id = data.azurerm_role_definition.contributor.role_definition_id
eligible_assignment_rules {
expiration_required = false
}
activation_rules {
maximum_duration = "PT8H"
require_approval = false
require_justification = true
}
}
resource "azurerm_pim_eligible_role_assignment" "contributor" {
scope = azurerm_resource_group.this.id
role_definition_id = "${data.azurerm_subscription.primary.id}${data.azurerm_role_definition.contributor.id}"
principal_id = data.azurerm_client_config.current.object_id
}
Now deploying this in my CLI and supplying the subscription ID running the command terraform apply -var 'subscription_id=(your-sub-id)'
& approving the deployment:

And in the portal if I go to manage Azure Resources and select my subscription and resource group I should see the following direct assignment:


And from the point of my user if I want to activate my permissions I can do that as well:

Now if you want to tear it all down just run: terraform destroy -var 'subscription_id=(your-sub-id)'
Conclusion
You can control PIM for Azure RBAC role assignments at different scopes. We now require PIM for the Contributor role at a resource group and you need to provide justification before you can activate the role. We have several layers of benefits here:
- Audit trail inside the Azure Environment
- Reduced attack surface as the role is not always active
- Everything is documented in source control and any changes will be documented as well
References
About me
