Christian Lehnert — Linux, Hacking & Faith

Azure Managed Identities

Christian Lehnert • 2026-10-03 • ~6 min read

Azure Managed Identities

The most dangerous thing in most Azure setups is a connection string
or a client secret sitting in a config file, an environment variable,
or a GitHub repository. It is the thing that leaks, the thing nobody
rotates, the thing an attacker finds and uses months later. Managed
identities exist to delete that thing entirely, and in 2026 there is
very little reason to still be holding Azure secrets at all.

The core idea is simple and worth stating plainly: with a managed
identity, the identity is the credential. There is no secret to store,
because the thing proving who you are is not a password you hold, it is
a trust relationship Azure enforces. Your code asks Azure for a token,
Azure checks that the code is running as the identity it claims, and
hands over a short-lived token scoped to exactly what that identity is
allowed to do. Nothing to put in a file. Nothing to rotate. Nothing to
leak.

The Two Kinds

There are two flavours, and the difference comes down to lifecycle.

A system-assigned managed identity is tied to one Azure resource. Turn
it on for a VM, a function app, a container app, and that resource gets
an identity that lives and dies with it. Delete the resource and the
identity is gone too. This is the right choice when a single resource
needs to talk to other Azure services and nothing else needs that
identity. It is the simplest thing that works: flip it on, grant it a
role, done.

A user-assigned managed identity is a standalone object with its own
lifecycle. You create it once, and you can assign it to several
resources, and it survives any one of them being deleted. This is the
right choice when multiple resources should share one identity, or when
the identity needs to outlive the thing currently using it, or, as
below, when you need federation, because that only works on
user-assigned.

The decision rule that cuts through it: one resource, nothing fancy,
system-assigned. Shared identity or federation anywhere on the roadmap,
user-assigned.

Inside Azure: Grant a Role, Not a Secret

The common case is one Azure resource reaching another: a function app
that reads from Key Vault, a container app that writes to a storage
account. With a managed identity this stops involving secrets at all.

You turn on the identity, then grant it an RBAC role scoped as narrowly
as possible, Key Vault Secrets User on that one vault, Storage Blob Data
Reader on that one container, and nothing more. The application code
uses the Azure SDK's default credential, which automatically finds the
managed identity at runtime and gets a token. No connection string, no
key, no secret in the environment. The permission lives in a role
assignment you can see and audit, not in a string someone pasted into a
config file two years ago and forgot.

This is the same least-privilege, blast-radius thinking that should run
through everything: the identity gets exactly the roles its job needs,
so a compromise of that workload reaches exactly those resources and no
further.

Across the Boundary: GitHub Actions Without a Secret

Here is the part that surprises people, and the reason managed
identities matter even for things running outside Azure. A GitHub
Actions workflow is not an Azure resource, so for years the way it
authenticated to Azure was a stored service principal secret, dropped
into GitHub as a repository secret, long-lived, and a prime target. You
do not need that anymore. The mechanism that removes it is Workload
Identity Federation.

Federation works on trust between identity providers rather than a
shared secret. GitHub publishes its own OIDC issuer at
token.actions.githubusercontent.com, and every workflow run can request
a short-lived OIDC token from it that cryptographically describes what
is running: this repository, this branch, this environment. You
configure a user-assigned managed identity in Azure to trust that
issuer for one specific repository and environment, a federated
credential, and the chain is complete without a secret anywhere.

At run time it goes like this: the workflow asks GitHub for its OIDC
token, presents it to Azure, Azure checks the token came from the issuer
it trusts and matches the exact repository and environment the federated
credential names, and only then issues an Azure access token. The
workflow gets a short-lived token scoped to that identity's roles, uses
it, and it expires. There is no secret in GitHub to steal, nothing to
rotate, and the trust is pinned so tightly that a different repository,
or even a different branch than the one you allowed, simply cannot get a
token.

The same federation mechanism works for Kubernetes service accounts,
Azure DevOps, and any OIDC-compliant provider, which is why it is worth
learning once: it is the general answer to "something outside Azure
needs to authenticate to Azure," and the answer is always a federated
identity, never a stored secret. Note the one constraint from the two
kinds above: federation requires a user-assigned identity, because a
federated credential cannot be configured on a system-assigned one.

The Honest Caveats

Managed identities remove the secret, not the need to think.

The role assignment is now the thing that matters. You deleted the
secret, but an over-broad role on a managed identity is its own blast
radius. An identity granted Contributor on a whole subscription because
it was easier than scoping it is a worse problem than a leaked
narrow-scope secret. The discipline moves from rotating secrets to
scoping roles, and that discipline still has to happen.

Federation trust must be pinned tightly. A federated credential that
trusts your whole GitHub organization, rather than one repository and
environment, trusts every repository in it, including the one a
contractor pushes to. Scope the subject to the exact repository and
environment that should get the token, and audit which federated
credentials exist, because adding a new trusted issuer is adding a new
way in.

And it is Azure-shaped. Managed identities are an Entra and Azure
mechanism. Federation reaches outward to GitHub and Kubernetes, but the
identity side lives in Azure, so this is not a portable multi-cloud
secrets story, it is the right way to do it when Azure is the thing
being accessed.

The Point

A stored secret is a liability that leaks, never rotates, and outlives
the person who created it. Managed identities delete it: the identity
becomes the credential, your code gets short-lived tokens by proving
what it is rather than by holding a password, and inside Azure that is
one toggle and a scoped role. Workload Identity Federation extends the
same idea across the boundary, so GitHub Actions, Kubernetes, and
anything speaking OIDC can authenticate to Azure with no stored secret
at all, just a trust relationship pinned to the exact workload allowed
to use it.

Turn on the identity, grant the narrowest role, federate the external
workloads instead of giving them secrets, and pin every trust to the
specific thing that should have it. The best secret is the one that does
not exist, and for authenticating to Azure, in 2026, it does not have
to.

Tagged:
#azure #devops #security
← Back to posts