IAM & Access Control
IAM Core Concepts
Authentication vs Authorization
| Concept | Question It Answers | Mechanisms | Examples |
|---|---|---|---|
| Authentication (AuthN) | "Who are you?" | Passwords, MFA, certificates, biometrics, hardware keys | SAML SSO, OIDC login, mTLS client certificates |
| Authorization (AuthZ) | "What can you do?" | RBAC, ABAC, ACL, policy engines | IAM policies, Kubernetes RBAC, OPA decisions |
Identity Providers & Federation
Modern environments use federated identity to allow users to authenticate once and access multiple systems (Single Sign-On). Key protocols:
- SAML 2.0 (Security Assertion Markup Language): XML-based standard used for enterprise SSO. IdP (Okta, ADFS) issues assertions consumed by SPs. Used for AWS IAM Identity Center, GCP Workforce Identity Federation.
- OIDC (OpenID Connect): OAuth 2.0 extension using JWT tokens. Widely used for modern web/mobile apps. Used for Kubernetes OIDC, GitHub Actions OIDC, GCP Workload Identity.
- OAuth 2.0: Authorization framework (not authentication). Grants scoped access tokens to third-party applications without exposing credentials.
Federation Flow (OIDC)
- User/workload presents identity to IdP (e.g., GitHub Actions, Okta)
- IdP issues a short-lived JWT token signed with its private key
- Cloud provider (AWS/GCP) validates the token against the IdP's public JWKS endpoint
- Cloud provider issues temporary credentials based on configured trust policy
- Workload uses temporary credentials — no long-lived keys stored anywhere
Access Control Models
| Model | Full Name | How It Works | Best For | Examples |
|---|---|---|---|---|
| RBAC | Role-Based Access Control | Permissions assigned to roles; users assigned to roles | Enterprise environments with well-defined job functions | Kubernetes RBAC, AWS IAM roles, database roles |
| ABAC | Attribute-Based Access Control | Access decisions based on attributes of user, resource, environment | Fine-grained, dynamic access control; multi-tenancy | AWS IAM Conditions, OPA policies, XACML |
| ReBAC | Relationship-Based Access Control | Access based on relationships between entities in a graph | Social platforms, document sharing, hierarchical resources | Google Zanzibar, SpiceDB, Ory Keto |
AWS IAM Deep Dive
IAM Building Blocks
- Users: Long-term credentials for humans or applications (legacy). Prefer federation and roles.
- Groups: Collections of users. Attach policies to groups, not individual users.
- Roles: Short-term credentials assumed by users, services, or cross-account principals. Preferred method for workloads.
- Policies: JSON documents defining allow/deny actions on resources. Evaluation order: explicit deny > explicit allow > implicit deny.
Policy Types
| Type | Scope | Use Case |
|---|---|---|
| AWS Managed Policy | Account | Common use cases provided by AWS (ReadOnlyAccess, AdministratorAccess) |
| Customer Managed Policy | Account | Custom policies reusable across multiple principals |
| Inline Policy | Single principal | Tightly coupled policy for one-off permissions — avoid for maintainability |
| Service Control Policy (SCP) | AWS Organization | Guardrails across all accounts in an OU — cannot grant permissions, only restrict |
| Permission Boundary | IAM entity | Maximum permission ceiling for a user/role — used to delegate IAM administration safely |
| Resource-based Policy | Resource | Attached to S3 buckets, SQS queues, KMS keys — grants cross-account access |
Least-Privilege S3 Policy Example
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListBucket",
"Effect": "Allow",
"Action": [
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": "arn:aws:s3:::my-app-data-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["app-data/${aws:PrincipalTag/Environment}/*"]
}
}
},
{
"Sid": "AllowObjectOperations",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-data-bucket/app-data/${aws:PrincipalTag/Environment}/*"
},
{
"Sid": "DenyUnencryptedUploads",
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-app-data-bucket/*",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "aws:kms"
}
}
}
]
}
EC2 IAM Role with Least Privilege (SSM + CloudWatch)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SSMCoreAccess",
"Effect": "Allow",
"Action": [
"ssm:UpdateInstanceInformation",
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
"ec2messages:AcknowledgeMessage",
"ec2messages:DeleteMessage",
"ec2messages:FailMessage",
"ec2messages:GetEndpoint",
"ec2messages:GetMessages",
"ec2messages:SendReply"
],
"Resource": "*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
],
"Resource": "arn:aws:logs:ap-southeast-1:123456789012:log-group:/app/*"
}
]
}
Trust Policy for Cross-Account Role Assumption
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ci-cd-role"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-abc123"
},
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
Service Control Policy — Deny Non-Approved Regions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"NotAction": [
"iam:*",
"organizations:*",
"support:*",
"sts:GetCallerIdentity"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["ap-southeast-1", "us-east-1"]
}
}
}
]
}
GCP IAM Deep Dive
Resource Hierarchy
GCP IAM policies are inherited down the hierarchy. A binding at a higher level grants access to all resources below it:
Organization (example.com)
└── Folder (Production)
└── Folder (Engineering)
└── Project (my-app-prod)
└── Resource (Cloud Storage bucket, GKE cluster, etc.)
Role Types
- Primitive roles: Owner, Editor, Viewer — overly broad, avoid in production
- Predefined roles: Fine-grained roles managed by Google (e.g.,
roles/container.developer,roles/storage.objectViewer) - Custom roles: User-defined with specific permissions — use when predefined roles are too broad
GCP IAM Binding Examples
# Grant a service account viewer access to a specific bucket
gcloud storage buckets add-iam-policy-binding gs://my-prod-bucket \
--member="serviceAccount:[email protected]" \
--role="roles/storage.objectViewer"
# Grant a group access to deploy to Cloud Run in a specific project
gcloud projects add-iam-policy-binding my-app-prod \
--member="group:[email protected]" \
--role="roles/run.developer"
# Create a custom role with minimal permissions
gcloud iam roles create AppDeployer \
--project=my-app-prod \
--title="App Deployer" \
--permissions="run.services.update,run.services.get" \
--stage=GENERAL_AVAILABILITY
Workload Identity Federation (GCP)
Allows workloads outside GCP (GitHub Actions, AWS, on-prem) to authenticate as GCP service accounts without key files:
# Configure Workload Identity Pool for GitHub Actions
gcloud iam workload-identity-pools create "github-pool" \
--project="my-project" \
--location="global" \
--display-name="GitHub Actions Pool"
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="my-project" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
# Bind the pool to a service account
gcloud iam service-accounts add-iam-policy-binding \
[email protected] \
--project="my-project" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"
Kubernetes RBAC
Core RBAC Resources
- Role: Namespace-scoped permissions
- ClusterRole: Cluster-wide permissions (or for non-namespaced resources)
- RoleBinding: Binds a Role to subjects within a namespace
- ClusterRoleBinding: Binds a ClusterRole to subjects cluster-wide
Developer Role (Namespace-Scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: my-app
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/portforward", "pods/exec"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: my-app
subjects:
- kind: Group
name: "developers"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
Platform Ops ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-ops
rules:
- apiGroups: [""]
resources: ["nodes", "persistentvolumes", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "statefulsets"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["*"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "rolebindings"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: platform-ops-binding
subjects:
- kind: Group
name: "platform-team"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: platform-ops
apiGroup: rbac.authorization.k8s.io
MFA Enforcement
AWS: Enforce MFA via IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowViewAccountInfo",
"Effect": "Allow",
"Action": ["iam:GetAccountPasswordPolicy", "iam:ListVirtualMFADevices"],
"Resource": "*"
},
{
"Sid": "AllowManageOwnMFA",
"Effect": "Allow",
"Action": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice"
],
"Resource": [
"arn:aws:iam::*:mfa/${aws:username}",
"arn:aws:iam::*:user/${aws:username}"
]
},
{
"Sid": "DenyAllExceptAllowedIfNoMFA",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:GetUser",
"iam:ListMFADevices",
"iam:ResyncMFADevice",
"sts:GetSessionToken"
],
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
GCP: Enforce MFA via Org Policy
# Require 2-step verification for all users in the organization
# In Google Admin Console: Security > 2-Step Verification > Allow users to turn on 2-Step Verification
# Then enforce: Enforcement > Turn on enforcement
# For GCP API access, use org policy to require OS Login with 2FA
gcloud resource-manager org-policies set-policy \
--organization=123456789 \
os_login_2fa_policy.yaml
# os_login_2fa_policy.yaml
constraint: constraints/compute.requireOsLoginTwoFactor
booleanPolicy:
enforced: true
Hardware Keys & TOTP
- YubiKey (FIDO2/WebAuthn): Hardware security key — phishing-resistant, supports AWS, GCP, GitHub, Okta. Recommended for privileged users.
- TOTP apps: Google Authenticator, Authy, 1Password — time-based one-time passwords. Good baseline but susceptible to real-time phishing.
- Push-based MFA: Duo, Okta Verify — convenient but vulnerable to MFA fatigue attacks (deny all unexpected push requests).
Service Account Best Practices
Rules for Service Accounts
- No shared accounts: One service account per application/service. Never share credentials between workloads.
- Workload Identity over key files: On Kubernetes/GKE, use Workload Identity to map K8s service accounts to cloud IAM — no JSON key files.
- Rotate keys automatically: If key files are unavoidable, automate rotation and store in a secrets manager.
- Audit regularly: Review service account permissions quarterly. Delete unused accounts and keys.
- Scope tightly: Grant only the specific permissions the service needs, scoped to specific resources.
# Enable Workload Identity on a GKE cluster
gcloud container clusters update my-cluster \
--workload-pool=my-project.svc.id.goog \
--region=asia-southeast1
# Annotate Kubernetes Service Account to bind to GCP SA
kubectl annotate serviceaccount my-app-ksa \
--namespace=production \
iam.gke.io/[email protected]
# Allow the KSA to impersonate the GCP SA
gcloud iam service-accounts add-iam-policy-binding \
[email protected] \
--role=roles/iam.workloadIdentityUser \
--member="serviceAccount:my-project.svc.id.goog[production/my-app-ksa]"
Privileged Access Management (PAM)
Just-In-Time (JIT) Access
JIT access eliminates standing privileges. Users request elevated access for a specific time window, it is approved, granted, used, and automatically revoked. This dramatically reduces the blast radius if credentials are compromised.
JIT Access Workflow
- Engineer submits access request with justification and time window (e.g., "need prod DB access for incident investigation, 2 hours")
- Request goes through approval (manager, security team, automated for low-risk)
- Temporary role/permission is granted via automation (AWS IAM role assumption, GCP IAM Conditions with expiry)
- All session activity is recorded (session recording via BeyondTrust, Teleport, AWS Session Manager)
- Access is automatically revoked after the time window expires
Break-Glass Procedures
Break-glass accounts are emergency access accounts used when normal access paths are unavailable (IdP outage, major incident). They must be:
- Stored in a physical safe or offline vault, not in a digital secrets manager that might be unavailable
- Protected by hardware MFA (YubiKey)
- Monitored with real-time alerting on any use
- Audited and credentials rotated after each use
- Tested quarterly to ensure they work when needed
Session Recording with AWS Session Manager
# Start a session with session recording enabled
aws ssm start-session \
--target i-0abc1234def567890 \
--document-name AWS-StartInteractiveCommand \
--region ap-southeast-1
# SSM Document for session recording to S3
{
"schemaVersion": "1.0",
"description": "Session recording configuration",
"sessionType": "Standard_Stream",
"inputs": {
"s3BucketName": "my-session-logs-bucket",
"s3KeyPrefix": "ssm-sessions/",
"s3EncryptionEnabled": true,
"cloudWatchLogGroupName": "/aws/ssm/sessions",
"cloudWatchEncryptionEnabled": true,
"idleSessionTimeout": "20",
"maxSessionDuration": "60",
"runAsEnabled": true,
"runAsDefaultUser": ""
}
}
IAM Audit & Governance
# AWS: Find all IAM users with console access but no MFA
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d | \
awk -F',' '$4 == "true" && $8 == "false" {print $1, "has console access but no MFA"}'
# AWS: List all roles and their last used date
aws iam get-account-authorization-details \
--filter Role \
--query 'RoleDetailList[*].[RoleName,RoleLastUsed.LastUsedDate]' \
--output table
# GCP: Find service account keys older than 90 days
gcloud iam service-accounts list --format="value(email)" | while read SA; do
gcloud iam service-accounts keys list \
--iam-account="$SA" \
--managed-by=user \
--format="table(name,validAfterTime)"
done
# Kubernetes: Find all ClusterRoleBindings to cluster-admin
kubectl get clusterrolebindings -o json | jq '
.items[] |
select(.roleRef.name == "cluster-admin") |
{name: .metadata.name, subjects: .subjects}'