Here's a confession: the first time I switched from AWS to GCP, I spent two hours looking for "IAM Roles" in the GCP console. I found them — but they meant something completely different from what AWS calls a "Role." Same word, different concept entirely. If you've ever felt this confusion, you're not alone. Every cloud uses slightly different terminology for the same fundamental concepts.

This guide does three things: (1) explains IAM from the ground up, (2) maps the terminology across all three major clouds, and (3) gives you production-ready examples for each. Whether you're on AWS, GCP, Azure, or all three — you'll walk away knowing exactly what to do.

What is IAM, Really?

Every single API call to any cloud service starts with two questions:

  • Authentication: "Who are you?" — prove your identity (certificate, password, token)
  • Authorization: "What can you do?" — check your permissions against a policy

IAM (Identity and Access Management) is the system that answers both. It's the bouncer at the door of every cloud resource.

How Every Cloud API Call Works
👤IdentityWho are you?
📋PolicyWhat can you do?
EvaluateAllow or deny?
AccessPerform action

The Rosetta Stone: Terminology Mapping

This is the most valuable table in this article. Bookmark it. Print it. Tattoo it. Every time you switch between clouds, come back here.

IAM Terminology: AWS vs GCP vs Azure
Concept AWS GCP Azure
Human userIAM UserGoogle AccountEntra ID User
Machine identityIAM RoleService AccountManaged Identity
Permission bundleIAM Policy (JSON)IAM Role (predefined)Role Definition
Attach permissions to identityAttach Policy to Role/UserGrant Role to MemberRole Assignment
GroupIAM GroupGoogle GroupEntra ID Group
Temp credentialsSTS AssumeRoleWorkload IdentityManaged Identity Token
Resource boundaryAccountProjectSubscription
Org-level guardrailSCPOrganization PolicyAzure Policy
Audit trailCloudTrailCloud Audit LogsActivity Log
Secret storeSecrets ManagerSecret ManagerKey Vault

The biggest confusion: In AWS, a "Role" is a machine identity (what a Lambda or EC2 instance assumes). In GCP, a "Role" is a set of permissions (like roles/storage.objectViewer). Completely different concepts, same word. Keep this in mind — it will save you hours of confusion.

AWS IAM — The Deepest, Most Granular

AWS has the most powerful (and most complex) IAM system. You can control permissions at a ridiculously fine level — down to "this Lambda can only read this specific S3 prefix between 9 AM and 5 PM on weekdays."

AWS Policy Anatomy

// AWS IAM Policy — the fundamental building block
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3ReadOnly",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-app-data",
        "arn:aws:s3:::my-app-data/*"
      ],
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "10.0.0.0/16"
        }
      }
    },
    {
      "Sid": "DenyDeleteEverything",
      "Effect": "Deny",
      "Action": "s3:DeleteObject",
      "Resource": "*"
    }
  ]
}

// Key concepts:
// Effect: Allow or Deny (Deny always wins)
// Action: What API calls are permitted
// Resource: Which specific resources (ARN = Amazon Resource Name)
// Condition: Extra constraints (IP, time, MFA, tags, etc.)

AWS: The Right Way (Roles, Not Keys)

# ❌ WRONG: Hardcoded credentials (will get leaked)
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=wJa...
# These end up in git, CI logs, and eventually on HaveIBeenPwned

# ✅ RIGHT: Use IAM Roles (no credentials to manage!)

# For EC2: Attach an Instance Profile
aws ec2 run-instances \
  --instance-type t3.micro \
  --iam-instance-profile Name=my-app-role \
  --image-id ami-xxx
# The EC2 instance automatically gets temp credentials via metadata service
# No keys. No rotation. No leaks.

# For Lambda: Attach an Execution Role
aws lambda create-function \
  --function-name process-orders \
  --role arn:aws:iam::123456789:role/lambda-order-processor \
  --runtime python3.12 \
  --handler app.handler
