Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

6. Integrations

11 min read

RBAC is the least standalone thing in Azure. It has no purpose of its own — it exists to make other services reachable by the right callers, so every integration on this page is really an answer to the question "how do these two resources talk to each other without a secret?"

Two glue mechanisms recur so often that they're worth stating before the table, because between them they answer most of those questions:

Managed identity + a role assignment — the keyless way one Azure resource authenticates to another. The identity provides who, the role assignment provides may. This pairing replaces connection strings, account keys, and stored passwords across essentially the whole catalogue.

Private endpoint + Private DNS zone — the way one Azure resource reaches another without traversing the public internet. Orthogonal to RBAC and frequently confused with it: a private endpoint controls reachability, RBAC controls authorisation. A caller needs both, and neither implies the other. Say which sub-resource the endpoint targets (blob, vault, sqlServer), because getting it wrong produces a DNS resolution that succeeds and a connection that hangs.

[Image Prompt: 2D minimalistic hub-and-spoke diagram with Azure RBAC at the centre connected by labelled edges to Microsoft Entra ID, managed identities, Key Vault, Azure Storage, Azure Policy, Azure Kubernetes Service, Azure SQL Database, Azure Monitor, and Terraform, each edge labelled with its glue mechanism, flat design, clean vector art style, white background]


The pairings that matter

Pairs with Why The glue
Microsoft Entra ID Supplies every principal RBAC can reference; nothing works without it The oid claim in the token is the object ID the role assignment points at. Group membership arrives as a token claim
Managed identities Gives a workload an identity with no credential to leak identity {} on the resource, then one narrow dataActions role assignment. Prefer user-assigned so replacement doesn't orphan the grant
Key Vault Holds the secrets that remain after you've eliminated the avoidable ones Set enableRbacAuthorization = true, then Key Vault Secrets User on the vault or a single secret. This retires access policies — the legacy parallel model
Blob Storage The canonical control-plane/data-plane demonstration Storage Blob Data Reader at container scope, plus allowSharedKeyAccess = false so keys stop being a bypass
Azure Policy Enforces the shape of access that RBAC merely expresses Policy at a management group auditing or denying Owner assignments at subscription scope; DeployIfNotExists to place a required assignment
Azure Monitor / Log Analytics The only record of who granted what A diagnostic setting routing the activity log to a workspace; KQL over AzureActivity for assignment writes
AKS Two RBAC systems in one product, and they can be wired together Azure RBAC for Kubernetes authorisation maps Azure role assignments to in-cluster permissions; workload identity federates a pod service account to a managed identity
Azure SQL Database Database access without a password Entra-only authentication, an Entra group as the database admin, and CREATE USER … FROM EXTERNAL PROVIDER for the workload's identity
GitHub Actions The pipeline needs the most dangerous permission in Azure Workload identity federation to an Entra app registration, granted Role Based Access Control Administrator — never Owner, never a client secret
Azure Functions / App Service The most common consumer of a data-plane role Managed identity plus a Key Vault reference in app settings, resolved at startup — which is why propagation timing matters here
Entra PIM Adds the time dimension to a role assignment roleEligibilityScheduleRequests through ARM, so eligibility is IaC and activation is an event
Terraform / the pipeline identity The thing that manages RBAC is itself governed by RBAC Federated credential scoped to one repo and branch; the identity holds roleAssignments/write and must not be able to widen itself

Managed identity + role assignment, in full

The pattern behind most of the table. Three moving parts, and the ordering of the third is where people get caught.

# 1. The identity — user-assigned, so replacement of the consumer doesn't orphan the grant
resource "azurerm_user_assigned_identity" "app" {
  name                = "id-payments-prod"
  resource_group_name = azurerm_resource_group.this.name
  location            = azurerm_resource_group.this.location
}

# 2. The consumer, wearing that identity
resource "azurerm_linux_function_app" "app" {
  # …
  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.app.id]
  }
  app_settings = {
    # Tells the SDK which identity to use when several are attached
    AZURE_CLIENT_ID = azurerm_user_assigned_identity.app.client_id
  }
}

