09.LAB-X · Hands-On

Cross-Cloud IAM Translator

Same intent, three policy languages. See what carries across cleanly and what each provider says differently.

Real organizations rarely use only one cloud. Engineers fluent in one provider but not the others write policies that drift in subtle ways — the AWS read-only role grants more than the equivalent Azure role, or vice versa — and the inconsistency becomes an audit finding.

This lab walks four common scenarios and writes each one in AWS IAM JSON, Azure RBAC, and GCP IAM bindings. Read all three columns. Notice where the syntax differs but the intent matches, and where the intent itself shifts because of how each provider models permissions.

01

Read-only access to one storage bucket

Intent: Group "DataAnalysts" can list and read objects in the bucket named reports — nothing else, no other buckets, no write access.

AWS IAM
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:ListBucket",
      "s3:GetObject"
    ],
    "Resource": [
      "arn:aws:s3:::reports",
      "arn:aws:s3:::reports/*"
    ]
  }]
}
Attached to a role or group. Note: the bucket-level ARN (no slash) is needed for ListBucket; the object-level ARN (with /*) is needed for GetObject.
Azure RBAC
# Built-in role: Storage Blob Data Reader
Role: Storage Blob Data Reader
Scope: /subscriptions/<id>/
       resourceGroups/data/
       providers/Microsoft.Storage/
       storageAccounts/reports
Principal: group:DataAnalysts
Built-in role at the storage account scope. Azure scopes are URL-like; the role is named, not JSON.
GCP IAM
{
  "bindings": [{
    "role": "roles/storage.objectViewer",
    "members": [
      "group:data-analysts@example.com"
    ]
  }]
}
# Applied to: bucket "reports" only
Predefined role storage.objectViewer. Binding attaches to the bucket, not the project — that's the scoping.

What diverges

AWS grants permissions through verbs (Get, List). Azure uses named built-in roles that bundle verbs. GCP uses predefined roles. All three map cleanly here — this is the easy case.
02

Full admin access to a single project / account / subscription

Intent: Alice should have administrative control over one specific environment (one AWS account, one Azure subscription, one GCP project), and nothing in any other environment.

AWS IAM
# Federated through IAM Identity Center
Permission set: AdministratorAccess
Account assignment: 111122223333
Principal: alice@example.com

# Or directly on the role:
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}
Modern AWS uses Identity Center with permission sets per account. The legacy way (an IAM user with the AdministratorAccess AWS-managed policy) still works but is discouraged.
Azure RBAC
Role: Owner
Scope: /subscriptions/<subscription-id>
Principal: alice@example.com
Azure's built-in Owner role at subscription scope. Beware: Owner can also grant access to others — "admin" in Azure usually means "User Access Administrator" + a less-permissive role for actual operations.
GCP IAM
{
  "bindings": [{
    "role": "roles/owner",
    "members": [
      "user:alice@example.com"
    ]
  }]
}
# At: projects/payments-prod
Primitive role roles/owner. Project-scoped binding. GCP's Owner is broad — consider resourcemanager.projectIamAdmin + service-specific admin roles instead.

What diverges

The scoping unit differs. AWS scopes at the account level (one Identity Center permission set per account assignment). Azure scopes within a subscription's URL path. GCP scopes within a project. The same intent — "admin one environment" — lives in three different namespaces.
03

Workload identity for an application reading secrets

Intent: An application running on the cloud's compute platform needs to read a specific secret from the cloud's secrets manager — without any downloaded credential file.

AWS IAM
# Role attached to the EC2 instance / Lambda / ECS task
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "secretsmanager:GetSecretValue",
    "Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:db-creds-*"
  }]
}
Workload assumes the role via the instance metadata service. IMDSv2 returns temporary credentials.
Azure RBAC
# System-assigned managed identity on the resource
Role: Key Vault Secrets User
Scope: /subscriptions/<id>/.../
       vaults/db-creds-vault
Principal: managed-identity:<principal-id>
Managed identity is created with the resource; RBAC binding gives it read access to Key Vault. No keys on the workload.
GCP IAM
{
  "bindings": [{
    "role": "roles/secretmanager.secretAccessor",
    "members": [
      "serviceAccount:app-prod@my-project.iam.gserviceaccount.com"
    ]
  }]
}
# Bound to: secret "db-creds"
Service account attached to the Compute Engine VM / Cloud Run service. Workload Identity Federation extends this to non-GCP workloads.

What diverges

All three converged on the same pattern. No long-lived keys; the workload gets credentials from the platform via an identity bound to the resource. The vocabulary is different (instance role, managed identity, service account) but the architecture is identical.
04

Conditional access — only from a specific network

Intent: Allow read access to a sensitive bucket only when the request comes from the corporate VPN range (203.0.113.0/24) or from a workload running inside the cloud account itself.

AWS IAM
{
  "Effect": "Allow",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::sensitive/*",
  "Condition": {
    "IpAddress": {
      "aws:SourceIp": "203.0.113.0/24"
    }
  }
}
Condition keys filter by source IP. For VPC-internal access, use aws:SourceVpce or aws:SourceVpc.
Azure
# Conditional Access policy (Entra)
Users: all
Apps: Storage
Conditions:
  Location:
    Include: Corporate VPN range
Grant: Allow

# + Storage account firewall
Storage firewall: 203.0.113.0/24
Azure splits this between Conditional Access (identity layer) and the storage firewall (network layer). Defense in depth.
GCP
# IAM Condition with CEL
{
  "role": "roles/storage.objectViewer",
  "members": ["group:analysts@..."],
  "condition": {
    "expression": "request.ip.matches('203.0.113.0/24')"
  }
}

# Stronger: VPC Service Controls perimeter
IAM Conditions handle simple IP filtering. For real data perimeter, use VPC Service Controls — the strongest option among the three providers.

What diverges

This is where the providers genuinely differ. AWS expresses network restrictions as IAM condition keys, baked into the policy. Azure splits it across Conditional Access (the IdP layer) and storage firewalls (the resource layer). GCP gives you IAM Conditions for simple cases and VPC Service Controls for the strong case — a discrete product with no AWS/Azure equivalent.

Patterns that generalize

After walking the four scenarios, certain patterns become visible across all three providers:

The point

Cloud IAM is multi-dialect, not multi-language. The mental models are essentially identical across the four major providers; the vocabulary, the scoping conventions, and which controls are bundled together differ. Practitioners who can read all three quickly diagnose multi-cloud problems that single-cloud experts get stuck on.

When you're translating a policy from one provider to another, the questions to ask are always the same: who is the principal? what action? on what resource? under what conditions? The answers are written in different syntax in each cloud, but the underlying intent is portable.

References

Formatted in APA 7. Alphabetized by first author's last name.

  1. Amazon Web Services. (n.d.). IAM JSON policy reference. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html
  2. Google Cloud. (n.d.). Policy reference: IAM. https://cloud.google.com/iam/docs/reference/rest/v1/Policy
  3. Microsoft. (n.d.). Azure built-in roles. Azure RBAC documentation. https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles