When you are creating role assignments in terraform using azurerm, it can quickly
become literally unreadable. Unless you place comments to which principal you are
granting, you will surely forget what that string of numbers represents.
An example of a standard role assignment#
resource "azurerm_role_assignment" "example" {
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-testing/providers/Microsoft.KeyVault/vaults/kv-testing"
role_definition_name = "Key Vault Secrets User"
principal_id = "00000000-0000-0000-0000-000000000001"
}
Here it is clear that you are granting Key Vault Secrets User on kv-testing,
but to who? The principal_id tells you nothing.
Ive created a module which makes it more reader friendly.
My solution#
This module supports granting roles to Security Groups and Service Principals. Why no users? Managing role assignments at the group level is simpler and more maintainable than granting them to individual users.
Configuration#
To configure this module, you give it a role_assignments list. This is a list
of objects. The objects look like this:
{
scope = ""
role_name = ""
principal = {
type = ""
name = ""
}
description = ""
}
scope: still the same as before: the Resource IDrole_name: the name of the role (e.g. Reader, Key Vault Secrets User)principal.type: eitherSecurity GrouporService Principalprincipal.name: the exact name of the security group or service principaldescription: optional, defaults to “Assigned through Terraform”
principal.typegets converted to lowercase and spaces get replaced with_, sosecurity_groupis also valid.
Module usage example#
module "smart_iam" {
source = "nietarne/smart-iam/azurerm"
version = "0.1.0" # optional
# or git source:
# source = "git::https://codeberg.org/nietarne/terraform-azure-smart-iam"
# if you want to pin a version / commit: add "?rev={tag or commit sha-1}"
role_assignments = [
{
role_name = "Role name"
scope = "Resource ID"
principal = {
type = "Service Principal | Security Group"
name = "name of SP / SG"
}
}
]
}
Now, this is a lot better already in my opinion. But I don’t usually configure
the roles in my .tf files. I’d rather use the .tfvars for the configuration,
this way you can easily place multiple environment configurations next to eachother.
The simplest way of doing this:
variable "role_assignments" {
description = "List of role assignments"
default = []
}
module "smart_iam" {
source = "nietarne/smart-iam/azurerm"
version = "0.1.0" # optional
# or git source:
# source = "git::https://codeberg.org/nietarne/terraform-azure-smart-iam"
# if you want to pin a version / commit: add "?rev={tag or commit sha-1}"
role_assignments = var.role_assignments
}
In the repository README there also is an example of a fully typed variable config for the role_assignments. This is what I would recommend to add instead of the bare variable above.
If you’ve added the variable, you can add the following into your .tfvars:
role_assignments = [
{
role_name = "Key Vault Secrets User"
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-testing/providers/Microsoft.KeyVault/vaults/kv-testing"
principal = {
type = "Security Group"
name = "SG-TERRAFORM-TESTING"
}
},
{
role_name = "Key Vault Secrets Officer"
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-testing/providers/Microsoft.KeyVault/vaults/kv-testing"
principal = {
type = "Service Principal"
name = "SP-TERRAFORM-PRD"
}
},
]
As you can see, no weird ID’s. The scope is still something I would want to edit to be easier to read, but for now this is perfect for me.
How it works#
If you have ever played with or used terraform, it basically works exactly like
you would think it works. Based on the type of the principal, it will query azuread
for the security group or service principal with that exact name using a data resource.
Once we have the object from azuread, we just create a standard role assignment
using the principal_id that is in the returned object.

