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

IAM Roles

IAM Roles are the right way to grant AWS permissions to applications, services, and federated identities. They issue short-lived credentials, can be assumed across accounts, and remove the need for long-lived access keys in code — the single biggest security upgrade most accounts can make.

AssumeRole, instance profiles, OIDC

EXAMPLE
// 1) Role vs User vs Group — pick the right thing
// User      — human identity; can have access keys (avoid keys for prod)
// Group     — bundles policies for many users (humans only)
// Role      — assumable identity; gets SHORT-LIVED creds; never has 'a password'
// Identity provider — federated identity from your IdP (Okta, Entra, Google)

// Use roles for: EC2 instance profiles, ECS task roles, Lambda execution roles, cross-account access,
// GitHub Actions OIDC, SAML / OIDC federation for humans.

// 2) A role has two policies:
//   Trust policy ("who can assume me") — defines principals + conditions
//   Permission policy ("what I can do") — IAM policies that gate AWS API calls

// 3) Trust policy — EC2 instance role
{
    "Version":"2012-10-17",
    "Statement": [
        {
            "Effect":"Allow",
            "Principal":{ "Service":"ec2.amazonaws.com" },
            "Action":"sts:AssumeRole"
        }
    ]
}
// Permission policy attached separately — only the permissions the workload needs.

// 4) ECS task role + execution role
{
    "Version":"2012-10-17",
    "Statement":[
        { "Effect":"Allow",
          "Principal":{ "Service":"ecs-tasks.amazonaws.com" },
          "Action":"sts:AssumeRole" }
    ]
}
// • Task role     — what the APP can do (S3 read, DynamoDB write)
// • Execution role — what ECS itself can do (pull image, push logs)

// 5) Cross-account role
{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Principal":{ "AWS":"arn:aws:iam::111122223333:root" },
            "Action":"sts:AssumeRole",
            "Condition":{
                "StringEquals":{ "sts:ExternalId":"customer-secret-123" },
                "Bool":{ "aws:MultiFactorAuthPresent":"true" }
            }
        }
    ]
}
// ExternalId — required for third-party assumptions (the 'confused deputy' fix).
// MFA condition — humans must MFA before assuming this role.

// 6) GitHub Actions via OIDC (no long-lived AWS keys!)
{
    "Version":"2012-10-17",
    "Statement":[
        {
            "Effect":"Allow",
            "Principal":{ "Federated":"arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
            "Action":"sts:AssumeRoleWithWebIdentity",
            "Condition":{
                "StringEquals":{
                    "token.actions.githubusercontent.com:aud":"sts.amazonaws.com",
                    "token.actions.githubusercontent.com:sub":"repo:my-org/my-repo:ref:refs/heads/main"
                }
            }
        }
    ]
}
# GitHub Actions YAML
- uses: aws-actions/configure-aws-credentials@v4
  with:
      role-to-assume: arn:aws:iam::111122223333:role/github-actions-deploy
      aws-region: ap-southeast-2
// No AWS access keys in GitHub Secrets. Token is JWT, valid for the workflow run.

// 7) Assume role from the CLI / SDK
awscli> aws sts assume-role \\
    --role-arn arn:aws:iam::111122223333:role/CrossAccountRead \\
    --role-session-name mara-debugging \\
    --external-id customer-secret-123 \\
    --duration-seconds 3600
// Returns AccessKeyId, SecretAccessKey, SessionToken — set as env vars for the next ~1 hour.

// SDK example
import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts';
const sts = new STSClient({ region: 'ap-southeast-2' });
const creds = await sts.send(new AssumeRoleCommand({
    RoleArn: 'arn:aws:iam::111122223333:role/CrossAccountRead',
    RoleSessionName: 'service-x',
    DurationSeconds: 3600,
}));

// 8) Role chaining — assuming a role from a role
// Limited to 1 hour duration max (AWS limitation).

// 9) Permission boundaries — cap what a role can do regardless of attached policies
// Attach a boundary policy to the role; the EFFECTIVE permissions = intersection of attached AND boundary.
// Useful to give developers self-service role creation with guardrails.

// 10) IAM Roles for Service Accounts (IRSA) on EKS
# K8s ServiceAccount annotation
apiVersion: v1
kind: ServiceAccount
metadata:
    name: my-app
    namespace: production
    annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/my-app
// Pods using this SA receive short-lived AWS creds — no static keys.

// 11) Cleanup — never leave roles around
// • Remove unused roles (Access Advisor shows last-used)
// • Rotate long-lived access keys IMMEDIATELY if used
// • Delete instance profiles when EC2 instances are decommissioned
// • Tag every role with Owner + Purpose for audit

// 12) Useful AWS CLI for roles
aws iam list-roles
aws iam get-role --role-name MyRole
aws iam list-attached-role-policies --role-name MyRole
aws iam list-role-policies          --role-name MyRole
aws iam get-policy-version --policy-arn arn:aws:iam::111122223333:policy/MyPolicy --version-id v1
aws iam simulate-principal-policy ...      # test a permission before granting

// 13) Monitor + audit
// • CloudTrail logs every AssumeRole call — review for unusual patterns
// • Access Advisor shows which permissions a role has actually used
// • IAM Access Analyzer finds public/cross-account access in trust policies
// • Tag roles by team/service for cost + ownership visibility

// 14) Common bugs
// • Long-lived access keys in code or CI → instant compromise risk; switch to roles + OIDC
// • Trust policy too broad (Principal: '*') → anyone can assume; ALWAYS scope by service/IdP/AWS account
// • Missing sts:ExternalId for third-party access → confused deputy attack
// • ECS task role and execution role swapped → 'task can't pull image' or 'task can't reach S3'
// • Boundary policy missing — role can be edited by devs to escalate; add a boundary
// • Assumed role expires mid-job — keep duration realistic + refresh proactively
// • EC2 metadata service v1 enabled → SSRF risk; require IMDSv2 on instance launch
// • Forgot to remove ec2.amazonaws.com from trust policy after migrating to Fargate → confusing failures

Why it matters

Roles + STS replace long-lived access keys. For every workload, attach a role with a tight trust policy and a least-privilege permission policy — instance profiles for EC2, task roles for ECS, IRSA for EKS, OIDC for GitHub Actions. Pair with permission boundaries so even your devs can’t accidentally escalate.

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

Example

Example
# Roles let services assume permissions — EC2 -> S3, Lambda -> DynamoDB.
# No long-lived access keys.
Try it Yourself »

Discussion

Loading…