Best Practices

Azure Managed Identities and the Supply Chain

Managed identities are the credential primitive that fixes most supply chain risk in Azure — but only if you use them the way the service actually intends.

Shadab Khan
Security Engineer
8 min read

If I could make one change to every Azure environment I review, it would be the same change: replace every long-lived service principal secret with a managed identity. Not because managed identities are perfect — they have sharp edges, which this post will cover — but because the modal credential-related incident I see in Azure is a service principal secret that leaked, got committed to a repo, or sat in an app setting long past its rotation date, and managed identities eliminate the class entirely.

The supply chain relevance is direct. Every credential that persists is a credential that can be exfiltrated through a compromised dependency, a leaked backup, or a misconfigured log pipeline. Managed identities do not persist in the same way — they are short-lived tokens issued at request time, scoped to the resource that requested them, with no material for an attacker to steal and replay later.

This post is the playbook I use for managed identity adoption: what they are, when to use which flavor, and the patterns that actually hold up under scale.

The Two Flavors and When To Use Each

Azure has two kinds of managed identity: system-assigned and user-assigned. The distinction is owned-by-the-resource vs. owned-independently-and-shared.

A system-assigned identity is tied one-to-one to a specific Azure resource — an App Service, a VM, a Container App. It is created when you enable it on the resource and is deleted when the resource is deleted. Its lifecycle is the resource's lifecycle. Use system-assigned identities when the resource is the only thing that should authenticate as that identity.

A user-assigned identity is a standalone Azure resource (Microsoft.ManagedIdentity/userAssignedIdentities) that can be attached to one or more resources. Its lifecycle is independent. Use user-assigned identities when multiple resources share the same authentication context — a scale set of VMs, a blue/green pair of App Services, a set of Container Apps behind one service.

The rule I use in reviews: prefer system-assigned unless there is a specific reason to share. "Convenience" is not a reason to share. Sharing an identity across unrelated resources means the blast radius of a compromise is every resource that holds the identity, and traceability of who did what (an identity granted access to a storage account: which resource actually made the call?) gets harder.

The Token Request Flow

It helps to understand what actually happens when a resource requests a token, because the security properties fall out of the mechanism.

Inside an Azure compute resource, there is an Instance Metadata Service (IMDS) endpoint at 169.254.169.254. When the resource calls IMDS with a managed identity request, the platform issues a token signed by Azure AD, scoped to the requested audience (the resource URI — https://vault.azure.net, https://database.windows.net, and so on). The token is valid for a few hours. The resource uses it as a bearer token against the target service.

The key security properties:

  • The token is only accessible from inside the resource. There is no way to request a token for a different resource from the outside.
  • The token is scoped to a specific audience. A token for Key Vault cannot be used against Storage.
  • The token has a short lifetime. Exfiltration of a token is useful for hours, not weeks.
  • There is no persistent secret. The identity's proof of existence is its presence on the resource; Azure AD verifies that presence when issuing the token.

This is what "credentials that do not persist" means in practice. There is no file to leak, no app setting to expose, no environment variable to read. A compromised dependency running in the resource can request a token and use it, but cannot export a reusable credential.

The exfiltration path that does exist is "request a token inside the resource, send the token to an attacker-controlled endpoint, attacker uses the token for the token's lifetime." That is a real attack and needs runtime detection (which is what Microsoft Defender for Cloud's MDR is for), but it is bounded in a way that a stolen client secret is not.

Federated Identity for the Non-Azure Side

Managed identities work inside Azure compute. Outside Azure — GitHub Actions, an on-premises CI server, a build running in AWS — you cannot have a managed identity, because there is no Azure-managed resource to attach one to.

The answer, since the GA of workload identity federation in 2022, is federated credentials on a user-assigned managed identity (or an app registration). A federated credential trusts a specific OIDC issuer and subject — for GitHub Actions, the issuer is https://token.actions.githubusercontent.com and the subject is the repository path plus ref. When a GitHub Action authenticates, it exchanges its OIDC token for an Azure AD token without any long-lived secret.

This is the pattern I recommend for every new CI pipeline that deploys to Azure. The federation is declarative, the trust is scoped to a specific workflow and branch, and rotation is unnecessary because there is no material to rotate. Existing pipelines using client secrets should be migrated on the same schedule as other credential rotations.

Scope: The Part Most Teams Get Wrong

Having a managed identity does not, by itself, reduce risk. An identity granted Contributor on the subscription has the same blast radius as a service principal with Contributor on the subscription. The value of managed identity is realized only when the identity is scoped to what it actually needs.

The scope I see most commonly in reviews is "Contributor at the subscription level," which is almost never what the application actually needs. A web app that reads secrets from one Key Vault and writes blobs to one storage container needs Key Vault Secrets User on that one vault and Storage Blob Data Contributor on that one container. Twelve role assignments per workload is the norm for a properly scoped identity; one role assignment per workload is the anti-pattern I keep finding.

The least-privilege pass is tedious. The shortcut is to deploy in audit mode — grant the broad permission, log every action the identity takes, derive the actual permission set from the log, and re-scope. Azure Monitor activity logs at the resource provider level give you enough to do this in a day or two per workload. The other shortcut is Privileged Identity Management (PIM) for Azure resources, which lets the identity have broad eligibility but only activate it when needed, with an audit trail.

Rotation Is Not the Right Frame

For client secrets, rotation is a first-class concern — a 90-day rotation interval is the baseline, a 30-day rotation is better, and anything longer is a finding. For managed identities, rotation is not the right question, because there is no secret to rotate. The right questions are:

  • When was this identity last used? (Azure AD sign-in logs for managed identities have been available since 2022; query AADManagedIdentitySignInLogs in Log Analytics.)
  • What resources have role assignments to this identity? (Azure Resource Graph query; the assignments should match the current documented use.)
  • What resources have this identity attached? (For user-assigned only.)
  • Is the identity still needed? (An identity with no recent sign-ins is either over-privileged for its actual use or orphaned.)

Orphaned identities — user-assigned identities that no resource uses but that still have role assignments — are the supply chain debt that accumulates silently. I regularly find tenants with hundreds of these, leftover from deployments that were torn down without cleanup. Each one is a potential reanimation point: a compromised Contributor principal can attach an orphaned identity to a new resource and inherit its permissions. Cleanup is boring and important.

Anti-Patterns I Keep Seeing

A few patterns that look like managed identity adoption but are not:

  • A managed identity plus a client secret. The app has both, and the client secret is the primary credential. This defeats the point. Remove the secret.
  • An identity passed through an environment variable. The app extracts the identity's object ID and uses it — somehow — in code that then authenticates with a different credential. This is not managed identity; this is theater.
  • Shared user-assigned identities across unrelated services, granted broad permissions, because the sharing was convenient. The blast radius returns.
  • Managed identity on the resource but the code uses a connection string in app settings. The identity is unused. This is the most common finding in my reviews.

How Safeguard Helps

Safeguard enumerates managed identities across the tenant, maps attached resources against role assignments against recent activity, and surfaces the orphaned, over-scoped, and mixed-credential cases in one place. For CI pipelines, it tracks which workflows still authenticate with client secrets versus federated credentials and prioritizes the migration by the role assignment blast radius. The platform turns the conversation from "do we use managed identity" into "for which workloads is managed identity actually the only credential," which is the question that moves the risk needle.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.