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