# Lambda gets temporary credentials automatically

# For EKS pods: Use IRSA (IAM Roles for Service Accounts)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/my-app-role
# Each pod gets its own IAM role via projected service account tokens

# For cross-account: AssumeRole
aws sts assume-role \
  --role-arn arn:aws:iam::OTHER_ACCOUNT:role/cross-account-reader \
  --role-session-name my-session
# Returns temporary credentials for the other account
# Terraform: Create an IAM Role for a Lambda function
resource "aws_iam_role" "lambda_role" {
  name = "order-processor-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "lambda.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_role_policy" "lambda_s3_access" {
  name = "s3-read-access"
  role = aws_iam_role.lambda_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:GetObject", "s3:ListBucket"]
      Resource = ["arn:aws:s3:::orders-bucket", "arn:aws:s3:::orders-bucket/*"]
    }]
  })
}

GCP IAM — Clean, Project-Centric

GCP takes a different approach. Instead of writing JSON policies from scratch, you pick from hundreds of predefined roles and grant them to identities at specific resource levels. Much simpler to get started, but less granular than AWS.

GCP IAM Model
Organization (mycompany.com)Top-level container. Organization policies set guardrails for all projects.
Folder (Engineering / Finance / Staging)Groups related projects. Permissions inherited downward.
Project (my-app-prod)The resource boundary. Each project has its own IAM bindings.
Resources (GCS bucket, Cloud SQL, GKE cluster)Individual resources can have their own IAM bindings too.
# GCP IAM: Grant a role to a service account at project level
gcloud projects add-iam-policy-binding my-app-prod \
  --member="serviceAccount:order-processor@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/storage.objectViewer"

# Key GCP roles you'll use most:
# roles/viewer                     — Read everything in the project
# roles/editor                     — Read + write (DANGEROUS — avoid!)
# roles/owner                      — Full control (only for admins)
# roles/storage.objectViewer       — Read GCS objects
# roles/cloudsql.client            — Connect to Cloud SQL
# roles/container.developer        — Deploy to GKE
# roles/iam.serviceAccountUser     — Impersonate service accounts

# ❌ WRONG: Download a JSON key file
gcloud iam service-accounts keys create key.json \
  --iam-account=my-sa@project.iam.gserviceaccount.com
# This key NEVER expires and will eventually leak. Don't do this.

# ✅ RIGHT: Use Workload Identity (no key files!)
# For GKE pods:
gcloud iam service-accounts add-iam-policy-binding \
  order-processor@my-app-prod.iam.gserviceaccount.com \
  --role="roles/iam.workloadIdentityUser" \
  --member="serviceAccount:my-app-prod.svc.id.goog[production/order-processor]"

# Kubernetes ServiceAccount annotation:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: order-processor
  namespace: production
  annotations:
    iam.gke.io/gcp-service-account: order-processor@my-app-prod.iam.gserviceaccount.com
# Pods using this SA automatically get GCP credentials. No JSON keys!

# For external workloads (GitHub Actions, other clouds):
# Use Workload Identity Federation — exchange an OIDC token for GCP credentials
gcloud iam workload-identity-pools create github-pool \
  --location="global" \
  --display-name="GitHub Actions"

# Your GitHub workflow uses: google-github-actions/auth@v2
# No service account key stored in GitHub secrets!
# Terraform: GCP IAM for a Cloud Run service
resource "google_service_account" "order_processor" {
  account_id   = "order-processor"
  display_name = "Order Processor Service"
  project      = "my-app-prod"
}

resource "google_project_iam_member" "storage_access" {
  project = "my-app-prod"
  role    = "roles/storage.objectViewer"
  member  = "serviceAccount:order-processor@my-app-prod.iam.gserviceaccount.com"
}

resource "google_project_iam_member" "sql_access" {
  project = "my-app-prod"
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:order-processor@my-app-prod.iam.gserviceaccount.com"
}

# Custom role (when predefined roles give too much access)
resource "google_project_iam_custom_role" "minimal_storage" {
  role_id     = "minimalStorageReader"
  title       = "Minimal Storage Reader"
  project     = "my-app-prod"
  permissions = [
    "storage.objects.get",
    "storage.objects.list",
  ]
}

Azure IAM — RBAC with Scope Hierarchy

Azure uses Role-Based Access Control (RBAC) with a clear scope hierarchy. Permissions flow down from Management Group → Subscription → Resource Group → Resource. The key concept is Managed Identities — Azure's equivalent of AWS Roles and GCP Service Accounts.

Azure RBAC Scope Hierarchy
Management GroupGroups multiple subscriptions. Enterprise-level governance.
Subscription (Pay-As-You-Go, Enterprise)Billing boundary. Like AWS Account or GCP Project.
Resource Group (rg-orders-prod)Logical container for related resources. Primary RBAC scope.
Resource (Storage Account, SQL Database, AKS)Individual resource. Finest RBAC scope.
# Azure: Assign a role to a Managed Identity
# Create a user-assigned managed identity
az identity create \
  --name order-processor-identity \
  --resource-group rg-orders-prod

# Assign "Storage Blob Data Reader" role scoped to a storage account
az role assignment create \
  --assignee order-processor-identity \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/SUB_ID/resourceGroups/rg-orders-prod/providers/Microsoft.Storage/storageAccounts/ordersdata"

# Key Azure built-in roles:
# Reader                          — Read everything
# Contributor                     — Read + write (no IAM changes)
# Owner                           — Full control including IAM
# Storage Blob Data Reader        — Read blobs
# Storage Blob Data Contributor   — Read + write blobs
# SQL DB Contributor               — Manage SQL databases
# AcrPull                          — Pull container images from ACR

# ❌ WRONG: Service Principal with client secret
az ad sp create-for-rbac --name my-app
# Creates a client ID + client secret that you have to rotate manually

# ✅ RIGHT: Managed Identity (no secrets to manage!)
# System-assigned: tied to one resource, deleted with it
az vm identity assign --name my-vm --resource-group rg-orders-prod

# User-assigned: reusable across multiple resources
az webapp identity assign \
  --name my-web-app \
  --resource-group rg-orders-prod \
  --identities order-processor-identity

# For AKS pods: Use Azure Workload Identity
# (Azure's equivalent of IRSA on AWS and Workload Identity on GCP)
az aks update --name my-cluster --resource-group rg-orders-prod \
  --enable-oidc-issuer --enable-workload-identity
# Terraform: Azure RBAC for an App Service
resource "azurerm_user_assigned_identity" "order_processor" {
  name                = "order-processor-identity"
  resource_group_name = azurerm_resource_group.orders.name
  location            = azurerm_resource_group.orders.location
}

resource "azurerm_role_assignment" "storage_read" {
  scope                = azurerm_storage_account.orders.id
  role_definition_name = "Storage Blob Data Reader"
  principal_id         = azurerm_user_assigned_identity.order_processor.principal_id
}

resource "azurerm_role_assignment" "sql_access" {
  scope                = azurerm_mssql_server.orders.id
  role_definition_name = "SQL DB Contributor"
  principal_id         = azurerm_user_assigned_identity.order_processor.principal_id
}

Machine Identity: The Most Important Concept

If there's ONE thing you take from this article, let it be this: never give your applications long-lived credentials. Every cloud has a way to give workloads temporary, auto-rotated identity without you managing any secrets.

Machine Identity: The Right Way on Each Cloud
Workload AWS GCP Azure
VM / ComputeInstance ProfileAttached SAManaged Identity
ServerlessLambda Execution RoleCloud Function SAFunction Managed ID
Kubernetes PodIRSAWorkload IdentityWorkload Identity
CI/CDOIDC ProviderWorkload Identity Fed.Federated Credential
Cross-cloudSTS AssumeRole w/ OIDCWorkload Identity Fed.Federated Identity

