Multi-Cloud · lesson 2 of 8
Intermediate
5Advanced
3Also part of Multi-Cloud
8- + CloudWatch Monitoring Basics from AWS
- + Azure Monitor Basics from Azure
- + Cloud Monitoring Basics from Google Cloud
- + AWS Cost and Well-Architected Basics from AWS
- + Azure Cost Management Basics from Azure
- + GCP Cost and Billing Basics from Google Cloud
- + Availability, Regions, and the Arithmetic of Nines from Cloud Interviews
- + Scaling, Queues, and Backpressure from Cloud Interviews
Also part of: Cloud Security
IAM Concepts That Transfer: AWS vs Azure vs GCP
Grant read-only access to one bucket, done in AWS IAM, Azure RBAC, and GCP IAM side by side — the vocabulary differs, the underlying model doesn't.
By SkillByExample · Published
Grant one identity read-only access to one bucket, nothing else. The task is identical on every cloud; only the nouns change.
The vocabulary mapping
| Concept | AWS | Azure | GCP |
|---|---|---|---|
| Who gets access | IAM user / role (ARN) | Security principal (user, group, service principal, managed identity — Object ID) | Member (user:, serviceAccount:, group:) |
| What they can do | Policy (JSON, list of allowed actions) | Role definition (built-in or custom, list of actions) | Role (predefined or custom, list of permissions) |
| On what | Resource ARN in the policy’s Resource field | Scope (a resource ID: subscription / resource group / storage account / container) | Resource the binding is attached to (bucket, project, folder) |
| The act of granting | Attach policy to user/role | Create a role assignment at a scope | Add an IAM policy binding on the resource |
Same shape every time: principal + role + resource. What’s actually worth learning is this mapping, not the JSON syntax of any one cloud — the syntax is what a reference page gives you; the mapping is what lets you read any of the three cold.
AWS: IAM policy scoped to one bucket
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyReportsBucket",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::reports-bucket",
"arn:aws:s3:::reports-bucket/*"
]
}
]
}
Both ARNs are required: the bucket ARN itself (without /*) covers s3:ListBucket, and
the /* suffix covers s3:GetObject on the objects inside it. Leaving off either one is
the most common reason a “read-only” policy still fails with AccessDenied on a list
call.
aws iam put-user-policy \
--user-name analytics-reader \
--policy-name read-only-reports-bucket \
--policy-document file://policy.json
aws iam get-user-policy --user-name analytics-reader --policy-name read-only-reports-bucket
$ aws iam get-user-policy --user-name analytics-reader --policy-name read-only-reports-bucket
{
"UserName": "analytics-reader",
"PolicyName": "read-only-reports-bucket",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadOnlyReportsBucket",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::reports-bucket", "arn:aws:s3:::reports-bucket/*"]
}
]
}
}
Azure: role assignment at container scope
Azure ships a built-in role for exactly this — Storage Blob Data Reader — so there’s no
policy document to author at all; you assign the existing role at the narrowest scope that
covers the container.
az role assignment create \
--assignee "a1e4f6b2-8c3d-4f19-b7e2-5d9c1a0f3e88" \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33/resourceGroups/learn-rg/providers/Microsoft.Storage/storageAccounts/reportsstore/blobServices/default/containers/reports"
$ az role assignment create --assignee "a1e4f6b2-8c3d-4f19-b7e2-5d9c1a0f3e88" --role "Storage Blob Data Reader" --scope "/subscriptions/.../containers/reports"
{
"condition": null,
"conditionVersion": null,
"id": "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33/.../roleAssignments/2f88a1c4-...",
"principalId": "a1e4f6b2-8c3d-4f19-b7e2-5d9c1a0f3e88",
"principalType": "User",
"roleDefinitionId": "/subscriptions/.../roleDefinitions/2a2b9908-6ea1-4ae2-8e65-a410df84e7d1",
"roleDefinitionName": "Storage Blob Data Reader",
"scope": "/subscriptions/3b1f9c2a-6d4e-4a7b-9c11-2f5e8a0b7d33/.../containers/reports"
}
--scope is the whole story here: assign at the storage-account level and the principal
can read every container in it, not just reports. Azure RBAC inherits downward through
management group -> subscription -> resource group -> resource -> (for storage) container,
so the scope you pick is the actual access boundary, not the role name.
GCP: IAM policy binding on the bucket
gcloud storage buckets add-iam-policy-binding gs://reports-bucket \
--member="user:reader@example.com" \
--role="roles/storage.objectViewer"
$ gcloud storage buckets add-iam-policy-binding gs://reports-bucket --member="user:reader@example.com" --role="roles/storage.objectViewer"
bindings:
- members:
- user:reader@example.com
role: roles/storage.objectViewer
etag: CAE=
version: 1
roles/storage.objectViewer grants storage.objects.get and storage.objects.list —
read and list, nothing else — the direct GCP counterpart of the AWS policy and the Azure
role above. Read the bucket’s whole policy to confirm what else is already bound there,
since add-iam-policy-binding merges into the existing document rather than replacing it:
gcloud storage buckets get-iam-policy gs://reports-bucket
$ gcloud storage buckets get-iam-policy gs://reports-bucket
bindings:
- members:
- user:reader@example.com
role: roles/storage.objectViewer
- members:
- serviceAccount:pipeline@my-project.iam.gserviceaccount.com
role: roles/storage.objectAdmin
etag: CAI=
version: 1
That second binding is a trap for the unwary: roles/storage.objectAdmin looks like a
sibling of objectViewer but grants full read/write/delete. GCP’s flat “list of bindings”
model makes it easy to see everyone with access to a resource in one call — which is an
advantage AWS’s per-principal policies and Azure’s per-assignment model don’t give you as
directly.
The one thing that doesn’t map cleanly
AWS policies can be explicit deny or implicit deny (anything not allowed is
denied, and an explicit Deny always wins over an Allow). Azure and GCP both support
explicit deny too (Azure via deny assignments, GCP via IAM Deny policies), but reach for it
far less often in practice — the idiomatic pattern on all three is still additive,
least-privilege grants, not deny rules layered on top of broad ones.
Practice
1. Write the AWS policy that grants read-only access to a single *prefix* inside a bucket (`reports-bucket/2026/`), not the whole bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::reports-bucket",
"Condition": { "StringLike": { "s3:prefix": "2026/*" } }
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::reports-bucket/2026/*"
}
]
}
ListBucket still targets the bucket ARN — you can’t scope a list call to a prefix without
a Condition — while GetObject scopes naturally through the resource ARN’s path.
2. Find the built-in Azure role that allows write but not delete on blob data.
az role definition list --name "Storage Blob Data Contributor" --query "[].permissions"
There isn’t one. Storage Blob Data Contributor grants read, write, and delete as a
single bundle — Azure has no built-in “read-write-no-delete” blob role. Getting that
exact permission set means authoring a custom role definition.
3. List every principal with any role on the GCP bucket, grouped by role.
gcloud storage buckets get-iam-policy gs://reports-bucket --format=json | jq '.bindings'
This is the query from the lesson body, pulled apart by role — useful as an access review before adding one more binding to a bucket you didn’t set up yourself.
4. Revoke the access granted in each of the three examples above.
aws iam delete-user-policy --user-name analytics-reader --policy-name read-only-reports-bucket
az role assignment delete --assignee "a1e4f6b2-8c3d-4f19-b7e2-5d9c1a0f3e88" \
--role "Storage Blob Data Reader" --scope "/subscriptions/.../containers/reports"
gcloud storage buckets remove-iam-policy-binding gs://reports-bucket \
--member="user:reader@example.com" --role="roles/storage.objectViewer"
Three different verbs — delete-user-policy, role assignment delete,
remove-iam-policy-binding — for the same conceptual undo.
Next: the same 4 vCPU / 16 GB workload, priced and named on all three clouds — what
m5.xlarge, Standard_D4s_v5, and n2-standard-4 actually mean.