# 3. The grant — narrowest scope that works
resource "azurerm_role_assignment" "app_reads_secrets" {
  scope                = azurerm_key_vault.this.id
  role_definition_name = "Key Vault Secrets User"
  principal_id         = azurerm_user_assigned_identity.app.principal_id
  principal_type       = "ServicePrincipal"
  description          = "Function app reads connection strings at startup"
}

AZURE_CLIENT_ID is the detail that saves an afternoon. When a resource has multiple identities attached, DefaultAzureCredential cannot guess which to use and the failure is a confusing 403 rather than a clear error. With one user-assigned identity it's optional; set it anyway.

On the consuming side there is no credential code at all:

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

client = SecretClient(
    vault_url="https://kv-payments-prod.vault.azure.net",
    credential=DefaultAzureCredential(),
)
conn = client.get_secret("db-connection-string").value

DefaultAzureCredential walks a chain — environment variables, managed identity, az login, and others. Convenient, and occasionally maddening: it can succeed locally using your generous permissions and fail in Azure where the managed identity has less. When debugging a 403, establish which identity the token belongs to before touching roles.


Key Vault — the migration that makes RBAC real

Key Vault shipped with access policies: a per-vault list of principals and permitted operations, entirely separate from RBAC. It still works, and while it's enabled you have two authorisation systems on the same vault.

resource "azurerm_key_vault" "this" {
  name                       = "kv-payments-prod"
  resource_group_name        = azurerm_resource_group.this.name
  location                   = azurerm_resource_group.this.location
  tenant_id                  = data.azurerm_client_config.current.tenant_id
  sku_name                   = "standard"

  enable_rbac_authorization  = true    # retires access policies for this vault
  purge_protection_enabled   = true
  soft_delete_retention_days = 90
}

Two things about this block are worth flagging.

enable_rbac_authorization = true is one-way in practice. Flipping it makes every existing access policy inert immediately — so migrate by adding the equivalent role assignments first, verifying, then flipping. Doing it in the other order is an outage.

purge_protection_enabled = true cannot be turned off. It's the right setting for production and it has an RBAC-adjacent consequence: a deleted vault keeps its name reserved for the retention period, so a recreate fails, and so does every role assignment your module wanted to make against it. The apply error is about the vault; the incident gets reported as "RBAC broke".

The role mapping, since the names don't line up with the old verbs:

Access-policy permission RBAC role
Secrets: get, list Key Vault Secrets User
Secrets: all Key Vault Secrets Officer
Keys: crypto operations Key Vault Crypto User
Certificates: manage Key Vault Certificates Officer
Manage the vault resource itself Key Vault Contributorand it reads no secrets

That last row is the control-plane/data-plane line in one sentence, in the service where it surprises people most.


Azure Policy — enforcing the access model

RBAC expresses who may do what; Policy is how you stop someone expressing the wrong thing. The three policies worth having, in rough order of value:

Audit or deny privileged assignments at subscription scope. Owner and User Access Administrator at a subscription are the grants that matter. There are built-in definitions in this area ⚠️ verify the current built-in set against current Azure docs.

Require the description field on new role assignments. Turns the drift query in Deployment from heuristic to reliable: anything without a description wasn't made by your pipeline.

DeployIfNotExists for assignments that must always exist. The security team's Security Reader on every subscription, for instance — a remediation task places it on new subscriptions automatically, so a new landing zone is governed on day one rather than when someone notices.

The interaction to remember: a DeployIfNotExists policy needs its own managed identity with enough permission to make the assignment it deploys. Policy remediation is itself an RBAC principal, and it needs roleAssignments/write — which means the policy assignment's identity is privileged and belongs in the same review category as the pipeline's.


AKS — two RBAC systems, wired together

AKS is the most confusing integration because Kubernetes has its own RBAC and the names collide. Three independent things, and it's worth naming which is which:

