IAM: AWS vs Azure vs GCP — Visual Comparison
1 — The spectrum (the one idea to remember)
The difference is where the permission record physically lives: attached to the person, or attached to the resource.
Azure and GCP are both resource-centric. AWS is the outlier — it is principal-centric.
2 — Where does the permission live?
AWS — on the Identity
Alice carries her permissions. Resources are passive.
Azure — at a Scope
Identity in Entra ID; the assignment object lives at the resource scope.
GCP — on the Resource
Resource owns the access list directly.
Audit question: "Who has access to bucket-A / the storage account?"
AWS → scan IAM policies of every user, role & group + the bucket's own policy. Scattered.
Azure → az role assignment list --scope <resource-id>. One scope.
GCP → gcloud storage buckets get-iam-policy gs://bucket-A. One resource.
Why Azure sits in the middle: the identity (Alice) lives in a separate directory — Entra ID (formerly Azure AD) — exactly like AWS keeps identities in IAM. But the permission (the role assignment) is a standalone object pinned to a scope (resource, resource group, subscription, or management group), exactly like GCP pins a binding to a resource. So Azure is resource-centric for grants but has a separate identity plane.
3 — Real-life scenario
Scenario: Data engineer Alice needs access to a pipeline
• READ objects from storage
raw-data (bucket / blob container)• WRITE / query the analytics warehouse (BigQuery / Redshift / Synapse)
• Nothing else — least privilege
AWS — attach a policy to the identity
1+2. create & attach
aws iam create-policy --policy-name AliceDataPipeline
--policy-document file://alice-policy.json
aws iam attach-user-policy --user-name alice
--policy-arn arn:aws:iam::123456789012:policy/AliceDataPipeline
3. verify (by identity)
aws iam list-attached-user-policies --user-name alice
The policy is attached to Alice. The bucket/cluster have no record of her unless you add resource policies too.
Azure — assign a role at the resource scope
az role assignment create
--assignee alice@company.com
--role "Synapse SQL User"
--scope "/subscriptions//resourceGroups/
3. verify — by resource scope
az role assignment list --scope "/subscriptions//.../storageAccounts/
...or by identity
az role assignment list --assignee alice@company.com
Built-in roles cover most needs (like GCP predefined roles). Identity stays in Entra ID; the grant lives at the scope.
GCP — add a binding on each resource
get-iam-policy per resource2. on the BigQuery dataset
bq add-iam-policy-binding
--member="user:alice@company.com"
--role="roles/bigquery.dataEditor"
project:ingestion_ds
3. verify (per resource)
gcloud storage buckets get-iam-policy gs://raw-data bq get-iam-policy project:ingestion_ds
Permissions stored on the resources. Alice's identity stays clean.
4 — The audit difference (why it matters in practice)
Answering access questions
role assignment list --scope — one callget-iam-policy on the bucket — one callsimulate-principal-policy — easy, policies on Alicerole assignment list --assignee — easy, queryableAWS: easy to audit a person (all perms in one place), hard to audit a resource (perms scattered).
GCP: easy to audit a resource (all bindings on it), harder to audit a person across everything.
Azure: best of both for auditing — role assignments are first-class objects you can query by scope OR by assignee — but at the cost of an extra object type and a separate identity plane (Entra ID).
5 — Quick reference
| Dimension | AWS | Azure | GCP |
|---|---|---|---|
| Centricity | Principal-centric | Resource-centric (grant) + separate identity | Resource-centric |
| Identity plane | IAM users/roles | Entra ID (Azure AD) | Google / Cloud Identity |
| Grant unit | Policy doc attached to identity | Role assignment = principal + role + scope | Binding = (resource, role, member) |
| Role definitions | Managed / customer policies | Built-in / custom roles | Predefined / custom roles |
| Scope hierarchy | Account; SCPs as ceiling | Mgmt Group → Subscription → RG → Resource | Org → Folder → Project → Resource |
| Inheritance | Per-account (no cross-account) | Inherits down the scope chain | Inherits down the hierarchy |
| Explicit deny | Yes — deny always wins | Deny assignments (limited; via Blueprints/managed apps) | IAM Deny (separate, newer feature) |
| Workload identity | IAM Role (STS assume) | Managed Identity / Service Principal | Service Account (identity + resource) |