Principle of Least Privilege in Azure: A Practical Guide
The principle of least privilege is the security practice of granting the minimum access required to do a job — and nothing more. In Azure, applying this principle means being deliberate about every role assignment: choosing the most specific role, scoping it as narrowly as possible, and removing access that is no longer needed. It sounds simple, but most real-world Azure environments violate it in ways that only become visible after an incident.
Why least privilege matters in cloud environments
In on-premises environments, excessive permissions were often a theoretical risk — an attacker would need physical or network access before permissions became relevant. In cloud environments, identity is the security perimeter. If a credential is stolen or an identity is compromised, the permissions attached to that identity determine exactly how much damage can be done.
Cloud access is also unique in that it is easy to grant broad permissions quickly and easy to forget those permissions were ever granted. Azure CLI commands and Terraform modules make granting Owner on a subscription a two-second operation. Auditing and correcting that assignment months later often takes much longer.
The other factor is automation. CI/CD pipelines, infrastructure provisioning tools, and background services all need identities and permissions. These service identities often run continuously with standing access, making them high-value targets. A developer’s compromised laptop is contained by their individual permissions. A compromised service principal with Owner on a subscription is a catastrophe.
Three real-world scenarios of over-permissioned access
The following scenarios are representative of what happens in real organizations. The details have been generalized, but these patterns appear repeatedly in cloud security assessments.
Scenario 1: The developer who deleted the production database
A backend developer was given Contributor on the production subscription when they joined the team. The reason was pragmatic: they needed to deploy code, view logs, and occasionally restart services. Contributor at subscription scope seemed like the simplest way to cover all of that.
Months later, during an incident late at night, the developer ran a cleanup script they had tested in staging. They forgot to switch the Azure CLI context from production to staging. The script deleted a resource group that contained the production SQL database.
The Contributor role at subscription scope gave them permission to delete resource groups anywhere in the subscription. There was no protection.
What should have been assigned instead:
- Contributor on the specific resource groups they actually deployed to (dev and staging)
- Website Contributor on the production App Service resource group (to restart services and deploy code to App Service without being able to delete the underlying resource group)
- Reader on the production SQL resource group (to view configuration and logs without write access)
With these narrower assignments, the accidental deletion would have failed with a permissions error, and the error itself would have made the developer realize they were operating in the wrong context.
Scenario 2: The CI pipeline with Owner and a stolen token
A DevOps team set up a GitHub Actions pipeline to deploy infrastructure. They created a service principal and assigned it Owner on the subscription. The reasoning was that Terraform sometimes needs to create role assignments (when setting up managed identity permissions), and Contributor cannot do that — only Owner can.
The pipeline was working fine for months. Then a public repository had its Actions secrets accidentally exposed through a verbose debug log that was committed to the repo. An automated scraper found the client secret within minutes.
With Owner on the subscription, the attacker could create new user accounts, assign themselves Owner, exfiltrate data from storage, create expensive compute resources, and make the environment essentially unrecoverable without full subscription reset.
What should have been assigned instead:
- A separate service principal for infrastructure deployment with Contributor on the subscription (for creating most resources)
- A separate service principal — or a federated identity — specifically for the role assignment step, with User Access Administrator scoped only to specific resource groups where managed identity assignments are needed
- Better: use managed identities for the applications being deployed, so the pipeline never needs to manage service principal credentials at all
- Best: switch from client secrets to federated OIDC credentials for the pipeline so there is no long-lived secret to steal
Assigning Owner to any service principal that authenticates with a client secret is extremely high risk. A stolen Owner client secret gives complete subscription control with no time limit. Use the narrowest role that lets the pipeline do its job, and use federated credentials to eliminate the secret entirely.
Scenario 3: The monitoring service that could also write
An internal monitoring agent was deployed to read VM metrics, query application logs from a storage account, and send alerts. The team assigned it Storage Blob Data Contributor because they wanted to also let it write diagnostic outputs to a container.
The monitoring agent had a bug introduced in an update. Instead of reading log files, it began overwriting them with empty files, destroying months of audit logs before the bug was caught.
What should have been assigned instead:
- Storage Blob Data Reader for reading logs from the primary storage account
- Storage Blob Data Contributor scoped only to the specific container used for diagnostic outputs — not the entire storage account
The right scope for the write permission was not the entire storage account but a single container. Scoping the write role to just that container would have confined the bug’s impact to diagnostic outputs only, preserving the audit logs.
Practical steps for applying least privilege
Least privilege is not a one-time configuration — it is an ongoing practice. Here are the concrete steps to apply it.
Step 1: Start with the narrowest scope possible
Before assigning a role at subscription scope, ask: does this identity actually need access to everything in the subscription? Most of the time the answer is no. Scope to the specific resource group, or even the specific resource, that the identity needs to interact with.
Use the RBAC scope hierarchy as a guide: management group → subscription → resource group → resource. Go as far down the hierarchy as the use case allows.
Step 2: Choose the most specific role
If someone needs to manage virtual machines, assign Virtual Machine Contributor rather than Contributor. If they only need to start and stop VMs, consider a custom role with just those actions. The built-in vs custom roles guide covers how to find and evaluate the right built-in roles.
Step 3: Separate read from write access
Many tools need read access and only occasionally need write access. Assign read roles as standing access and reserve write roles for specific operations, using time-bound access when possible.
Step 4: Audit existing assignments regularly
List all role assignments in a subscription to find assignments that are broader than necessary:
az role assignment list \
--subscription YOUR_SUBSCRIPTION_ID \
--include-inherited \
--output tableLook for:
- Owner assignments at subscription scope (should be rare)
- Contributor assignments at subscription scope for individual users
- Assignments to accounts that have left the organization
- Service principal assignments that have broader scope than the service needs
Step 5: Use time-bound access for sensitive operations
Azure Privileged Identity Management (PIM) lets you configure roles so that principals only hold them during an active session they explicitly request. A developer who occasionally needs to run a production deployment does not need standing Contributor access on the production subscription — they can request it, get it for one hour, and have it automatically revoked.
PIM also enables approval workflows, requiring a manager or security team to approve a request for elevated access before it is granted. This creates an audit trail and a human checkpoint for sensitive operations.
The most common least-privilege violations
Beyond the three scenarios above, these patterns appear repeatedly in Azure environments that have not had a security review:
Assigning roles “temporarily” and never removing them
Someone needs elevated access for a one-time task. You assign Contributor on the subscription “just for today.” Three months later, the assignment still exists. Use PIM for temporary access, or set a calendar reminder to remove the assignment after the task is complete.
Using Owner for the service principal that manages Terraform state
The Terraform state storage account exists so that Terraform can track what it has deployed. The service principal managing this state rarely needs Owner — it typically needs Contributor on the subscription for resource creation and possibly User Access Administrator narrowly scoped for managed identity assignments. Owner is almost never required.
Granting access to the entire management group when subscription scope is sufficient
Management group roles inherit down to every subscription, resource group, and resource in the group. This is the broadest possible scope. Assignments at this level should be reserved for organization-wide policies and governance roles, not for individual team access.
Not separating the deployment identity from the read identity
Dashboards, monitoring tools, and reporting scripts only need read access. They should use a different identity than the deployment pipeline. A read-only identity that is compromised is far less dangerous than a Contributor identity that is compromised.
Mistakes to avoid
- Granting broad access to unblock yourself quickly. Assigning Owner or subscription-level Contributor to quickly get past a permission error creates technical debt that compounds over time. Take the extra five minutes to identify the correct narrow role and scope.
- Treating access reviews as a one-time task. New assignments are created constantly as teams grow and change. Without quarterly or monthly reviews, over-permissioned assignments accumulate silently.
- Conflating “needs to see” with “needs to change.” Auditors, managers, and monitoring tools usually only need read access. Granting them Contributor because it is easier is a common pattern that violates least privilege.
- Not having a process for offboarding. When someone leaves the team, their direct role assignments persist. Group-based access management makes this easier — removing them from the group removes all their access at once.
Summary
- Least privilege means the minimum permissions required to do a job — not “probably enough” permissions.
- In Azure, this means choosing the most specific role (not Contributor when Virtual Machine Contributor will do) and scoping it as narrowly as possible (resource group, not subscription).
- Over-permissioned developers have accidentally deleted databases; over-permissioned pipelines with stolen credentials have led to full subscription compromises.
- Regularly audit role assignments with
az role assignment listand remove any that are broader than necessary. - Use Azure PIM for time-bound access to sensitive roles instead of standing access.
- Assign roles to groups rather than individuals to simplify access management and offboarding.
Frequently asked questions
What is the principle of least privilege in Azure?
The principle of least privilege means granting only the exact permissions a security principal needs to do its job — nothing more. In Azure, this means choosing the most specific role available, scoping it as narrowly as possible, and removing access when it is no longer needed.
How do I check what permissions a user actually has in Azure?
Use az role assignment list --assignee USER_OBJECT_ID to see all explicit role assignments. To see effective permissions at a scope, use az role assignment list --assignee USER_OBJECT_ID --scope SCOPE_ID --include-inherited. The Azure portal also has a "Check access" button on any resource, resource group, or subscription that shows a principal's effective access.
What is a just-in-time access model in Azure?
Just-in-time (JIT) access means principals do not hold standing permissions — they request elevated access for a specific task and the access is automatically revoked after a time period. Azure Privileged Identity Management (PIM) implements JIT for Azure RBAC roles, requiring justification and optionally requiring approval before a user can activate an elevated role.