Skip to content

Reader Access: Architecture, Patterns & Options

Reader access in cloud platforms is frequently mischaracterized as a low-risk, trivial permission. In an enterprise landing zone aligned with Microsoft CAF, the Enterprise Access Model (EAM), and NIST SP 800-53, reader access is a multi-dimensional architecture decision: balancing reconnaissance risk against operational visibility, distinguishing control-plane metadata from data-plane secrets, selecting the optimal inheritance scope, and choosing between standing GitOps grants and just-in-time (JIT) PIM activation.


The Core Fundamentals

Before designing reader access, cloud architects must recognize three core security and architectural realities:

1. Control Plane (ARM) vs. Data Plane Isolation

In Azure, permissions are strictly bifurcated between the Management/Control Plane (Azure Resource Manager - ARM) and the Data Plane:

graph LR
    subgraph "Control Plane (ARM API)"
        ARM[Azure Resource Manager]
        ROLE_R["Built-in 'Reader'"]
        ARM --> ROLE_R
        ROLE_R --> MD1["Resource Configurations & SKU"]
        ROLE_R --> MD2["Virtual Network Topology & IP rules"]
        ROLE_R --> MD3["Tags, Metrics & Diagnostic Settings"]
    end

    subgraph "Data Plane (Resource Service APIs)"
        DP[Data Endpoints]
        ROLE_DPR["Data Plane Roles<br/>(e.g., Storage Blob Data Reader)"]
        DP --> ROLE_DPR
        ROLE_DPR --> D1["Storage Blobs & Files"]
        ROLE_DPR --> D2["Key Vault Secret & Key Values"]
        ROLE_DPR --> D3["SQL & Cosmos DB Table Rows"]
    end

    ROLE_R -.->|NO ACCESS| DP
  • Built-in Reader permits inspection of ARM metadata: resource types, IDs, SKUs, networking associations, tags, and diagnostic settings.
  • Built-in Reader strictly CANNOT read data payloads: it cannot view the secret strings in Key Vault, read blobs in a storage container, query rows in Azure SQL or Cosmos DB, or receive messages from a Service Bus queue.
  • Architectural implication: An auditor or engineer holding Reader can verify infrastructure posture, encryption configurations, and logging coverage without gaining exposure to sensitive business data or credentials.

2. The "Mechanism vs. Capability" Paradox

Per Microsoft's Enterprise Access Model (EAM), we must separate the capability a role grants from the mechanism required to assign it:

  • Capability: The Reader role is non-destructive (Tier 2 / Data & Workload plane). A user holding it cannot alter configurations or destroy resources.
  • Mechanism: Provisioning a Reader role assignment at the Tenant Root Group (/) or Intermediate Root (mg-grinntec) requires Owner or User Access Administrator at that top-level scope. That mechanism is Tier 0 / Control Plane.
  • Consequently, while the resulting read access is low privilege, the pipeline or identity that creates that assignment must be governed with Tier 0 controls (see Tier 0 Pipeline Design).

3. Reconnaissance & Information Disclosure Risks

While read access cannot directly modify or delete assets, unconstrained estate-wide read access introduces significant reconnaissance risk: - It reveals internal subnet IP addresses, firewall rules, and virtual network peerings. - It exposes resource naming patterns, subscription IDs, and tenant hierarchy topologies. - It permits an adversary who compromises a reader account to map out high-value targets, security monitoring gaps, and potential attack paths before attempting privilege escalation.


Scoping & Hierarchy Options (Where to Assign)

Azure RBAC inherits downward through the management hierarchy: Management Group → Subscription → Resource Group → Resource. Selecting where reader access is anchored determines both its blast radius and its operational overhead.

graph TD
    ROOT["Tenant Root Group (/)<br/>Option 1: Extreme breadth (rarely used)"]
    MG_INT["mg-grinntec (Intermediate Root)<br/>Option 1: Estate-Wide Central Reader (grp-tenant-readonly)"]

    MG_PLAT["mg-platform<br/>Option 2: Platform Ops Reader"]
    MG_LZ["mg-landingzones<br/>Option 2: Workload Archetype Reader"]
    MG_SB["mg-sandboxes<br/>Option 2: Sandbox Reader"]

    SUB_PROD["Subscription: gt-mkdocs-prod-westeu<br/>Option 3: Per-Sub Reader (grp-gt-mkdocs-prod-westeu-reader)"]
    RG_STATE["Resource Group: rg-terraform-state<br/>Option 4: Scoped Component Reader (sp-*-ro)"]

    ROOT --> MG_INT
    MG_INT --> MG_PLAT
    MG_INT --> MG_LZ
    MG_INT --> MG_SB
    MG_LZ --> SUB_PROD
    SUB_PROD --> RG_STATE