Layer Governs Configured by
Azure RBAC on the cluster resource Managing the cluster: scale it, upgrade it, read the kubeconfig Role assignment on the AKS resource. Azure Kubernetes Service Cluster User gets you a kubeconfig and nothing inside
Azure RBAC for Kubernetes authorisation Permissions inside the cluster, expressed as Azure role assignments azure_rbac_enabled = true; then roles like Azure Kubernetes Service RBAC Reader scoped to the cluster or a namespace
Kubernetes RBAC Permissions inside the cluster, expressed as Role/RoleBinding objects kubectl, and bypassed entirely by the admin kubeconfig
resource "azurerm_kubernetes_cluster" "this" {
  # …
  azure_active_directory_role_based_access_control {
    azure_rbac_enabled     = true
    admin_group_object_ids = [data.azuread_group.aks_admins.object_id]
  }
  local_account_disabled = true    # retires the admin kubeconfig bypass
  oidc_issuer_enabled    = true    # required for workload identity
  workload_identity_enabled = true
}

local_account_disabled = true is the AKS equivalent of allowSharedKeyAccess = false. Until it's set, az aks get-credentials --admin hands out a certificate that bypasses every Azure role assignment on the cluster.

Workload identity is the pod-level half: a Kubernetes service account is federated to an Entra managed identity via the cluster's OIDC issuer, so a pod gets a token with no secret mounted. It replaces the older pod-identity approach ⚠️ verify current guidance and any deprecation timelines against current Azure docs.

The most common AKS RBAC failure worth pre-empting: AcrPull on the wrong scope. The cluster's kubelet identity — not the cluster identity, and not the control-plane identity — needs AcrPull on the registry. Three identities, similar names, and the symptom is ImagePullBackOff, which nobody immediately reads as a role assignment problem.


Azure SQL Database — access with no password

-- Run as the Entra admin, against the target database
CREATE USER [id-payments-prod] FROM EXTERNAL PROVIDER;
ALTER ROLE db_datareader ADD MEMBER [id-payments-prod];

The important structural point: Azure RBAC does not grant access to data inside a SQL database. There is no dataActions role for table reads. RBAC governs the server and database resources; inside the database, permissions are SQL's own role model, granted to an Entra principal that RBAC never sees. Two authorisation systems in sequence rather than in parallel, which is different from the storage pattern and catches people who generalised from it.

Set azuread_authentication_only = true on the server to retire SQL logins, the same move as allowSharedKeyAccess and local_account_disabled.


Azure Monitor — the only record of who granted what

Role assignment writes appear in the activity log, which is retained briefly by default and must be routed somewhere to be useful. Diagnostic settings are not on by default; this one is worth having on day one.

az monitor diagnostic-settings subscription create \
  --name activity-to-law \
  --location uksouth \
  --workspace "$(az monitor log-analytics workspace show -g rg-platform-obs -n law-platform --query id -o tsv)" \
  --logs '[{"category":"Administrative","enabled":true},{"category":"Security","enabled":true},{"category":"Policy","enabled":true}]'

Then the query that answers the audit question:

AzureActivity
| where OperationNameValue in~ (
    "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE",
    "MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/DELETE",
    "MICROSOFT.AUTHORIZATION/ROLEDEFINITIONS/WRITE",
    "MICROSOFT.AUTHORIZATION/ELEVATEACCESS/ACTION")
| where ActivityStatusValue == "Success"
| project TimeGenerated, OperationNameValue, Caller, CallerIpAddress,
          scope = tostring(parse_json(Properties).entity)
| order by TimeGenerated desc

ELEVATEACCESS/ACTION in that list is the one to alert on unconditionally — it's a Global Administrator granting themselves User Access Administrator at the root scope, which is legitimate as a break-glass move and should never be silent. More in Production.


The two mistakes that span every integration

Granting the control-plane role and expecting data access. Storage Account Contributor for something that reads blobs. Key Vault Contributor for something that reads secrets. Azure Service Bus Contributor for something that sends messages. All three fail, all three look like a bug, and the fix is always a role with Data in the name.

Leaving the parallel path open. Every integration above has a legacy credential that bypasses RBAC entirely, and each has a switch:

az storage account update -g rg -n stapp --allow-shared-key-access false
az keyvault update -g rg -n kv-payments --enable-rbac-authorization true
az aks update -g rg -n aks-payments --disable-local-accounts
az sql server ad-only-auth enable -g rg -n sql-payments

Until those are set, your role assignments are documentation.


Next: Production →

← Back to the Azure RBAC overview · ← Previous: Deployment