Skip to content

Platform Identity & RBAC

The platform identity model defines who can access what, at what scope, using Entra ID security groups rather than individual role assignments. Groups are assigned at management group scope so access automatically covers every subscription that exists now and any added in the future.


The model

Access in the Grinntec tenant is structured in two layers:

Platform groups — managed in azure-platform-identity, assigned at mg-grinntec scope. These give team-level access across all subscriptions simultaneously.

Per-subscription groups — created automatically by the subscription bootstrap module when a new subscription is vended. These give workload-specific access scoped to a single subscription.

Platform groups are nested into per-subscription groups so that platform team members automatically have access to every subscription without per-subscription configuration.

graph TD
    PE["grp-platform-engineers<br/>Owner at mg-grinntec"]
    FR["grp-finops-readers<br/>Cost Management Reader at mg-grinntec"]
    TR["grp-tenant-readonly<br/>Reader at mg-grinntec"]

    subgraph "Per subscription (auto-created by bootstrap)"
        SC["grp-{sub}-contributor<br/>Contributor"]
        SR["grp-{sub}-reader<br/>Reader"]
    end

    PE -->|nested member| SC
    FR -->|nested member| SR

Platform groups

These three groups cover the platform team's permanent access needs. All are managed in azure-platform-identity.

grp-platform-engineers

Role: Owner at mg-grinntec
Inherits to: Every subscription in the tenant

Platform engineers need Owner to manage role assignments (e.g., granting cross-subscription access for workload SPs) and to create and configure resources across the entire estate. Owner at the intermediate root rather than the tenant root keeps this within the Grinntec-controlled scope.

locals {
  platform_engineer_upns = toset([
    "neil@grinntec.net",
    "brett.anderson@grinntec.net",
  ])
}

grp-finops-readers

Role: Cost Management Reader at mg-grinntec
Inherits to: Every subscription in the tenant

Gives the finance and audit team visibility into cost data, usage, and budgets across all subscriptions without the ability to create or modify resources. Scoped to Cost Management Reader — not Reader — so it grants cost visibility only.

locals {
  finops_reader_upns = toset([
    "justine.frischmann@grinntec.net",
    "liam.gallagher@grinntec.net",
    "louise.wener@grinntec.net",
    "mark.morriss@grinntec.net",
  ])
}

grp-tenant-readonly

Role: Reader at mg-grinntec
Inherits to: Every subscription in the tenant

Tenant-wide read access for stakeholders who need visibility across the estate — senior management, security reviewers, or external auditors. Can view all resources and configurations but cannot make any changes.

locals {
  tenant_readonly_upns = toset([
    "cerys.matthews@grinntec.net",
    "damon.albarn@grinntec.net",
    "jarvis.cocker@grinntec.net",
  ])
}

Per-subscription groups

When a subscription is vended, the bootstrap module automatically creates three Entra ID security groups scoped to that subscription:

Group Role Purpose
grp-{name}-owner Owner Break-glass or privileged access. Assign sparingly.
grp-{name}-contributor Contributor Day-to-day workload engineers.
grp-{name}-reader Reader Auditors, read-only oversight.

Membership in these groups is managed via the rbac variable in the subscription's bootstrap module call — not as separate Terraform resources. This keeps everything in one place:

module "bootstrap_gt_mkdocs_prod_westeu" {
  ...
  rbac = {
    owner       = { user_upns = ["mark.morriss@grinntec.net"],                            group_names = [] }
    contributor = { user_upns = ["richard.ashcroft@grinntec.net", "damon.albarn@grinntec.net"], group_names = [] }
    reader      = { user_upns = ["tim.burgess@grinntec.net"],                              group_names = [] }
  }
}

Subscription memberships

The subscription-memberships.tf file in azure-platform-identity nests platform groups into per-subscription groups. This is what gives platform engineers contributor access to every managed subscription — through group nesting rather than direct role assignments.

locals {
  subscriptions = {
    "gt-sandbox-dev-westeu-01" = {
      contributor_groups = ["grp-platform-engineers"]
      reader_groups      = ["grp-finops-readers"]
    }
    "gt-mkdocs-prod-westeu" = {
      contributor_groups = ["grp-platform-engineers"]
      reader_groups      = ["grp-finops-readers"]
    }
  }
}

When a new subscription is vended, add an entry here to nest the appropriate platform groups into its RBAC groups.

Note

grp-platform-engineers gets Owner at mg-grinntec via the management group role assignment — which already gives Owner on every subscription. The subscription-level contributor nesting via subscription-memberships.tf is belt-and-braces, and also useful for any future subscriptions that might land outside mg-grinntec scope.


Adding and removing members

Members are managed as a list of UPNs (email addresses) in the relevant grp-*.tf file. Terraform looks up each UPN in Entra ID to get the object ID — no GUIDs to find or paste.

To add a member to grp-platform-engineers:

  1. Add their UPN to the platform_engineer_upns set in grp-platform-engineers.tf
  2. Open a Merge Request in azure-platform-identity
  3. Review the plan — it will show one azuread_group_member to be created
  4. Merge — the pipeline applies

To remove a member, delete their UPN from the list and follow the same process.


How it all fits together

graph LR
    subgraph "azure-management-groups"
        MG["mg-grinntec<br/>mg-platform / mg-online<br/>mg-sandboxes..."]
    end

    subgraph "azure-platform-identity"
        PE["grp-platform-engineers → Owner at mg-grinntec"]
        FR["grp-finops-readers → Cost Mgmt Reader at mg-grinntec"]
        TR["grp-tenant-readonly → Reader at mg-grinntec"]
        SM["subscription-memberships.tf<br/>nests platform groups into sub groups"]
    end

    subgraph "azure-subscriptions (per sub)"
        BS["Bootstrap module creates:<br/>grp-{sub}-owner<br/>grp-{sub}-contributor<br/>grp-{sub}-reader"]
    end

    MG -->|role scope| PE
    MG -->|role scope| FR
    MG -->|role scope| TR
    BS -->|groups exist| SM
    SM -->|nesting| BS

The three deployments are independent — each with its own service principal and pipeline. A change to platform identity does not touch subscription infrastructure, and vice versa.


Service principal permissions

The sp-terraform-azure-platform-identity service principal that runs the pipeline requires:

Permission Where Why
Groups Administrator Entra ID (tenant) Create security groups and manage membership
User Access Administrator mg-grinntec (management group) Assign roles to groups at management group scope

It has no Contributor or Owner on any subscription — it cannot create or modify Azure resources, only Entra groups and role assignments.