For my work I went from AWS to mostly Azure. Most concepts adapt easily. IAM did not. Not until I stopped fighting it and found the right mental model.
AWS IAM and Azure IAM are not the same thing even though the name is equal. They think in opposite directions, and I was running my AWS reflexes on a system built the other way around.
Lets call it a mirror
This mirror changes what you can automate, how least privilege works, and even how the companies around each platform are organized.
The inverted model
Lets start with AWS. Here you think from the identity.
A Lambda gets a role. That role carries a set of policies, and the policies spell out what the function may do: read this bucket, write to that queue, invoke that other function.
You want to know what something is allowed to do? You open the role and you read it.
The question is always what can this identity do?
Azure turns this around. You don’t start at the identity, you start at the resource. Open a storage account, go to Access control (IAM), and assign a role to some principal. That assignment lives on the storage account.
So when you want to know who can touch something, you go to the thing itself. The question becomes who can do what on this resource?
Same result of course, access is controlled either way. But the reasoning runs the other direction.
This same reasoning tells you who each model was made for. The Azure way is great for an auditor. You walk over your resources and per resource you see who can do what.
And that is a real win, not a consolation prize. Ask AWS who can reach this bucket and you are scanning every policy in the account. AWS even built Access Analyzer because nobody could answer that question by hand. Azure answers it on the resource itself, one blade, done.
The AWS way is made for the engineer who writes a policy and attaches it to a role. Keep that in mind, it comes back below.
What this means for automation
Well ehh… nothing! At least if you have Owner rights on the subscription. Hand your pipeline broad power and everything deploys just fine, on both clouds. The mirror only starts to matter when you want to do this properly.
AWS is built for doing it properly. A grant is an edit on your own role, so a deploy never reaches outside its own stack. And sure, AWS is not pure here, bucket policies and KMS key policies are everyday AWS. But the grant you do fifty times a day is an edit on a role you own.
Then flip the mirror. On Azure a role assignment is also just a resource, Bicep or Terraform declares it and your IAM is automated the same way. Until you check what rights that declaration needs. Here the mirror shows its teeth: writing an assignment is a privileged operation on the target, Owner or User Access Administrator on whatever holds that resource. So to let your pipeline hand out fine grained access, you first hand it broad power over everything it touches.
to hand out small access you first need big access
Take the smallest case. A function app and a storage account, both in the same stack you own. To let the function read the storage you write a role assignment, and writing it needs Owner on that storage account. Owning both resources buys you nothing here. The grant is a privileged act on the target either way. The picture says it better than the prose:
In a permissive subscription you just give the pipeline that right and move on. In a governed enterprise you don’t. So gluing your own two resources together becomes a ticket to the central identity team. That is culture as much as tech, a reflex carried straight out of the on-prem datacenter, and Azure fits it like a glove.
Least privilege is where it bites
All of the above is mostly annoying. This next part actually matters for your security.
Take a simple CRUD API, serverless. Four operations, create read update delete. On AWS I make four Lambdas and every one gets its own execution role. The read function may only read the table. The delete function may delete. Nothing more. So when someone breaks into the read endpoint, they sit on a credential that can read one table and that is it. IAM blocks the rest at the door.
On Azure the normal unit is a Function App, and a Function App has one managed identity. You drop your four functions in it and they all run as that same identity. So whatever that identity can do, all four can do. You needed write for the create function? Now your read function can write too. Break the read endpoint and the attacker gets the whole set. You can fix it by splitting into four Function Apps, but almost nobody does that, it goes against how the platform wants you to work.
Why nobody fixes it
And here I have to be honest about my own argument: this one is not the mirror. A Function App carrying one identity is App Service heritage, Azure packages compute as apps and the identity boundary follows the app. Flip Azure to identity first IAM tomorrow and those four functions still share one principal. The reverse holds too, nothing stops a team from running ten Lambdas on one shared role, and plenty of teams do exactly that.
So the shared identity comes from how Azure builds its PaaS, not from the mirror. But the mirror is what makes it expensive to fix. The fix is splitting into four Function Apps, and Azure pushes back on every step:
- Four times the role assignments. Every one of them a privileged write on the target.
- The 4000 cap. Azure allows 4000 role assignments per subscription. Truly fine grained, one assignment per identity per resource, hits that wall. The advice is always consolidate.
- Locked custom roles. Making one needs
Microsoft.Authorization/roleDefinitions/write, and that sits with, you guessed it, the central identity team.
And before you point at the escape hatches, I know they exist:
Storage Blob Data Readerinstead of Contributor- an assignment scoped to a single container
- an ABAC condition on the blob path
Every one of them is still a role assignment write on the target, so every one runs through the same gate you already can’t pass.
Fine grained is possible on Azure. It is just never the path of least resistance, and the path of least resistance is Storage Blob Data Contributor on the resource group.
On AWS least privilege is just the easy path. To be precise, grantRead is CDK, not IAM, and hand writing the policy JSON with the right ARN wildcards is nobody’s idea of easy. But the tooling can only make it a one liner because the grant is an edit on a role you own. On Azure the same one liner still needs Owner on the target, no tooling can paper over that. There least privilege is something you have to fight the platform for.
But isn’t Managed Identity the equivalent?
This is the objection I always get, so let me answer it. Managed Identity is the Azure thing everyone points at as the IAM role equivalent. And the identity half does match. Your compute gets an identity, credentials are handled for you, no secrets to rotate. A system assigned identity on a Function App is really close to what a Lambda gets for free.
But that is only the identity. A Lambda’s execution role carries its policy, the role is the access. A managed identity carries nothing, you still walk back to the resource and make the assignment there. It fixes the credential problem, not the access model.
ABAC is the same story if you were about to bring it up. On AWS the condition rides on the identity, on Azure it hangs on a role assignment on the resource. Both reach for the identity first, both get pulled back to the target.
Where Azure actually wins: PIM
One honest win for Azure, and then I’ll stop. Privileged Identity Management (PIM). You make a role assignment eligible instead of active. Someone requests it, it gets approved, the access turns on for a couple of hours and then expires by itself. All logged, no permanent standing access on your sensitive stuff.
And it works so well exactly because Azure is resource centric. The assignment already sits on the resource, so putting a timer and an approval on it is the natural thing to do. I do like this, too bad it’s behind the Entra ID P2 license.
Where this leaves me
The mental model shapes the tooling, and the tooling shapes how a whole company works. Azure was made for people moving from on-prem, where an admin hands out access on servers and an auditor checks who can reach what. That is also why my enterprise feels so at home in it. AWS was made for people building new systems, where the identity is a first class thing that travels with your code.
The whole mirror in one table:
| AWS | Azure | |
|---|---|---|
| You start at | the identity | the resource |
| The question | what can this identity do? | who has access to this resource? |
| A grant is | an edit on your own role | a privileged write on the target |
| Made for | the engineer | the auditor |
If you build infrastructure and you want access that is composable and that you can move around, the identity first model wins.
My preference is automation first, so I am happiest with AWS. And that is exactly the challenge when I work on Azure: “how to automate this without losing the least-privilege grip AWS gives me for free”.
Next in this world-vision series I want to show how the same split comes back in the way both clouds handle deployment.
End note
Thanks for reading, I hope it was useful. Please drop me a note on linked in when you have additional questions or remarks.
Oh, and I'm putting all of this into practice. A fully serverless email server that deploys on your own AWS in minutes and runs for about a dollar a month. More on that soon.
~ Joost van der Waal (Cloud guru)