Azure RBAC and TerraForm
My next challenge in my TerraForm journey is to assign some RBAC roles (some custom) to users on specific resources.
Custom roles
Roles need to be assigned to a scope and have a list of scopes to which they can be assigned. For these we may need the subscription id, something that up until now I have not needed in my TerraForm files. Fortunately, there are several Azure data providers with the TerraForm integration including azurerm_subscription which will give the current Azure Resource Manager provider’s subscription id if no other arguments are provided:
data "azurerm_subscription" "current" {
}
After this, a custom role definition is fairly straight forward to create:
resource "azurerm_role_definition" "vmuser" {
name = "Virtual Machine User"
scope = data.azurerm_subscription.current.id
description = "Can deallocate and start virtual machines"
permissions {
actions = [
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/deallocate/action",
"Microsoft.Network/publicIPAddresses/read",
"Microsoft.Network/virtualNetworks/read",
"Microsoft.Network/loadBalancers/read",
"Microsoft.Network/networkInterfaces/read",
"Microsoft.Compute/virtualMachines/*/read",
"Microsoft.Compute/virtualMachines/restart/action"
]
data_actions = ["Microsoft.Compute/virtualMachines/login/action"]
not_actions = []
not_data_actions = []
}
assignable_scopes = [
data.azurerm_subscription.current.id, # /subscriptions/00000000-0000-0000-0000-000000000000
]
}
Users and groups
In order to access user and group information, we need to use the azuread provider.
Firstly we need to add the azuread
provider to TerraForm:
terraform {
required_providers {
#...
azuread = {
source ="hashicorp/azuread"
version = ">=2.12.0"
}
#...
}
}
Once added to the configuration, we need to re-run terraform init
.
Then, groups and users can be looked up like this:
data "azuread_group" "rdpusers" {
display_name = "RDPUsers-Production-G"
security_enabled = true
}
data "azuread_user" "someuser" {
user_principal_name = "some.user@domain.tld"
}
What I found useful is that the traditional login for the user is available via the onpremises_sam_account_name
property (e.g. data.azuread_user.someuser.onpremises_sam_account_name
). This can be useful if that login was used to generate other things, like file-share names.
Assigning the role
resource "azurerm_role_assignment" "role-assignment" {
scope = data.azurerm_management_group.primary.id
role_definition_id = azurerm_role_definition.vmuser.id
principal_id = data.azuread_user.someuser.id
}
In-built roles can be assigned by putting role_definition_name = "Reader"
(for example) instead of using role_definition_id
or looking up the role definition with the azurerm_role_definition
data source and using the returned id property:
data "azurerm_role_definition" "reader" {
name = "Reader"
}
I prefer the latter as using the id consistently works better when you start modularising and doing for_each
loops with a mix of custom and built-in roles (using the ids in the loop variables).
Adding to VM module
Expanding my previous work modularising the creation of VDIs to assign a role to a list of principals is relatively straight-forward (with a little reworking, this could probably be generalised to allow combinations of roles but I only need to assign one at the VM level).
Firstly, add 2 new variables - one for the role id to assign and one for the list of principals to assign to (this could be turned into a map of role to principals to generalise it):
variable "vm_user_role_id" {
type = string
description = "Scoped resource ID of the role to assign to principals for the VM"
}
variable "vm_user_principal_ids" {
type = set(string)
description = "IDs of principals to assign the vm_user_role_id to for this VM"
}
Then adding the assignment is very easy:
resource "azurerm_role_assignment" "vm_role" {
scope = azurerm_linux_virtual_machine.vdi.id
role_definition_id = var.vm_user_role_id
principal_id = each.value
for_each = var.vm_user_principal_ids
}
Importing these roles is a bit finicky. There is no easy way I have found to see the assignment’s full id in the portal so the best route seems to be to use the Azure CLI to look it up:
$sub_id="$(az account show --query id -o tsv)"
$vdi_users = @{
VDI01 = "some.other.user@domain.tld"
VDI27 = "some.user@domain.tld"
VDI99 = "someone.else@domain.tld"
}
$vdi_groups = @{
VDI24 = "RDPUsers-Production-G"
}
foreach ( $entry in $vdi_users.GetEnumerator()) {
$user_id="$(az ad user show --id $($entry.Value) --query objectId -o tsv)"
terraform import "module.vdis[\""$($entry.Name)\""].azurerm_role_assignment.vm_role[\""$user_id\""]" "$(az role assignment list --scope /subscriptions/$sub_id/resourceGroups/RG-VDI-001/providers/Microsoft.Compute/virtualMachines/$($entry.Name) --assignee $user_id --query [].id -o tsv)"
}
foreach ( $entry in $vdi_groups.GetEnumerator()) {
$group_id="$(az ad group show --group $($entry.Value) --query objectId -o tsv)"
terraform import "module.vdis[\""$($entry.Name)\""].azurerm_role_assignment.vm_role[\""$group_id\""]" "$(az role assignment list --scope /subscriptions/$sub_id/resourceGroups/RG-VDI-001/providers/Microsoft.Compute/virtualMachines/$($entry.Name) --assignee $group_id --query [].id -o tsv)"
}