-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/entra id groups #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
younGihan
wants to merge
6
commits into
main
Choose a base branch
from
feature/entra-ID-groups
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f0f0aa0
feat: entra ID group creation building block
younGihan 8e9a8f5
feat: adding users to groups
younGihan 2e3e019
fix: adding user based on UPN not GUID
younGihan bf8bc10
fix: adding user lookup strategy
younGihan 980414e
update: update backplane
younGihan 311da14
chore: solve comments
florianow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # Azure Entra ID Groups — Backplane | ||
|
|
||
| This backplane creates the automation identity used to provision Entra security groups for meshStack project roles. | ||
|
|
||
| ## What it provisions | ||
|
|
||
| - **User-Assigned Managed Identity (UAMI)** — the automation principal that runs the building block. No client secrets. | ||
| - **Resource Group** — hosts the UAMI in the configured Azure region. | ||
| - **Workload Identity Federation credentials** — bind the UAMI to the meshStack replicator's OIDC issuer and subject, enabling secret-free authentication. | ||
| - **Microsoft Graph app roles** on the UAMI: | ||
| - `User.Read.All` — look up users by UPN or primary mail address to resolve object IDs for group membership. | ||
| - `Group.ReadWrite.All` — create and manage Entra security groups. | ||
| - `AdministrativeUnit.ReadWrite.All` — add groups to Administrative Units (used when `administrative_unit_id` is supplied at building block runtime). | ||
|
|
||
| ## Required permissions to deploy | ||
|
|
||
| The platform engineer running this backplane needs: | ||
|
|
||
| | Permission | Scope | Why | | ||
| |---|---|---| | ||
| | `Managed Identity Contributor` | Target subscription | Create and update the UAMI | | ||
| | `Owner` or `User Access Administrator` | `var.scope` | Create role assignments on the UAMI | | ||
| | `Privileged Role Administrator` (Entra) | Tenant | Grant admin-consented Microsoft Graph app roles | | ||
|
|
||
| ## Operational notes | ||
|
|
||
| - The UAMI principal ID maps to a service principal in Entra. The `User.Read.All`, `Group.ReadWrite.All`, and `AdministrativeUnit.ReadWrite.All` app role assignments require **admin consent** — ensure a Global Administrator or Privileged Role Administrator approves the assignments in the Entra portal after the first `apply`. | ||
| - No secrets are created; the UAMI authenticates via OIDC token exchange. | ||
| - The backplane resource group is named after `var.name` and must be unique within the subscription. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| resource "azurerm_resource_group" "this" { | ||
| name = var.name | ||
| location = var.location | ||
| } | ||
|
|
||
| resource "azurerm_user_assigned_identity" "this" { | ||
| name = var.name | ||
| location = var.location | ||
| resource_group_name = azurerm_resource_group.this.name | ||
| } | ||
|
|
||
| resource "azurerm_federated_identity_credential" "this" { | ||
| for_each = { for i, s in var.workload_identity_federation.subjects : tostring(i) => s } | ||
|
|
||
| name = "subject-${each.key}" | ||
| resource_group_name = azurerm_resource_group.this.name | ||
| parent_id = azurerm_user_assigned_identity.this.id | ||
| audience = ["api://AzureADTokenExchange"] | ||
| issuer = var.workload_identity_federation.issuer | ||
| subject = each.value | ||
| } | ||
|
|
||
| # Grant Microsoft Graph app roles so the UAMI can read users, manage groups, and manage Administrative Unit members. | ||
| data "azuread_service_principal" "msgraph" { | ||
| client_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph | ||
| } | ||
|
|
||
| resource "azuread_app_role_assignment" "user_read_all" { | ||
| app_role_id = data.azuread_service_principal.msgraph.app_role_ids["User.Read.All"] | ||
| principal_object_id = azurerm_user_assigned_identity.this.principal_id | ||
| resource_object_id = data.azuread_service_principal.msgraph.object_id | ||
| } | ||
|
|
||
| resource "azuread_app_role_assignment" "group_readwrite_all" { | ||
| app_role_id = data.azuread_service_principal.msgraph.app_role_ids["Group.ReadWrite.All"] | ||
| principal_object_id = azurerm_user_assigned_identity.this.principal_id | ||
| resource_object_id = data.azuread_service_principal.msgraph.object_id | ||
| } | ||
|
|
||
| resource "azuread_app_role_assignment" "administrative_unit_readwrite_all" { | ||
| app_role_id = data.azuread_service_principal.msgraph.app_role_ids["AdministrativeUnit.ReadWrite.All"] | ||
| principal_object_id = azurerm_user_assigned_identity.this.principal_id | ||
| resource_object_id = data.azuread_service_principal.msgraph.object_id | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| output "identity" { | ||
| description = "UAMI identity attributes consumed by meshstack_integration.tf as static inputs." | ||
| value = { | ||
| client_id = azurerm_user_assigned_identity.this.client_id | ||
| principal_id = azurerm_user_assigned_identity.this.principal_id | ||
| tenant_id = azurerm_user_assigned_identity.this.tenant_id | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| provider "azurerm" { | ||
| features {} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| variable "name" { | ||
| type = string | ||
| nullable = false | ||
| description = "Name for the UAMI and related backplane resources. Must match pattern ^[-a-z0-9]+$." | ||
|
|
||
| validation { | ||
| condition = can(regex("^[-a-z0-9]+$", var.name)) | ||
| error_message = "Only lowercase alphanumeric characters and dashes are allowed." | ||
| } | ||
| } | ||
|
|
||
| variable "location" { | ||
| type = string | ||
| nullable = false | ||
| description = "Azure region for the backplane resource group and UAMI." | ||
| } | ||
|
|
||
| variable "workload_identity_federation" { | ||
| type = object({ | ||
| issuer = string | ||
| subjects = list(string) | ||
| }) | ||
| nullable = false | ||
| description = "WIF issuer and subjects for federated authentication from the meshStack replicator." | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| terraform { | ||
| required_version = ">= 1.0.0" | ||
|
|
||
| required_providers { | ||
| azurerm = { | ||
| source = "hashicorp/azurerm" | ||
| version = ">= 4.0" | ||
| } | ||
| azuread = { | ||
| source = "hashicorp/azuread" | ||
| version = ">= 3.8" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| name: Azure Entra ID Groups | ||
| supportedPlatforms: | ||
| - azure | ||
| description: Creates Entra security groups for meshStack project roles, with optional Administrative Unit membership. | ||
| --- | ||
|
|
||
| Automatically provision Entra ID security groups for every role in a meshStack project. Groups are named consistently using the workspace identifier, project identifier, an optional prefix, and the role name as suffix — giving your teams a predictable, auditable group structure in Azure Active Directory. | ||
|
|
||
| ## When to use it | ||
|
|
||
| Use this building block when you want to: | ||
| - Map meshStack project roles (admin, user, reader, or custom roles) to Entra security groups for RBAC assignments in Azure. | ||
| - Enforce a standard naming scheme across all projects in your platform. | ||
| - Optionally scope groups inside a dedicated Entra Administrative Unit to isolate tenant-level identities from the rest of the directory. | ||
|
|
||
| ## Usage examples | ||
|
|
||
| **Default meshStack roles (admin / user / reader):** | ||
|
|
||
| A project `my-project` in workspace `my-workspace` with prefix `plat` produces three groups: | ||
| - `plat-my-workspace-my-project-admin` | ||
| - `plat-my-workspace-my-project-user` | ||
| - `plat-my-workspace-my-project-reader` | ||
|
|
||
| **Custom roles:** | ||
|
|
||
| Set *Project Roles* to `devops,qa,readonly` to create: | ||
| - `plat-my-workspace-my-project-devops` | ||
| - `plat-my-workspace-my-project-qa` | ||
| - `plat-my-workspace-my-project-readonly` | ||
|
|
||
| **With Administrative Unit:** | ||
|
|
||
| Provide the object ID of an existing Entra Administrative Unit. All generated groups are added as members of that AU, restricting who can manage them in the directory. | ||
|
|
||
| ## Shared Responsibilities | ||
|
|
||
| | Responsibility | Platform Team | Application Team | | ||
| |---|:---:|:---:| | ||
| | Deploy and configure the backplane identity | ✅ | ❌ | | ||
| | Define the group naming prefix | ✅ | ❌ | | ||
| | Create and delete Entra groups | ✅ | ❌ | | ||
| | Add the Administrative Unit (optional) | ✅ | ❌ | | ||
| | Choose which project roles get groups | ❌ | ✅ | | ||
| | Assign users to the generated groups | ❌ | ✅ | | ||
| | Use group IDs in downstream RBAC assignments | ❌ | ✅ | |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| locals { | ||
| roles = [for r in split(",", var.project_roles) : trimspace(r) if trimspace(r) != ""] | ||
| au_id = var.administrative_unit_id != "" ? var.administrative_unit_id : null | ||
| name_parts = compact([var.prefix, var.workspace_identifier, var.project_identifier]) | ||
|
|
||
| unique_user_euids = toset([for user in var.users : user.euid]) | ||
|
|
||
| user_role_assignments = { | ||
| for pair in flatten([ | ||
| for user in var.users : [ | ||
| for role in user.roles : { | ||
| key = "${user.euid}-${role}" | ||
| euid = user.euid | ||
| role = role | ||
| } | ||
| ] | ||
| ]) : pair.key => pair | ||
| if contains(local.roles, pair.role) | ||
| } | ||
| } | ||
|
|
||
| data "azuread_user" "by_upn" { | ||
| for_each = var.user_lookup_attribute == "upn" ? local.unique_user_euids : toset([]) | ||
| user_principal_name = each.value | ||
| } | ||
|
|
||
| data "azuread_user" "by_email" { | ||
| for_each = var.user_lookup_attribute == "email" ? local.unique_user_euids : toset([]) | ||
| mail = each.value | ||
| } | ||
|
|
||
| resource "azuread_group" "project_role" { | ||
| for_each = toset(local.roles) | ||
|
|
||
| display_name = join(".", concat(local.name_parts, [each.value])) | ||
| security_enabled = true | ||
| mail_enabled = false | ||
| administrative_unit_ids = local.au_id != null ? [local.au_id] : [] | ||
| } | ||
|
|
||
| resource "azuread_group_member" "project_role" { | ||
| for_each = local.user_role_assignments | ||
|
|
||
| group_object_id = azuread_group.project_role[each.value.role].object_id | ||
| member_object_id = var.user_lookup_attribute == "upn" ? data.azuread_user.by_upn[each.value.euid].object_id : data.azuread_user.by_email[each.value.euid].object_id | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| output "group_object_ids" { | ||
| description = "Map of project role name to Entra group object ID." | ||
| value = { for role, g in azuread_group.project_role : role => g.object_id } | ||
| } | ||
|
|
||
| output "group_display_names" { | ||
| description = "Map of project role name to Entra group display name." | ||
| value = { for role, g in azuread_group.project_role : role => g.display_name } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| provider "azuread" {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| variable "prefix" { | ||
| type = string | ||
| nullable = false | ||
| description = "Optional prefix prepended to all group display names. Leave empty to omit." | ||
| } | ||
|
|
||
| variable "workspace_identifier" { | ||
| type = string | ||
| nullable = false | ||
| description = "meshStack workspace identifier included in the group name." | ||
| } | ||
|
|
||
| variable "project_identifier" { | ||
| type = string | ||
| nullable = false | ||
| description = "meshStack project identifier included in the group name." | ||
| } | ||
|
|
||
| variable "project_roles" { | ||
| type = string | ||
| nullable = false | ||
| description = "Comma-separated list of project role name suffixes. One Entra group is created per role. Defaults to the three standard meshStack roles: admin, user, reader." | ||
| } | ||
|
|
||
| variable "administrative_unit_id" { | ||
| type = string | ||
| nullable = false | ||
| description = "Object ID of the Entra Administrative Unit to add the groups to. Leave empty to skip AU membership." | ||
| } | ||
|
|
||
| variable "user_lookup_attribute" { | ||
| type = string | ||
| nullable = false | ||
| description = "Azure AD attribute used to look up users. 'upn' matches on User Principal Name; 'email' matches on the primary mail address." | ||
| validation { | ||
| condition = contains(["upn", "email"], var.user_lookup_attribute) | ||
| error_message = "Must be 'upn' or 'email'." | ||
| } | ||
| } | ||
|
|
||
| variable "users" { | ||
| type = list(object({ | ||
| meshIdentifier = string | ||
| username = string | ||
| firstName = string | ||
| lastName = string | ||
| email = string | ||
| euid = string | ||
| roles = list(string) | ||
| })) | ||
| nullable = false | ||
| description = "Project members from meshStack with their assigned roles. Each user is added to the group matching their role." | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| terraform { | ||
| required_providers { | ||
| azuread = { | ||
| source = "hashicorp/azuread" | ||
| version = ">= 3.8.0" | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d: I remember having this admin consent thing automated in the past, as this can become really annoying as admins need to approve stuff. maybe worthwhile to discuss this f2f or at least mention it.