Option 1: Estate-Wide at Intermediate Root (mg-grinntec)

Assigning Reader once at the intermediate root management group (mg-grinntec) automatically inherits downward to every current subscription and any future subscription created by the vending factory.

  • Target Personas: Central CISO audit teams, Enterprise Architects, Central FinOps (using Cost Management Reader), and Cloud Center of Excellence (CCoE) leads.
  • Implementation: Entra ID group grp-tenant-readonly assigned built-in Reader at mg-grinntec via azure-platform-identity.
  • Advantages: Zero operational touch when vending new subscriptions; complete, unbroken estate visibility.
  • Trade-offs: Broadest possible reconnaissance surface; exposes sandbox experiments, production workloads, and core networking simultaneously.

Option 2: Archetype Management Group Scoping (mg-platform vs mg-landingzones)

Instead of granting estate-wide visibility at the root, role assignments are placed at archetype branch management groups: - Readers assigned at mg-landingzones see all workload subscriptions, but have zero visibility into mg-platform (hub networking, central firewalls, shared state backends). - Readers assigned at mg-platform see foundational infrastructure without exposing application payloads or workload subscriptions.

  • Target Personas: Domain architects, platform infrastructure reviewers, business-unit security leads.
  • Advantages: Enforces separation of concerns between shared platform services and application workloads.
  • Trade-offs: Requires multiple group assignments (one per branch) rather than a single root assignment.

Option 3: Per-Subscription Vended Groups (grp-{sub}-reader)

Every subscription provisioned by Grinntec's subscription vending engine automatically receives its own dedicated Entra ID security group: grp-{subscription_name}-reader. This group is bound to the built-in Reader role at that specific subscription scope only.

  • Target Personas: Application developers, workload QA teams, subscription-specific compliance reviewers, and third-party vendor auditors.
  • Implementation: Declared in the subscription bootstrap module call via rbac.reader:
    module "sub_gt_mkdocs_prod_westeu" {
      source = "./modules/subscription-bootstrap"
      ...
      rbac = {
        owner       = { user_upns = ["lead@grinntec.net"], group_names = [] }
        contributor = { user_upns = ["dev@grinntec.net"],  group_names = [] }
        reader      = { user_upns = ["auditor@grinntec.net"], group_names = [] }
      }
    }
    
  • Advantages: Absolute workload boundary isolation; application teams can inspect their full deployment without seeing adjacent services or platform hubs.
  • Trade-offs: Higher group count in Entra ID (1 reader group per subscription).

Option 4: Resource Group or Component Scoping

Role assignments are placed directly on a targeted Resource Group (e.g., rg-terraform-state or rg-hub-networking) or on individual resources.

  • Target Personas: Automation service principals and specialized diagnostic tools.
  • Implementation: Commonly used for CI/CD plan identities:
    resource "azurerm_role_assignment" "ci_plan_state_rg_reader" {
      scope                = azurerm_resource_group.state.id
      role_definition_name = "Reader"
      principal_id         = azuread_service_principal.ci_plan_sp.object_id
    }
    
  • Advantages: Strict least privilege; permits ARM metadata inspection (e.g., terraform init validating storage account configuration) without subscription-wide visibility.
  • Trade-offs: Cannot be handled entirely through generic management group inheritance; requires component-level Terraform declaration.

Option 5: The Hybrid Group Nesting Pattern (Grinntec Standard)

To avoid managing thousands of direct user assignments across hundreds of subscriptions, Grinntec combines management group assignments with group nesting:

graph TD
    CENTRAL["Central Entra Group<br/>(e.g., grp-finops-readers)"]
    SUB_GRP["Vended Subscription Group<br/>(grp-gt-mkdocs-prod-westeu-reader)"]
    USER["Individual User UPN<br/>(e.g., tim.burgess@grinntec.net)"]
    SUB["Subscription Scope<br/>Role: Reader"]

    CENTRAL -->|Nested Member via subscription-memberships.tf| SUB_GRP
    USER -->|Direct Member via rbac variable| SUB_GRP
    SUB_GRP -->|azurerm_role_assignment| SUB
  • Platform-wide groups (grp-platform-engineers, grp-finops-readers) can be nested directly into per-subscription reader groups using subscription-memberships.tf.
  • Subscription vending modules can accept external group IDs via reader_group_ids.
  • Result: Central teams gain automatic access through existing per-subscription group infrastructure without altering the subscription's internal RBAC definitions.

Role Specialization Options (What to Assign)

Assigning generic Reader everywhere often violates NIST AC-6 (Least Privilege) when the user only requires a specific slice of information. Azure provides specialized built-in reader roles that should be selected based on the persona:

Built-In Role Primary Scope Capabilities Granted Capabilities Denied Ideal Persona
Reader MG, Sub, RG Views all ARM resource configurations, tags, deployment history, topology Cannot read data-plane payloads (blobs, secrets, DB rows) Developers, Architects, General Auditors
Cost Management Reader MG or Sub Views billing accounts, invoices, cost analysis, budget thresholds, reservation recommendations Cannot view resource configurations, networking, or ARM resource blades FinOps, Finance, Procurement
Monitoring Reader MG, Sub, RG Views Azure Monitor metrics, alert rules, diagnostic settings, Log Analytics workspaces, workbooks Cannot inspect underlying application code or sensitive resource configurations SRE, NOC Engineers, Dashboard Tools
Security Reader Tenant, MG, Sub Views Microsoft Defender for Cloud alerts, secure scores, regulatory compliance maps, vulnerability assessments Cannot modify security policies or remediate recommendations SOC Tier 1, Security Compliance Officers
Key Vault Reader RG or Key Vault Reads Key Vault resource metadata (network ACLs, tags, diagnostics) Cannot view, read, or export the actual secret, key, or certificate values Cloud Governance & Vault Infrastructure Auditors
Storage Blob Data Reader Storage Account or Container Data-Plane Role: Reads, downloads, and lists blobs inside storage containers Cannot modify storage account ARM properties Data Analysts, Backup Verifiers

Principle of Least Privilege: FinOps vs. Engineering

Do not assign generic Reader to the finance team. Assign Cost Management Reader at mg-grinntec. This gives full cost visibility across all current and future subscriptions while preventing non-technical teams from inadvertently inspecting resource topologies or security settings.


Access Governance & Activation Models (How Access is Granted)

How reader access is held and delivered determines the organisation's exposure to credential theft and lateral movement:

graph LR
    subgraph "Model A: Standing GitOps"
        MR_A[GitLab MR] --> TF_A[Terraform Apply] --> GRP_A[Standing Group Membership]
    end

    subgraph "Model B: Just-In-Time (PIM)"
        USER_B[User Request] --> PIM_B[Entra PIM Activation<br/>MFA + Justification] --> TIME_B[Time-Bound Role<br/>(4 to 8 Hours)]
    end

    subgraph "Model C: Machine Identity"
        OIDC_C[GitLab OIDC] --> SP_C[sp-*-ro Service Principal] --> PLAN_C[Plan-Only Pipeline]
    end

Model A: Standing GitOps-Managed Groups (Continuous Access)

  • How it works: Members are declared as UPN lists in Terraform (azure-platform-identity or azure-subscriptions). Changes require a Merge Request, peer review, and CI apply.
  • When to use: Day-to-day workload developers who continuously monitor application infrastructure; FinOps automated cost ingestors.
  • Controls: Protected branch rules, mandatory code reviews, automated CI syntax validation.

Model B: Just-In-Time (JIT) via Entra Privileged Identity Management (PIM)

  • How it works: Users do not hold standing Reader access. Instead, they are provisioned as Eligible in Entra PIM for Azure Resources. When an audit or investigation is needed, the user activates the role via the Azure portal or CLI for a time-bounded window (e.g., 4 or 8 hours) requiring MFA, business justification, and optional peer approval.
  • When to use:
  • Read access to Production subscriptions (mg-online, gt-mkdocs-prod-westeu).
  • Read access to Central Management / Logging (mg-management, hosting central Log Analytics and audit trails).
  • External third-party auditors or contractors.
  • Controls: Aligns with NIST AC-2(6) (Dynamic Privilege Management) and NIST AC-6(5) (Privileged Accounts), eliminating permanent discovery footprints.

Access reviews apply on top of Models A and B

Access reviews are not a separate way of granting access. They are a control that applies to access however it was granted. A scheduled Entra ID access review asks group owners or managers to confirm that each reader still needs access, and removes anyone who doesn't. Compliance frameworks such as SOC 2, ISO 27001 and PCI-DSS expect this, to stop orphaned accounts and access creep.

Access reviews are not configured in the tenant yet. Reader access granted through the Read-Only @ Landing Zones access package expires after 90 days and must be requested again. Standing reader groups (Model A) are not reviewed at all today. See Access Reviews for the recommended scopes, frequencies and reviewers.

Model C: Machine Read-Only Pipelines (Workload Identity Federation)

  • How it works: Automated CI/CD pipelines require read access to generate Terraform execution plans, run security scans (Trivy, Checkov), or verify posture.
  • Design pattern: Under Grinntec's Dual-Lane delivery model, read-only service principals (sp-pla-tf-*-ro) hold Reader at their respective management group or subscription and are authenticated strictly via passwordless OIDC federated tokens (no stored secrets).