Universal Best Practices (All Clouds)

IAM Best Practices — Follow These or Get Hacked
1. Never use long-lived credentials
No access keys, no JSON key files, no client secrets stored in env vars. Use Roles (AWS), Service Accounts with Workload Identity (GCP), or Managed Identities (Azure).
2. Least privilege — always
If a service only reads from S3, don't give it s3:*. Give it s3:GetObject on the specific bucket. Review quarterly. Remove what's not used.
3. Separate environments with boundaries
Production in a separate AWS Account / GCP Project / Azure Subscription. Cross-environment access requires explicit trust — not just IAM policies.
4. MFA for all human users — no exceptions
Every human console login must require MFA. AWS: virtual MFA or hardware key. GCP: Google 2-Step. Azure: Entra Conditional Access with MFA.
5. Use Infrastructure as Code for IAM
Terraform or Pulumi for all IAM resources. Never click in the console to create roles. IaC = auditable, reviewable, rollbackable.
6. Monitor and alert on IAM changes
Alert on: new admin role grants, policy changes, root/owner usage, unusual API calls. AWS CloudTrail + GuardDuty, GCP Audit Logs + SCC, Azure Activity Log + Defender.
7. Lock the root/owner account
AWS Root Account: enable MFA, delete access keys, never use for daily work. GCP: Super Admin should be break-glass only. Azure: Global Admin same.

Common IAM Mistakes (Real Horror Stories)

# Mistake 1: The "I'll fix permissions later" policy
# Developer creates this during debugging... and forgets to remove it
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}
# Congratulations, your Lambda can now delete your production database,
# send emails as you, and mine Bitcoin on your EC2 instances.

# Mistake 2: Committing AWS credentials to GitHub
# In 2023, GitGuardian detected 12.8 MILLION leaked secrets on GitHub.
# AWS bots scan GitHub and will disable your keys within minutes,
# but attackers scan faster. Don't be this person.

# Mistake 3: One Service Account for everything
# "Let's create one SA with admin access and share it across 15 services"
# If ANY of those 15 services gets compromised, the attacker has ADMIN
# access to your entire cloud. Blast radius = everything.

# Mistake 4: Not rotating credentials
# GCP service account key created 3 years ago, used by 7 services,
# copied to 4 laptops, stored in 2 Slack channels. Good luck.

# Mistake 5: Forgetting to revoke access when someone leaves
# Former employee still has admin access 6 months after leaving.
# Automate offboarding: SCIM provisioning, regular access reviews.

Production IAM Architecture

Recommended Cloud Organization Structure
Organization Root Management account / Org admin
Separate accounts/projects per environment
🔒SecurityAudit logs, SIEM
🛠DevelopmentDev environments
🧪StagingPre-prod testing
🚀ProductionLive workloads

Which Cloud Has the Best IAM?

IAM Strengths by Cloud
Criteria AWS GCP Azure
GranularityMost granularGood (predefined)Good (scope-based)
Ease of useComplexSimplestMedium
Enterprise SSOSSO via IAM Identity CenterGoogle WorkspaceEntra ID (best)
K8s integrationIRSA (good)Workload Identity (best)Workload Identity (good)
Condition-basedMost powerfulLimitedConditional Access (Entra)

My honest recommendation:

  • If you need maximum control over fine-grained permissions — AWS
  • If you want the simplest setup with good defaults — GCP
  • If your company is already on Microsoft 365 / Active Directory — Azure (Entra ID integration is unbeatable)
  • If you're multi-cloud — learn the terminology table at the top and apply the same principles everywhere

The cloud doesn't matter as much as the practices. Least privilege, machine identities, no long-lived credentials, MFA everywhere, IaC for policies, regular access reviews. Follow these on any cloud and you'll be more secure than 90% of organisations out there. The remaining 10% is about catching the edge cases — and that comes with experience.