iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

IAM Policies

IAM policies are JSON documents that grant or deny actions on AWS resources. Get this wrong and you grant the world write access to your data; get it right and even a leaked credential is contained to a narrow blast radius.

Identity vs resource, conditions, deny

EXAMPLE
// 1) Policy shape
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid":      "AllowReadOwnObjects",
            "Effect":   "Allow",
            "Action":   ["s3:GetObject"],
            "Resource": "arn:aws:s3:::reports-bucket/users/${aws:userid}/*"
        }
    ]
}
// Effect: Allow | Deny    (an explicit Deny always wins)
// Action: service:Operation (\*  wildcard within a service)
// Resource: an ARN or list of ARNs (also wildcards)
// Condition: extra constraints (IP, MFA, tag match, time of day)

// 2) Identity-based vs resource-based policies
//   Identity policy  — attached to a user, role, or group; says 'this principal can do X'
//   Resource policy  — attached to the resource (S3 bucket, KMS key, SQS queue);
//                      says 'these principals can do X to me'
//   Both are evaluated; access requires at least one Allow AND no explicit Deny.

// 3) Least privilege — narrow Action AND Resource
// ✓ Good — read one specific bucket only
{
    "Effect":   "Allow",
    "Action":   ["s3:GetObject", "s3:ListBucket"],
    "Resource": [
        "arn:aws:s3:::company-invoices",
        "arn:aws:s3:::company-invoices/*"
    ]
}
// ❌ Bad — over-broad
{
    "Effect":   "Allow",
    "Action":   "*",
    "Resource": "*"
}

// 4) Conditions — situational gates
{
    "Effect":   "Allow",
    "Action":   "s3:*",
    "Resource": "arn:aws:s3:::sensitive-data/*",
    "Condition": {
        "Bool":           { "aws:MultiFactorAuthPresent": "true" },
        "NumericLessThan":{ "aws:MultiFactorAuthAge":     "3600" },
        "IpAddress":      { "aws:SourceIp": ["203.0.113.0/24", "198.51.100.0/24"] },
        "StringEquals":   { "aws:RequestedRegion": "ap-southeast-2" }
    }
}
// IP allowlist + MFA required + region locked.

// 5) Tag-based access — scales without rewriting policy per resource
// Resource has tag Team=billing
// Principal allowed when their tag PrincipalTeam == resource tag aws:ResourceTag/Team
{
    "Effect":   "Allow",
    "Action":   ["ec2:StartInstances", "ec2:StopInstances"],
    "Resource": "arn:aws:ec2:*:*:instance/*",
    "Condition": {
        "StringEquals": { "aws:ResourceTag/Team": "${aws:PrincipalTag/Team}" }
    }
}

// 6) Explicit Deny for guardrails — wins over any Allow
{
    "Effect":   "Deny",
    "Action":   "*",
    "Resource": "*",
    "Condition": {
        "StringNotEquals": { "aws:RequestedRegion": ["ap-southeast-2", "us-east-1"] }
    }
}
// Locks all activity to two regions — even if someone attaches AdministratorAccess.

// 7) Bucket policy — limit S3 to TLS and a VPC endpoint
{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Sid":"DenyInsecureTransport",
            "Effect":"Deny",
            "Principal":"*",
            "Action":"s3:*",
            "Resource":["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"],
            "Condition":{ "Bool": { "aws:SecureTransport": "false" } }
        },
        {
            "Sid":"OnlyFromVPC",
            "Effect":"Deny",
            "Principal":"*",
            "Action":"s3:*",
            "Resource":["arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*"],
            "Condition":{ "StringNotEquals": { "aws:SourceVpce": "vpce-0a1b2c3d" } }
        }
    ]
}

// 8) Trust policy — who can ASSUME a role
{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Principal":{ "Service":"ec2.amazonaws.com" },
            "Action":"sts:AssumeRole"
        }
    ]
}
// EC2 instances using this role's instance profile can call STS to assume it.
// For cross-account: Principal: { AWS: "arn:aws:iam::ACCOUNT_ID:root" } + ExternalId condition.

// 9) Permission boundary — cap on what a role can EVER grant or use
// Attach a boundary policy that says 'no action beyond this list'.
// Useful for letting devs create their own roles within guardrails.

// 10) Common patterns to avoid
//   • iam:PassRole granted broadly → privilege escalation (pass an admin role to a service)
//   • Resource: * with destructive actions like s3:DeleteBucket or ec2:TerminateInstances
//   • Inline policies — hard to audit, prefer customer-managed
//   • AdministratorAccess on dev users → fine for sandboxes, terrible for prod accounts

// 11) Tools to validate before you ship
//   • IAM Access Analyzer — finds external access in policies, validates policies
//   • aws iam simulate-principal-policy — replay an action against a principal
//   • Policy linter (cfn-lint, parliament) in CI
//   • CloudTrail + IAM Access Advisor — find unused permissions, then trim

awscli> aws iam simulate-principal-policy \\
    --policy-source-arn arn:aws:iam::123:role/AppRole \\
    --action-names s3:GetObject \\
    --resource-arns arn:aws:s3:::my-bucket/secret.json

// 12) Checklist
//   ✓ One role per workload, not shared
//   ✓ Action and Resource narrowed; \* only after deliberate review
//   ✓ Conditions for MFA, IP, region where it makes sense
//   ✓ Resource policies + identity policies aligned (both required)
//   ✓ Explicit Deny guardrails at the organization / SCP level
//   ✓ Permission boundaries on roles devs can create
//   ✓ Quarterly review: Access Advisor → remove unused permissions

Why it matters

Least privilege is hard, but you don’t need to ship it on day one — ship something workable, then use Access Advisor and CloudTrail to peel away unused permissions over the next few sprints. Lock the worst foot-guns up front: explicit Deny guardrails for regions you don’t use, MFA required for sensitive actions, and never Action: *, Resource: * for a real workload.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
{
    "Version": "2012-10-17",
    "Statement": [{ "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::bucket/*" }]
}
Try it Yourself »

Discussion

Loading…