# IAM Deep Dive — Part 2: Least Privilege in Practice

## 1. Least Privilege Is a Process

Nobody writes perfect policies up front. The practical loop:

1. Start from **AWS managed policies** only for prototypes; for production write **customer managed** policies scoped to specific actions and resource ARNs.
2. Use **IAM Access Analyzer**: *policy validation* (syntax, security warnings), *policy generation* from CloudTrail activity ("generate a policy from what this role actually did in the last 90 days"), *unused access* findings (roles, keys, permissions not used), and *external access* findings (resources shared outside the account/org).
3. Use **last accessed** data (service and action level) to remove unused permissions.
4. Enforce guardrails centrally with **SCPs**, **permissions boundaries** and **RCPs**.

## 2. Cross-Account Access Patterns

```
account A (CI/tools)                       account B (prod)
role ci-deployer ── sts:AssumeRole ──►  role deploy-prod
                                         trust: { "Principal": { "AWS": "arn:aws:iam::A:role/ci-deployer" },
                                                  "Condition": { "StringEquals": { "sts:ExternalId": "…" } } }   ← ExternalId for third parties
```

- Prefer **role assumption** over sharing resources with resource policies, except for things like S3/KMS where resource policies are natural (with `aws:PrincipalOrgID` conditions).
- **ExternalId** protects against the *confused deputy* problem when a **third party** (a SaaS vendor) assumes roles in many customers' accounts.
- `aws:SourceArn` / `aws:SourceAccount` conditions protect service principals from confused-deputy issues (e.g. SNS publishing to your SQS queue).

## 3. Federation for Humans and CI

- **IAM Identity Center** (SSO): users from your IdP (Okta, Entra ID, Google) get **permission sets** in specific accounts; short-lived role sessions via the CLI (`aws sso login`) — no IAM users (`security/security/09-identity_providers`).
- **GitHub Actions / GitLab OIDC**: the CI system's OIDC token is exchanged for role credentials with conditions on the repository, branch or environment (`devops-cloud/terraform/07-terraform_in_ci_cd`).

## 4. Permissions Boundaries: Safe Delegation

Let developers create roles for their Lambda functions **without** being able to escalate privileges:

```json
// developers may create roles only if they attach this boundary
{
  "Effect": "Allow",
  "Action": ["iam:CreateRole", "iam:PutRolePolicy", "iam:AttachRolePolicy"],
  "Resource": "arn:aws:iam::111111111111:role/app/*",
  "Condition": { "StringEquals": { "iam:PermissionsBoundary": "arn:aws:iam::111111111111:policy/AppBoundary" } }
}
```

The boundary caps effective permissions of any role they create, no matter what policy they attach.

## 5. ABAC: Scaling With Tags

Instead of one policy per team per resource, tag principals and resources and write one policy:

```json
{
  "Effect": "Allow",
  "Action": ["secretsmanager:GetSecretValue"],
  "Resource": "*",
  "Condition": { "StringEquals": { "aws:ResourceTag/team": "${aws:PrincipalTag/team}" } }
}
```

New team members and new secrets need only tags. Protect the tags themselves (deny `iam:TagRole`/`*:TagResource` for the controlling tag keys except to admins).

## 6. Privilege Escalation Paths to Block

Permissions that let a principal grant itself more: `iam:CreatePolicyVersion`, `iam:SetDefaultPolicyVersion`, `iam:AttachUserPolicy`/`AttachRolePolicy`, `iam:PutUserPolicy`, `iam:UpdateAssumeRolePolicy`, `iam:PassRole` (to a more privileged role) combined with `lambda:CreateFunction`/`ec2:RunInstances`/`ecs:RunTask`, `iam:CreateAccessKey` for other users. Scope `iam:PassRole` to specific role ARNs and `iam:PassedToService`.

## 7. Monitoring

- **CloudTrail** (organization trail, all regions, log file validation) — who did what.
- Alerts on root usage, IAM policy changes, access key creation, `AssumeRole` from unexpected sources, `AccessDenied` spikes.
- **GuardDuty** for anomalous credential use (keys used from unusual locations, instance credentials used outside AWS).

## Under the Hood: How `iam:PassRole` Works

Many services act **as a role you choose**: Lambda runs with its execution role, ECS tasks with task roles, EC2 with an instance profile, CloudFormation with a service role. When you create such a resource, the service doesn't check what the role can do — it checks whether **you** are allowed to `iam:PassRole` that role to that service. `PassRole` isn't an API call; it's a permission evaluated implicitly during `CreateFunction`, `RunInstances`, `RegisterTaskDefinition`, etc. If it's granted on `*`, any user who can create a Lambda function can attach the account's most powerful role to it and run arbitrary code with those permissions — one of the most common escalation paths. Scope it: `"Resource": "arn:aws:iam::111111111111:role/app/*"` with `"Condition": { "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" } }`.

![Least privilege as a loop: write scoped policies, validate and generate them with Access Analyzer from CloudTrail activity, remove unused access, and enforce guardrails with SCPs, permissions boundaries and ABAC tags; cross-account access uses role trust with ExternalId and organization conditions](diagrams/02-least-privilege-loop.svg)

## Common Pitfalls (Part 2)

1. **`Action: "*"` / `Resource: "*"`** in workload roles.
2. **IAM users with access keys** for humans or apps.
3. **`iam:PassRole` on `*`**.
4. **Trust policies with `"Principal": {"AWS": "*"}`** (anyone!) or missing ExternalId for vendors.
5. **Ignoring Access Analyzer external-access findings** (buckets/keys shared publicly).
6. **Unprotected ABAC tags** — users change their own tags to gain access.

## Quick Reference (Part 2)

```
Access Analyzer: validate · generate from CloudTrail · unused access · external access
cross-account: role assumption, ExternalId for third parties, PrincipalOrgID, SourceArn/SourceAccount
humans: Identity Center permission sets · CI: OIDC with repo/branch conditions
delegation: permissions boundaries · scale: ABAC (PrincipalTag = ResourceTag) · block escalation: scope PassRole
