Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~14 min· topic 4 of 4

Topic 1.4

STS, MFA & Least Privilege in Practice

In one line

STS is the service that hands out every temporary credential in AWS; MFA conditions and OIDC federation let humans and CI pipelines use those temporary credentials instead of static keys; and least privilege is a process you iterate on with real access data, not a policy you get right on day one.

0/4 · 0%

Think of it like this

A visitor desk at a secure building. It checks who you are (your employee badge, your passport, a phone code), then prints a day pass with only the floors you need. That desk is STS — the Security Token Service — and every temporary credential in AWS comes from it.

Key ideas

  1. 01

    Key STS calls: AssumeRole (a principal takes on a role — Topic 1.2), AssumeRoleWithWebIdentity (an external OIDC identity such as GitHub Actions or a Kubernetes service account takes on a role), GetSessionToken (a user gets temporary creds, typically after proving MFA), and GetCallerIdentity (who am I right now? — your first debugging command, always).

  2. 02

    MFA ENFORCEMENT: add a Condition with aws:MultiFactorAuthPresent: true to sensitive actions, or require MFA in a role's trust policy, so even a leaked password or key can't perform them without the second factor.

  3. 03

    OIDC FEDERATION for CI: instead of storing an AWS access key as a GitHub secret, configure GitHub as an OIDC identity provider in IAM and let the workflow call AssumeRoleWithWebIdentity. The trust policy can restrict WHICH repo and WHICH branch may assume the role (the sub claim), so a fork or a feature branch can't deploy to production. The CI/CD course's Phase 7 covers the pipeline side of this.

  4. 04

    For human access, IAM IDENTITY CENTER (formerly AWS SSO) is the modern default: people sign in once through your identity provider, pick an account and a permission set, and aws sso login gives the CLI short-lived credentials. No IAM users with long-lived keys for humans at all.

  5. 05

    LEAST PRIVILEGE in practice is iterative: start with a reasonably scoped policy, then use IAM Access Analyzer's policy generation (built from real CloudTrail activity) and the 'last accessed' data on each role to REMOVE permissions nobody has used in 90 days. Trying to write a perfect minimal policy up front usually ends with someone frustrated attaching *.

Code & diagrams

github-oidc-trust.jsonjson

Only the main branch of one repo can assume this role. Without the `sub` condition, ANY GitHub repo could.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012: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:acme/api:ref:refs/heads/main"
        }
      }
    }
  ]
}
require-mfa-for-delete.jsonjson
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyDestructiveWithoutMFA",
      "Effect": "Deny",
      "Action": ["ec2:TerminateInstances", "rds:DeleteDBInstance", "s3:DeleteBucket"],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": { "aws:MultiFactorAuthPresent": "false" }
      }
    }
  ]
}
least-privilege-loop.shbash
# 1. Always start debugging with: who am I?
aws sts get-caller-identity

# 2. Which services has this role actually used, and when?
JOB=$(aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789012:role/app-server --query JobId --output text)
aws iam get-service-last-accessed-details --job-id "$JOB" \
  --query 'ServicesLastAccessed[].[ServiceNamespace,LastAuthenticated]' --output table

# 3. Simulate a policy before shipping it
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/app-server \
  --action-names s3:DeleteObject \
  --resource-arns arn:aws:s3:::acme-reports/q3.csv

# 4. Humans: short-lived creds through Identity Center, no static keys
aws sso login --profile acme-dev

Explain it without notes

01

Why is OIDC federation from GitHub Actions safer than storing an AWS access key as a repository secret?

02

Why does the MFA policy use BoolIfExists rather than Bool?

Practice

01

Modify the GitHub OIDC trust policy so that ANY branch of acme/api can assume the role, but no other repository can.

02

A role has had AmazonS3FullAccess, AmazonDynamoDBFullAccess, and AmazonSQSFullAccess attached for a year. Describe the steps you'd take to reduce it to least privilege without breaking production.

Trade-offs

  • ↔

    Tight least-privilege policies shrink the blast radius of a compromise, but every missing permission is a production AccessDenied waiting to happen. Generating policies from real access data and rolling out in stages is the practical middle ground between 'too broad to be safe' and 'too narrow to ship'.

Done when you can

  • I run aws sts get-caller-identity first whenever permissions behave unexpectedly.

  • I can write an OIDC trust policy that pins a specific repo and branch.

  • I can require MFA for destructive actions and explain why BoolIfExists matters.

  • I use last-accessed data and Access Analyzer to remove unused permissions over time.