Comparative Decision Matrix

Use this matrix to select the appropriate reader pattern for any project or team:

Persona / Scenario Recommended Scope Role Definition Delivery Model Blast Radius Overhead
Central CISO / Audit Lead mg-grinntec Reader + Security Reader PIM Eligible (JIT) Estate-wide (ARM metadata) Low (1 central grant)
FinOps / Cost Analyst mg-grinntec Cost Management Reader Standing (GitOps) Financial data only Low (1 central grant)
Platform Ops Engineer mg-platform Reader Standing (GitOps) Platform subscriptions Low
Workload App Developer Target Subscription Reader Standing (GitOps) Single subscription Medium (1 group per sub)
Production Incident Responder Production Subscription Reader PIM Eligible (4h window) Single production sub Low (PIM managed)
External Security Auditor Target MG or Sub Reader PIM Eligible (Approved) Strictly bounded Low (Time-bound)
CI/CD Pull Request Runner Target Sub or RG Reader OIDC Machine Identity Bounded to pipeline scope Automated

Grinntec Reference Implementation & Terraform Patterns

Here is how these options are implemented in Grinntec's codebase:

1. Estate-Wide Central Reader (azure-platform-identity)

Declared at the intermediate root management group mg-grinntec in grp-tenant-readonly.tf:

# azure-platform-identity/grp-tenant-readonly.tf

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

resource "azuread_group" "tenant_readonly" {
  display_name     = "grp-tenant-readonly"
  security_enabled = true
  mail_enabled     = false
}

resource "azuread_group_member" "tenant_readonly" {
  for_each         = local.tenant_readonly_upns
  group_object_id  = azuread_group.tenant_readonly.object_id
  member_object_id = data.azuread_user.users[each.key].object_id
}

resource "azurerm_role_assignment" "tenant_readonly" {
  scope                = data.azurerm_management_group.mg_grinntec.id
  role_definition_name = "Reader"
  principal_id         = azuread_group.tenant_readonly.object_id
}

2. FinOps Role Specialization (azure-platform-identity)

Assigning Cost Management Reader instead of generic Reader:

# azure-platform-identity/grp-finops-readers.tf

resource "azurerm_role_assignment" "finops_reader" {
  scope                = data.azurerm_management_group.mg_grinntec.id
  role_definition_name = "Cost Management Reader"
  principal_id         = azuread_group.finops_readers.object_id
}

3. Vended Per-Subscription Reader Group (azure-subscriptions)

Subscription factory automatically vending grp-{sub}-reader and binding it:

# azure-subscriptions/modules/subscription-bootstrap/main.tf

resource "azuread_group" "sub_reader" {
  display_name     = "grp-${var.subscription_name}-reader"
  security_enabled = true
  mail_enabled     = false
}

resource "azurerm_role_assignment" "sub_reader" {
  scope                = "/subscriptions/${var.subscription_id}"
  role_definition_name = "Reader"
  principal_id         = azuread_group.sub_reader.object_id
}

# Optional extra reader groups (e.g. centralized security readers)
resource "azurerm_role_assignment" "extra_readers" {
  for_each             = toset(var.reader_group_ids)
  scope                = "/subscriptions/${var.subscription_id}"
  role_definition_name = "Reader"
  principal_id         = each.value
}

4. Group Nesting Pattern (azure-platform-identity)

Nesting central groups into per-subscription groups without modifying subscription role assignments:

# azure-platform-identity/subscription-memberships.tf

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

resource "azuread_group_member" "sub_reader_nesting" {
  for_each = {
    for pair in local.subscription_reader_pairs : "${pair.sub}-${pair.group}" => pair
  }

  group_object_id  = data.azuread_group.sub_reader_groups[each.value.sub].object_id
  member_object_id = data.azuread_group.platform_groups[each.value.group].object_id
}

Architectural Recommendations

  1. Default to Role Specialization: Never assign generic Reader where Cost Management Reader or Monitoring Reader fulfills the requirement.
  2. Isolate Workloads from Platform: For non-central personnel, restrict reader grants to mg-landingzones or the per-subscription grp-{sub}-reader group rather than mg-grinntec.
  3. Use PIM for Production and Sensitive Scopes: Enforce JIT activation with required business justification for any read access granted on production subscriptions or management groups hosting audit repositories (mg-management).
  4. Never conflate Control Plane with Data Plane: Explicitly document to audit and engineering teams that ARM Reader does not grant access to Key Vault secrets or storage blob contents. If data access is legitimately needed, use dedicated data-plane roles subject to mandatory PIM approval.