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.
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.
| Concept | AWS | GCP | Azure |
|---|---|---|---|
| Human user | IAM User | Google Account | Entra ID User |
| Machine identity | IAM Role | Service Account | Managed Identity |
| Permission bundle | IAM Policy (JSON) | IAM Role (predefined) | Role Definition |
| Attach permissions to identity | Attach Policy to Role/User | Grant Role to Member | Role Assignment |
| Group | IAM Group | Google Group | Entra ID Group |
| Temp credentials | STS AssumeRole | Workload Identity | Managed Identity Token |
| Resource boundary | Account | Project | Subscription |
| Org-level guardrail | SCP | Organization Policy | Azure Policy |
| Audit trail | CloudTrail | Cloud Audit Logs | Activity Log |
| Secret store | Secrets Manager | Secret Manager | Key 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: 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: 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.
| Workload | AWS | GCP | Azure |
|---|---|---|---|
| VM / Compute | Instance Profile | Attached SA | Managed Identity |
| Serverless | Lambda Execution Role | Cloud Function SA | Function Managed ID |
| Kubernetes Pod | IRSA | Workload Identity | Workload Identity |
| CI/CD | OIDC Provider | Workload Identity Fed. | Federated Credential |
| Cross-cloud | STS AssumeRole w/ OIDC | Workload Identity Fed. | Federated Identity |
Universal Best Practices (All Clouds)
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
Which Cloud Has the Best IAM?
| Criteria | AWS | GCP | Azure |
|---|---|---|---|
| Granularity | Most granular | Good (predefined) | Good (scope-based) |
| Ease of use | Complex | Simplest | Medium |
| Enterprise SSO | SSO via IAM Identity Center | Google Workspace | Entra ID (best) |
| K8s integration | IRSA (good) | Workload Identity (best) | Workload Identity (good) |
| Condition-based | Most powerful | Limited | Conditional 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.