Advanced Cloud
IAM and least privilege in the cloud — policies, roles, SCPs, and common mistakes
Identity and Access Management (IAM) is the central access control layer of the cloud. Configured permissively — whether due to haste, lack of knowledge, or convenience — it becomes the primary entry point for attacks. The principle of least privilege requires that each identity only has access to what it needs, when it needs it.
Core concepts — AWS IAM as reference
Principal → who requests access (user, role, service, account)
Policy → JSON document defining Allow/Deny on Actions and Resources
Role → assumable identity with no password — used by services and workloads
SCP → Service Control Policy — maximum permission boundary across orgs/OUs
Permission Boundary → maximum limit a policy can grant to a role
Well-written policy vs dangerous policy
// BAD — full S3 access on any resource
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
// GOOD — read-only access scoped to specific bucket and prefix
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::finance-prod",
"arn:aws:s3:::finance-prod/reports/*"
],
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
Roles for workloads — never use hardcoded access keys
# Bad: access key in code or environment variable
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# Correct: EC2 assumes an Instance Profile (role)
# Lambda uses an Execution Role
# EKS uses IRSA (IAM Roles for Service Accounts)
# No persistent keys — credentials are temporary and automatically rotated
Example trust policy for a role used by Lambda:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "lambda.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
SCPs — organizational guardrails
SCPs do not grant permissions — they cap the ceiling. Even if a role has s3:*, if the SCP blocks s3:DeleteBucket, the action is denied.
// SCP: prevent S3 bucket deletion across the entire production OU
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyS3BucketDelete",
"Effect": "Deny",
"Action": [
"s3:DeleteBucket",
"s3:DeleteBucketPolicy"
],
"Resource": "*"
}]
}
// SCP: restrict allowed AWS regions
{
"Effect": "Deny",
"NotAction": [
"iam:*", "sts:*", "cloudfront:*", "route53:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "sa-east-1"]
}
}
}
Common mistakes and how to fix them
1. AdministratorAccess on application roles
Fix: use IAM Access Analyzer to generate least-privilege policies from real usage logs
2. IAM users with access keys for automation
Fix: roles + STS AssumeRole or OIDC federation
3. Inline policies attached to users
Fix: managed policies — reusable and versioned
4. No context conditions (IP, MFA, time of day)
Fix: add Condition blocks for sensitive access
5. Roles with open trust policy ("Principal": "*")
Fix: always specify the exact principal
IAM auditing
# Generate credential report (who has what)
aws iam generate-credential-report
aws iam get-credential-report --query 'Content' --output text | base64 -d
# IAM Access Analyzer — detect unintended external access
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789:analyzer/my-analyzer
# Simulate permissions for a role
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789:role/my-role \
--action-names s3:DeleteBucket \
--resource-arns arn:aws:s3:::finance-prod