Enhance examples and documentation for role assignments, adding scenarios for multiple principals and role constraints
This commit is contained in:
58
README.md
58
README.md
@@ -1,19 +1,21 @@
|
||||
# Azure RM Simple IAM module
|
||||
|
||||
This module creates Azure RBAC role assignments for a given scope and principal.
|
||||
This module creates a set of Azure RBAC role assignments for a given scope and principal. To use it, the granting principal must have the **Owner**, **User Access Administrator** or **Role Based Access Control Administrator** role at the scope.
|
||||
|
||||
It also optionally assigns the **Role Based Access Control Administrator** role with an ABAC condition that limits roleAssignments write/delete to a selected set of delegable roles.
|
||||
It also optionally assigns the **Role Based Access Control Administrator** role with an ABAC condition that limits role assignments write/delete using allow-list (`delegable_roles`) and/or deny-list (`restricted_roles`) role constraints.
|
||||
|
||||
The constrained RBAC Administrator assignment is created only when `delegable_roles` is non-empty.
|
||||
The constrained RBAC Administrator assignment is created when `delegable_roles` or `restricted_roles` is non-empty.
|
||||
|
||||
## Usage
|
||||
|
||||
```hcl
|
||||
module "iam" {
|
||||
source = "../modules/simple-iam"
|
||||
# This is only an example, use the actual path to the module or an URL
|
||||
source = "./modules/terraform-azurerm-simple-iam"
|
||||
|
||||
scope = data.azurerm_subscription.current.id
|
||||
principal_id = azuread_service_principal.sp.object_id
|
||||
scope = data.azurerm_subscription.current.id
|
||||
principal_id = azuread_service_principal.sp.object_id
|
||||
principal_type = "ServicePrincipal"
|
||||
|
||||
roles = [
|
||||
"Contributor",
|
||||
@@ -25,18 +27,56 @@ module "iam" {
|
||||
"Key Vault Certificates Officer",
|
||||
]
|
||||
|
||||
restricted_roles = [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
|
||||
# Optional
|
||||
principal_type = "ServicePrincipal"
|
||||
delegable_roles_to_sp_only = true
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** The above example is for demonstration of every input variable. **restricted_roles** and **delegable_roles** will not make sense to use together in most scenarios.
|
||||
|
||||
### Scenarios
|
||||
|
||||
#### Assging roles to user and group principals
|
||||
|
||||
This is a common scenario when you want to assign roles to users and groups in Azure AD. Infrastructure users will not need any delegation permissions, so the `delegable_roles` and `restricted_roles` lists can be left empty. The module will only create the unconditional role assignments.
|
||||
|
||||
**Best practice:** Assign the Reader role at broad scope and add job function roles at more narrow scopes. Work with the end users to identify the right roles and scopes for their needs. Avoid assigning high privilege roles like Owner or User Access Administrator.
|
||||
On production environments, use even more restrictive roles and scopes, for example by avoiding generic roles like Contributor.
|
||||
|
||||
#### Assigning roles to devops engineers
|
||||
|
||||
Devops engineers often need permissions to create resources and assign roles to service principals and managed identities. They often request Owner role for convenience, but this is not a recommended practice for any environment other than lab/sandbox.
|
||||
|
||||
**Best practice:** Assign the unconditional **Contributor** role and use restricted roles to stop them from assigning **Owner**, **User Access Administrator** and **Role Based Access Control Administrator** roles to any principal on relaxed environments. On production environments, use only Job Function roles and delegable roles limited to the specific needs of the project, for example **Storage Blob Data Contributor** and **Key Vault Secrets Officer** roles. Additionally limit their ability to assign roles to service principals only, so they will be able to configure Managed Identities and Service Principals for their applications, but not assign any roles to themselves or others.
|
||||
|
||||
#### Assigning roles to managed identities and service principals
|
||||
|
||||
Managed identities and service principals are often used by applications to access other resources and IaC pipelines to create and manage resources.
|
||||
|
||||
**Best practice:** Application dedicated identities should have only the permissions they need at the narrowest scope possible. IaC pipelines should have permissions to assign only the roles for services that are being deployed. For example, a pipeline deploying a web application that needs to access a storage account and a key vault should have the **Storage Blob Data Contributor**, **Key Vault Secrets User** and **Key Vault Certificates User** delegable roles.
|
||||
|
||||
## Examples
|
||||
|
||||
The `examples` folder contains example usage of the module for different scenarios. The examples are not meant to be deployed as-is, but rather to provide guidance on how to use the module in different ways.
|
||||
|
||||
1. Multiple principals with different role assignments at the same scope.
|
||||
2. A single principal with unconditional roles at different scopes.
|
||||
3. Multiple principals given roles at multiple scopes.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `scope` (string): Scope ID at which to assign roles.
|
||||
- `principal_id` (string): Object ID of the principal.
|
||||
- `roles` (list(string)): Unconditional role definition names to assign.
|
||||
- `delegable_roles` (list(string)): Role definition names allowed by the constrained RBAC Admin condition. When empty, RBAC Admin is not assigned.
|
||||
- `principal_type` (string): Passed to `azurerm_role_assignment.principal_type`.
|
||||
- `roles` (list(string)): Unconditional role definition names to assign.
|
||||
- `delegable_roles` (list(string)): Role definition names allowed by the constrained RBAC Admin condition.
|
||||
- `restricted_roles` (list(string)): Role definition names denied by the constrained RBAC Admin condition.
|
||||
- `delegable_roles_to_sp_only` (bool): When true, RBAC Admin delegation can only assign/delete roles to principals of type ServicePrincipal.
|
||||
|
||||
## Outputs
|
||||
|
||||
48
examples/scenario-1.tf
Normal file
48
examples/scenario-1.tf
Normal file
@@ -0,0 +1,48 @@
|
||||
# Scenario: Multiple principals with different role assignments at the same scope
|
||||
|
||||
variable "principals" {
|
||||
type = map(object({
|
||||
principal_name = string
|
||||
principal_type = string
|
||||
roles = list(string)
|
||||
delegable_roles = optional(list(string))
|
||||
restricted_roles = optional(list(string))
|
||||
}))
|
||||
|
||||
default = {
|
||||
principal1 = {
|
||||
principal_name = "sp-principal1"
|
||||
principal_type = "User"
|
||||
roles = ["Reader"]
|
||||
}
|
||||
principal2 = {
|
||||
principal_name = "sg-admins"
|
||||
principal_type = "Group"
|
||||
roles = ["Contributor"]
|
||||
}
|
||||
principal3 = {
|
||||
principal_name = "john.doe@example.com"
|
||||
principal_type = "User"
|
||||
roles = ["Owner"]
|
||||
restricted_roles = [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "simple_iam" {
|
||||
source = "../modules/terraform-azurerm-simple-iam"
|
||||
|
||||
scope = data.azurerm_subscription.current.id
|
||||
principal_id = each.value.principal_id
|
||||
principal_type = each.value.principal_type
|
||||
roles = each.value.roles
|
||||
|
||||
delegable_roles = try(each.value.delegable_roles, [])
|
||||
restricted_roles = try(each.value.restricted_roles, [])
|
||||
|
||||
for_each = var.principals
|
||||
}
|
||||
30
examples/scenario-1.tfvars.json
Normal file
30
examples/scenario-1.tfvars.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"principals": {
|
||||
"principal1": {
|
||||
"principal_name": "sp-principal1",
|
||||
"principal_type": "User",
|
||||
"roles": [
|
||||
"Reader"
|
||||
]
|
||||
},
|
||||
"principal2": {
|
||||
"principal_name": "sg-admins",
|
||||
"principal_type": "Group",
|
||||
"roles": [
|
||||
"Contributor"
|
||||
]
|
||||
},
|
||||
"principal3": {
|
||||
"principal_name": "john.doe@example.com",
|
||||
"principal_type": "User",
|
||||
"roles": [
|
||||
"Owner"
|
||||
],
|
||||
"restricted_roles": [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
48
examples/scenario-2.tf
Normal file
48
examples/scenario-2.tf
Normal file
@@ -0,0 +1,48 @@
|
||||
# Scenario: A single principal with unconditional roles at different scopes.
|
||||
|
||||
variable "principal" {
|
||||
type = object({
|
||||
principal_name = string
|
||||
principal_id = string
|
||||
principal_type = string
|
||||
})
|
||||
|
||||
default = {
|
||||
principal_name = "sp-platform-ops"
|
||||
principal_id = "00000000-0000-0000-0000-000000000001"
|
||||
principal_type = "ServicePrincipal"
|
||||
}
|
||||
}
|
||||
|
||||
variable "role_assignments" {
|
||||
type = map(object({
|
||||
scope = string
|
||||
roles = list(string)
|
||||
}))
|
||||
|
||||
default = {
|
||||
subscription = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000"
|
||||
roles = ["Reader"]
|
||||
}
|
||||
rg_platform = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-platform"
|
||||
roles = ["Contributor"]
|
||||
}
|
||||
rg_security = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-security"
|
||||
roles = ["Log Analytics Contributor", "Monitoring Reader"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module "simple_iam" {
|
||||
source = "../modules/terraform-azurerm-simple-iam"
|
||||
|
||||
scope = each.value.scope
|
||||
principal_id = var.principal.principal_id
|
||||
principal_type = var.principal.principal_type
|
||||
roles = each.value.roles
|
||||
|
||||
for_each = var.role_assignments
|
||||
}
|
||||
28
examples/scenario-2.tfvars.json
Normal file
28
examples/scenario-2.tfvars.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"principal": {
|
||||
"principal_name": "sp-platform-ops",
|
||||
"principal_id": "00000000-0000-0000-0000-000000000001",
|
||||
"principal_type": "ServicePrincipal"
|
||||
},
|
||||
"role_assignments": {
|
||||
"subscription": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000",
|
||||
"roles": [
|
||||
"Reader"
|
||||
]
|
||||
},
|
||||
"rg_platform": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-platform",
|
||||
"roles": [
|
||||
"Contributor"
|
||||
]
|
||||
},
|
||||
"rg_security": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-security",
|
||||
"roles": [
|
||||
"Log Analytics Contributor",
|
||||
"Monitoring Reader"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
100
examples/scenario-3.tf
Normal file
100
examples/scenario-3.tf
Normal file
@@ -0,0 +1,100 @@
|
||||
# Scenario: Multiple principals given roles at multiple scopes.
|
||||
|
||||
variable "principals" {
|
||||
type = map(object({
|
||||
principal_name = string
|
||||
principal_id = string
|
||||
principal_type = string
|
||||
role_assignments = map(object({
|
||||
scope = string
|
||||
roles = list(string)
|
||||
delegable_roles = optional(list(string))
|
||||
restricted_roles = optional(list(string))
|
||||
}))
|
||||
}))
|
||||
|
||||
default = {
|
||||
principal1 = {
|
||||
principal_name = "sp-app-ops"
|
||||
principal_id = "00000000-0000-0000-0000-000000000011"
|
||||
principal_type = "ServicePrincipal"
|
||||
role_assignments = {
|
||||
subscription = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000"
|
||||
roles = ["Reader"]
|
||||
delegable_roles = [
|
||||
"Reader",
|
||||
"Contributor"
|
||||
]
|
||||
}
|
||||
rg_app = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app"
|
||||
roles = ["Contributor"]
|
||||
delegable_roles = [
|
||||
"Reader",
|
||||
"Contributor"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
principal2 = {
|
||||
principal_name = "sg-security-reviewers"
|
||||
principal_id = "00000000-0000-0000-0000-000000000022"
|
||||
principal_type = "Group"
|
||||
role_assignments = {
|
||||
rg_security = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-security"
|
||||
roles = ["Owner"]
|
||||
restricted_roles = [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
}
|
||||
rg_logs = {
|
||||
scope = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-logs"
|
||||
roles = ["Role Based Access Control Administrator"]
|
||||
restricted_roles = [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
role_assignments = {
|
||||
for item in flatten([
|
||||
for principal_key, principal in var.principals : [
|
||||
for assignment_key, assignment in principal.role_assignments : {
|
||||
key = "${principal_key}_${assignment_key}"
|
||||
value = {
|
||||
scope = assignment.scope
|
||||
roles = assignment.roles
|
||||
principal_id = principal.principal_id
|
||||
principal_type = principal.principal_type
|
||||
delegable_roles = try(assignment.delegable_roles, [])
|
||||
restricted_roles = try(assignment.restricted_roles, [])
|
||||
}
|
||||
}
|
||||
]
|
||||
]) : item.key => item.value
|
||||
}
|
||||
}
|
||||
|
||||
module "simple_iam" {
|
||||
source = "../modules/terraform-azurerm-simple-iam"
|
||||
|
||||
scope = each.value.scope
|
||||
principal_id = each.value.principal_id
|
||||
principal_type = each.value.principal_type
|
||||
roles = each.value.roles
|
||||
delegable_roles = each.value.delegable_roles
|
||||
restricted_roles = each.value.restricted_roles
|
||||
|
||||
for_each = local.role_assignments
|
||||
}
|
||||
60
examples/scenario-3.tfvars.json
Normal file
60
examples/scenario-3.tfvars.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"principals": {
|
||||
"principal1": {
|
||||
"principal_name": "sp-app-ops",
|
||||
"principal_id": "00000000-0000-0000-0000-000000000011",
|
||||
"principal_type": "ServicePrincipal",
|
||||
"role_assignments": {
|
||||
"subscription": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000",
|
||||
"roles": [
|
||||
"Reader"
|
||||
],
|
||||
"delegable_roles": [
|
||||
"Reader",
|
||||
"Contributor"
|
||||
]
|
||||
},
|
||||
"rg_app": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-app",
|
||||
"roles": [
|
||||
"Contributor"
|
||||
],
|
||||
"delegable_roles": [
|
||||
"Reader",
|
||||
"Contributor"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"principal2": {
|
||||
"principal_name": "sg-security-reviewers",
|
||||
"principal_id": "00000000-0000-0000-0000-000000000022",
|
||||
"principal_type": "Group",
|
||||
"role_assignments": {
|
||||
"rg_security": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-security",
|
||||
"roles": [
|
||||
"Owner"
|
||||
],
|
||||
"restricted_roles": [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
},
|
||||
"rg_logs": {
|
||||
"scope": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-logs",
|
||||
"roles": [
|
||||
"Role Based Access Control Administrator"
|
||||
],
|
||||
"restricted_roles": [
|
||||
"Owner",
|
||||
"User Access Administrator",
|
||||
"Role Based Access Control Administrator"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,12 +36,12 @@ variable "delegable_roles" {
|
||||
}
|
||||
|
||||
variable "restricted_roles" {
|
||||
type = list(string)
|
||||
default = []
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Role definitions names that RBAC Administrator is not allowed to assign/delete via ABAC condition."
|
||||
|
||||
validation {
|
||||
condition = length(distinct(var.restricted_roles)) == length(var.restricted_roles)
|
||||
condition = length(distinct(var.restricted_roles)) == length(var.restricted_roles)
|
||||
error_message = "restricted_roles must not contain duplicates."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user