13.1 Microsoft Entra Service Principals & Managed Identities
Key Takeaways
- Microsoft Entra Service Principals represent the local instance of an application registration in a specific tenant, whereas Managed Identities are fully automated Azure-managed identities bound to Azure compute resources.
- System-Assigned Managed Identities share a 1:1 strict lifecycle with the parent Azure resource and are automatically destroyed when the resource is deleted, while User-Assigned Managed Identities exist independently and can be shared across multiple resources.
- Managed Identities completely eliminate credential storage, rotation overhead, and secret leakage risks for workloads and self-hosted build agents hosted inside Microsoft Azure.
- External environments such as GitHub-hosted runners, on-premises agents, and multi-cloud systems require Service Principals or Workload Identity Federation because they cannot access the Azure Instance Metadata Service (IMDS).
- Azure Role-Based Access Control (Azure RBAC) must enforce the principle of least privilege by scoping permissions at the Resource Group or Resource level rather than Subscription or Management Group levels.
13.1 Microsoft Entra Service Principals & Managed Identities
In modern Continuous Integration and Continuous Delivery (CI/CD) environments, automated pipelines, orchestration agents, and deployment scripts require authenticated access to cloud infrastructure. Historically, organizations relied on embedded administrative credentials or dedicated service accounts with static passwords, introducing severe security vulnerabilities including credential exfiltration, secret sprawl across source repositories, and operational outages caused by unmanaged password expirations.
Within the Microsoft Azure ecosystem, Microsoft Entra ID (formerly Azure Active Directory) provides the foundational identity and access management fabric. For DevOps engineers preparing for the AZ-400 exam, mastering the architectural distinctions, lifecycle characteristics, security boundaries, and role-based governance of non-human identities—specifically Service Principals and Managed Identities—is an essential core competency.
1. Identity Evolution: Application Objects vs. Service Principals
To understand non-human authentication in Microsoft Entra ID, engineers must distinguish between an Application Registration (Application Object) and a Service Principal.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Microsoft Entra ID Home Tenant │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Application Registration (App Object) │ │
│ │ • Global Blueprint / Schema │ │
│ │ • App ID (Client ID), Redirect URIs, Declared API Permissions │ │
│ └──────────────────────────────────┬──────────────────────────────────┘ │
│ │ Instantiates │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Service Principal (Enterprise Application) │ │
│ │ • Local identity object in the home tenant │ │
│ │ • Object ID, Local Azure RBAC Role Assignments │ │
│ │ • Authentication Credentials (Client Secret / Certificate) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
- Application Object: The abstract definition and global blueprint of the application. It resides in the home tenant where the application was registered and defines core properties such as the Application (Client) ID, supported account types, API permissions, and reply URLs.
- Service Principal: The concrete local instance of an Application Object created in a specific Microsoft Entra tenant. The Service Principal defines what the application can actually do in that specific directory, who can access it, and what Azure resources it can manage. When you assign an Azure Role-Based Access Control (Azure RBAC) role to an application, you assign that role to the Service Principal, not the Application Object.
Authentication Credentials for Service Principals
Service Principals traditionally authenticate using one of two credential types:
- Client Secrets (Symmetric Keys): A string password generated in Entra ID. While simple to implement in pipeline variables, client secrets present substantial risks: they can be accidentally committed to Git repositories, printed in pipeline debug logs, and must be proactively rotated before their expiration date (maximum lifespan of 24 months).
- X.509 Certificates (Asymmetric Keys): A public/private key pair where the public key is uploaded to Entra ID and the private key is held securely by the client runner. Certificates provide superior cryptographic strength over client secrets but introduce Public Key Infrastructure (PKI) operational overhead, including certificate distribution and renewal management.
2. Managed Identities: Secretless Azure Authentication
To solve the fundamental security problem of managing, storing, and rotating credentials, Microsoft introduced Managed Identities for Azure Resources. A Managed Identity is an automatically managed identity in Microsoft Entra ID provisioned directly for an Azure compute resource (such as a Virtual Machine, Virtual Machine Scale Set, Azure App Service, or Azure Container App).
How Managed Identities Work Under the Hood
Managed Identities leverage the internal Azure Instance Metadata Service (IMDS), an unroutable REST endpoint accessible only from within the running Azure compute resource at IP address http://169.254.169.254/metadata/identity/oauth2/token.
┌─────────────────────────────────────────────────────────────────────────┐
│ Azure Virtual Machine / Self-Hosted Agent Host │
│ │
│ ┌───────────────────────┐ │
│ │ Pipeline Task / Script│ │
│ └───────────┬───────────┘ │
│ │ 1. GET /metadata/identity/oauth2/token?resource=... │
│ ▼ (Metadata: true) │
│ ┌────────────────────────────────────────┐ │
│ │ Azure Instance Metadata Service (IMDS) │ │
│ │ IP: 169.254.169.254 │ │
│ └───────────────────┬────────────────────┘ │
└──────────────────────┼──────────────────────────────────────────────────┘
│ 2. Acquires Token using Internal Fabric Credential
▼
┌─────────────────────────────┐
│ Microsoft Entra ID │
│ (Issues Short-Lived JWT) │
└──────────────┬──────────────┘
│ 3. Returns OAuth 2.0 Access Token (Valid ~24 hrs)
▼
┌─────────────────────────────┐
│ Azure Resource Manager (ARM)│
│ (Authorizes Deployments) │
└─────────────────────────────┘
- The deployment script or Azure CLI on the VM requests an access token from the local IMDS endpoint, specifying the target resource URI (
https://management.azure.com/). - The Azure underlying fabric presents an internally managed cryptographic credential to Microsoft Entra ID on behalf of the compute resource.
- Microsoft Entra ID returns a short-lived OAuth 2.0 JSON Web Token (JWT) to the IMDS endpoint, which forwards it to the requesting script.
- The pipeline step uses this temporary bearer token to execute deployment operations against Azure Resource Manager (ARM).
Zero Secret Footprint: The developer never creates, views, manages, or stores passwords, secrets, or certificates. Microsoft Entra ID handles creation, automated internal key rotation, and eventual revocation.
3. System-Assigned vs. User-Assigned Managed Identities
Microsoft provides two distinct operational models for Managed Identities. Choosing the correct model is a frequent topic on the AZ-400 exam.
SYSTEM-ASSIGNED MANAGED IDENTITY USER-ASSIGNED MANAGED IDENTITY
┌──────────────────────────┐ ┌──────────────────────────┐
│ Azure Resource (VM/App) │ │ Standalone Identity Res │
│ ┌────────────────────┐ │ │ (Independent Lifecycle) │
│ │ Embedded Identity │ │ └────────────┬─────────────┘
│ └────────────────────┘ │ │ Shared Across
└─────────────┬────────────┘ ┌────────────┴─────────────┐
│ ▼ ▼
▼ ┌────────────────────────┐ ┌────────────────────────┐
[Resource Deletion] │ Azure Compute Node 01 │ │ Azure Compute Node 02 │
│ │ (VMSS Instance) │ │ (Container App) │
▼ └────────────────────────┘ └────────────────────────┘
Identity is AUTOMATICALLY Identity PERSISTS after compute
deleted from Entra ID instances are deprovisioned
System-Assigned Managed Identity
- 1:1 Lifecycle Binding: Enabled directly on an individual Azure resource instance. Its lifecycle is strictly tied to that specific resource.
- Automated Deletion: When the host Azure resource (e.g., a virtual machine) is deleted, Azure automatically deletes the corresponding Service Principal in Microsoft Entra ID and removes all associated RBAC role assignments.
- No Multi-Resource Sharing: A system-assigned identity cannot be shared with any other Azure resource.
- Ideal Use Case: Single-purpose workloads, such as an isolated build agent VM that needs dedicated access to an Azure Key Vault, or an App Service that connects to an Azure SQL Database.
User-Assigned Managed Identity
- Independent Lifecycle: Created as an independent, standalone Azure resource under the
Microsoft.ManagedIdentity/userAssignedIdentitiesresource provider. - 1:Many Sharing: Can be assigned to multiple Azure resources simultaneously. For example, a fleet of 20 self-hosted build agent virtual machines in a scale set can all share a single user-assigned identity.
- Persistent Existence: Deprovisioning or deleting the host virtual machines has zero impact on the identity. The identity persists in Entra ID along with all its configured RBAC permissions.
- Multi-Identity Binding: A single Azure resource can be assigned multiple user-assigned managed identities (as well as one system-assigned identity), enabling granular segregation of duties (e.g., Identity A for storage container access, Identity B for container registry pulls).
- Ideal Use Case: Elastic compute pools, Azure Virtual Machine Scale Sets (VMSS) running build agents, Kubernetes clusters, or standardized infrastructure deployments across development, staging, and production environments where permissions must be pre-allocated before VM provisioning.
4. Workload Placement & Decision Criteria: Managed Identity vs. Service Principal
The AZ-400 exam requires candidates to determine the optimal identity mechanism based on architectural hosting constraints and security policies.
Comprehensive Identity Selection Matrix
| Architectural Requirement / Constraint | System-Assigned Managed Identity | User-Assigned Managed Identity | Service Principal (App Registration) |
|---|---|---|---|
| Hosting Location | Azure compute resources only | Azure compute resources only | Any environment (Azure, on-premises, multi-cloud) |
| Credential Management | Zero (fully managed by Azure) | Zero (fully managed by Azure) | Manual (must manage secrets, certs, or OIDC) |
| Secret Rotation Risk | None (automated internal rotation) | None (automated internal rotation) | High (outage if secret expires unmanaged) |
| Lifecycle Bound to Resource | Yes (1:1 strict binding) | No (independent standalone lifecycle) | No (independent directory object) |
| Multi-Resource Sharing | Unsupported | Supported (1 identity to many resources) | Supported (shared across applications) |
| Pre-allocation of Permissions | Impossible (resource must exist first) | Supported (assign RBAC prior to VM creation) | Supported (assign RBAC anytime) |
| On-Premises / Multi-Cloud Pipelines | Unsupported (requires Azure Arc) | Unsupported (requires Azure Arc) | Fully supported natively |
| GitHub-Hosted Runners | Unsupported | Unsupported | Required (via OIDC or Client Secret) |
Key Architectural Scenarios
- Self-Hosted Agents on Azure VMs or VMSS: Use Managed Identities.
- If the build agent pool runs on an Azure Virtual Machine Scale Set (VMSS), configure a User-Assigned Managed Identity on the scale set model.
- Benefit: As the scale set scales out new agent instances during peak pipeline runs, every new VM automatically inherits the identity and its RBAC permissions without passing secrets through
cloud-initscripts.
- Azure Kubernetes Service (AKS) Pods: Use Azure Workload Identity.
- Legacy pod-identity solutions (
aad-pod-identity) intercepted node-level IMDS traffic, causing scaling bottlenecks. Modern AKS uses Azure Workload Identity, which federates a Kubernetes Service Account with a User-Assigned Managed Identity via OpenID Connect (OIDC).
- Legacy pod-identity solutions (
- GitHub-Hosted Runners & External CI/CD: Use Service Principals with Workload Identity Federation.
- Because GitHub-hosted runners (
ubuntu-latest) execute inside GitHub's multi-tenant infrastructure outside your Azure Virtual Network, they cannot query the Azure IMDS endpoint at169.254.169.254. - A Microsoft Entra Service Principal is mandatory. However, rather than generating static client secrets, use OIDC Workload Identity Federation (explored in Section 13.2).
- Because GitHub-hosted runners (
- On-Premises Build Servers: Use Azure Arc or Service Principals.
- Physical servers or on-premises VMware machines running Azure DevOps self-hosted agents can either authenticate via an Entra Service Principal with X.509 certificates or onboard to Azure Arc-enabled servers, which extends Azure Managed Identity capabilities directly to on-premises operating systems.
5. Role-Based Access Control (Azure RBAC) & Least-Privilege Scoping
Securing pipeline identities requires enforcing strict Least Privilege. An identity should only possess the minimum permissions necessary to execute its intended infrastructure deployment tasks.
The Azure RBAC Scope Hierarchy
Permissions in Azure RBAC are assigned at four distinct hierarchical levels. Role assignments inherit downward to all child containers:
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. Management Group Level (/providers/Microsoft.Management/...) │
│ Applies to all Subscriptions within the Management Group hierarchy │
└──────────────────────────────────────┬──────────────────────────────────────┘
│ Inherits down
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. Subscription Level (/subscriptions/{subscription-id}) │
│ Applies to all Resource Groups within the Subscription │
└──────────────────────────────────────┬──────────────────────────────────────┘
│ Inherits down
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. Resource Group Level (/subscriptions/.../resourceGroups/{rg-name}) │
│ Applies to all Resources contained inside the Resource Group │
└──────────────────────────────────────┬──────────────────────────────────────┘
│ Inherits down
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. Resource Level (/subscriptions/.../providers/{type}/{name}) │
│ Applies exclusively to a single target resource (e.g., Key Vault, ACR) │
└─────────────────────────────────────────────────────────────────────────────┘
Scoping Best Practices for DevOps Engineers
- Avoid Subscription-Wide Contributor: A frequent anti-pattern is assigning the built-in
Contributorrole at the Subscription scope to a pipeline Service Principal. If that pipeline is compromised, the blast radius encompasses the entire cloud subscription. - Scope to Dedicated Resource Groups: Assign permissions strictly at the Resource Group scope where the application's resources reside (e.g.,
rg-payments-prod). - Separate Control Plane vs. Data Plane: In Azure, managing a resource (control plane, e.g., restarting a Key Vault) is distinct from reading its payload (data plane, e.g., reading a secret). Roles like
Contributoronly grant control plane access. To read secrets, assign data plane roles such asKey Vault Secrets Userdirectly on the specific Key Vault instance.
Built-in vs. Custom RBAC Roles
When built-in roles (Owner, Contributor, Reader, User Access Administrator) provide excessive capabilities, DevOps engineers must author Custom RBAC Roles defining granular actions.
{
"Name": "DevOps WebApp Deployer",
"Id": "11111111-2222-3333-4444-555555555555",
"IsDataActions": false,
"Description": "Allows deployment to Azure Web Apps and restarts without administrative rights.",
"Actions": [
"Microsoft.Web/sites/read",
"Microsoft.Web/sites/publishxml/action",
"Microsoft.Web/sites/restart/action",
"Microsoft.Web/sites/slots/read",
"Microsoft.Web/sites/slots/restart/action"
],
"NotActions": [
"Microsoft.Web/sites/delete",
"Microsoft.Authorization/*/Write"
],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": [
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-ecommerce-prod"
]
}
- Actions: Control plane operations the role can execute.
- NotActions: Explicit subtractions from the
Actionslist (used to restrict specific high-risk operations like deletion). - AssignableScopes: Restricts where the custom role can be assigned, preventing unauthorized reuse across other subscriptions or management groups.
6. CLI Implementations: Identity Management & Role Assignments
The AZ-400 exam frequently presents Azure CLI command-line snippets. Candidates must recognize the exact commands used to provision and govern identities.
Provisioning a Service Principal with Scoped Role Assignment
# Create a Service Principal with Contributor role restricted to a single Resource Group
az ad sp create-for-rbac \
--name "sp-az400-cicd-pipeline" \
--role "Contributor" \
--scopes "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/rg-frontend-prod" \
--years 1
Enabling a System-Assigned Managed Identity on an Azure VM
# Enable system-assigned identity on an existing build agent VM
az vm identity assign \
--resource-group "rg-devops-infra" \
--name "vm-build-agent-01"
# Assign the Reader role to the VM's identity on a target resource group
VM_PRINCIPAL_ID=$(az vm identity show \
--resource-group "rg-devops-infra" \
--name "vm-build-agent-01" \
--query "principalId" -o tsv)
az role assignment create \
--assignee "$VM_PRINCIPAL_ID" \
--role "Reader" \
--scope "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/rg-frontend-prod"
Creating and Assigning a User-Assigned Managed Identity to a VMSS
# 1. Create the standalone User-Assigned Managed Identity
az identity create \
--name "id-vmss-buildagents" \
--resource-group "rg-devops-infra"
# 2. Retrieve identity resource ID and principal ID
IDENTITY_RES_ID=$(az identity show \
--name "id-vmss-buildagents" \
--resource-group "rg-devops-infra" \
--query "id" -o tsv)
IDENTITY_PRINCIPAL_ID=$(az identity show \
--name "id-vmss-buildagents" \
--resource-group "rg-devops-infra" \
--query "principalId" -o tsv)
# 3. Assign role to the identity BEFORE assigning to compute
az role assignment create \
--assignee "$IDENTITY_PRINCIPAL_ID" \
--role "Contributor" \
--scope "/subscriptions/12345678-1234-1234-1234-123456789012/resourceGroups/rg-frontend-prod"
# 4. Attach the identity to an Azure Virtual Machine Scale Set (VMSS)
az vmss identity assign \
--resource-group "rg-devops-infra" \
--name "vmss-build-pool" \
--identities "$IDENTITY_RES_ID"
7. Realistic Exam Scenario & Common Traps
Scenario: Securing an Elastic Build Agent Fleet
Company Profile: Contoso Insurance operates a critical policy-underwriting engine. The DevOps team maintains an elastic pool of 40 self-hosted build agents running on an Azure Virtual Machine Scale Set (VMSS) deployed within an Azure Virtual Network. Agents compile C# microservices, package Docker containers, and deploy infrastructure as code (Bicep templates) across multiple resource groups.
Problem: The team previously stored an administrative Service Principal password in pipeline variable groups. During an internal security audit, the InfoSec team discovered that the client secret was six days away from expiration and had been checked into an unencrypted scratch repository by a junior engineer.
DevOps Solution:
- Provision a single User-Assigned Managed Identity named
id-contoso-agents. - Assign the identity least-privilege Azure RBAC roles:
Contributorscoped strictly to the application resource groups (rg-policy-dev,rg-policy-prod), andAcrPushon the central Azure Container Registry. - Attach the user-assigned identity to the VMSS model. As VMSS auto-scales instances up and down, each instance automatically accesses ARM without storing any credentials in pipeline files or Azure DevOps variable groups.
- Decommission the old Service Principal, permanently eliminating secret expiration outages and credential leakage risks.
Common Exam Traps to Avoid
- Trap: Selecting System-Assigned Identity for VMSS agent pools requiring pre-allocated permissions. System-assigned identities generate unique principal IDs per VM instance, meaning you cannot assign permissions until each individual VM instance is provisioned. For VMSS and scale-out architectures, always choose a User-Assigned Managed Identity so permissions can be assigned once to the identity itself.
- Trap: Confusing Azure RBAC roles with Microsoft Entra directory roles. Assigning an identity the
Application AdministratororGlobal Administratorrole in Microsoft Entra ID does not grant rights to deploy Azure resources (such as VMs or App Services). Deploying Azure infrastructure requires Azure RBAC roles (Contributor,Owner, or custom roles) scoped within Azure subscriptions. - Trap: Believing Managed Identities can be used natively by GitHub-hosted runners. GitHub-hosted runners execute on external infrastructure outside Azure and cannot contact the Azure IMDS endpoint (
169.254.169.254). They must use a Service Principal or OIDC Workload Identity Federation.
An organization hosts its self-hosted Azure Pipelines build agents on an Azure Virtual Machine Scale Set (VMSS) within an internal Azure Virtual Network. The pipelines must deploy Bicep templates to an Azure resource group without storing any client secrets, certificates, or passwords in Azure DevOps. The solution must ensure that all newly scaled VM instances immediately possess deployment permissions without requiring per-instance role assignments. Which identity solution should the DevOps engineer implement?
A continuous delivery workflow runs on GitHub-hosted runners (ubuntu-latest) and deploys microservices into Microsoft Azure. Company policy strictly mandates that automated deployments must not rely on passwords or client secrets that require manual rotation. Why can the deployment pipeline NOT use an Azure System-Assigned Managed Identity for authentication?
A security auditor discovers that an Azure DevOps pipeline Service Principal has been granted the built-in 'Owner' role across an entire production Azure subscription. The pipeline only needs to deploy containerized code to an existing Azure App Service and restart the web application in resource group 'rg-prod-web'. To adhere strictly to the principle of least privilege, what changes should the DevOps engineer make?