---
id: iam-best-practices
version: "1.1.0"
title: "Identity & Access Management Best Practices"
description: "Least-privilege IAM design, key rotation, MFA enforcement, role assumption, and cross-account access patterns for AWS / GCP / Azure / Kubernetes"
category: prevention
severity: critical
applies_to:
  - "when generating IAM policies, roles, or trust documents"
  - "when wiring CI/CD service accounts or workload identities"
  - "when reviewing access-key creation, rotation, or revocation"
  - "when designing cross-account or cross-tenant access"
languages: ["hcl", "yaml", "json", "python", "go", "typescript"]
token_budget:
  minimal: 1100
  compact: 1500
  full: 2400
rules_path: "rules/"
related_skills: ["auth-security", "iac-security", "secret-detection"]
last_updated: "2026-06-06"
sources:
  - "NIST SP 800-53 Rev. 5 (AC-2, AC-3, AC-6, AC-17, IA-2, IA-5)"
  - "NIST SP 800-63B (Authenticator Assurance)"
  - "CIS Controls v8 (Controls 5 and 6)"
  - "AWS IAM Best Practices"
  - "Google Cloud IAM Recommender"
  - "Microsoft Azure RBAC Best Practices"
  - "CNCF Kubernetes RBAC Good Practices"
  - "OWASP Cloud Security Top 10"
external_tools:
  - name: prowler
    purpose: "live cloud account IAM/security posture assessment (AWS/Azure/GCP)"
    command: "prowler aws"
---

# Identity & Access Management Best Practices

## Rules (for AI agents)

### ALWAYS
- Grant the **minimum** permissions required for the workload's stated job
  (NIST AC-6). Start with a deny-by-default policy and add concrete actions
  with explicit `Resource` ARNs; never `Action: "*"` combined with
  `Resource: "*"`.
- Prefer **workload identity** (IAM roles for service accounts on EKS,
  GKE Workload Identity, Azure Managed Identity) over long-lived access
  keys. Static access keys are the exception, not the default.
- Require **MFA** for every human IAM user, especially any principal that
  can assume a privileged role. Enforce MFA via an IAM policy condition
  (`aws:MultiFactorAuthPresent: true`), not just a directory-level setting.
- Rotate access keys, service-account keys, and signing keys on a documented
  schedule (≤ 90 days). Detect inactive credentials (≥ 90 days unused) and
  disable them automatically.
- Use **role assumption with `sts:AssumeRole` + ExternalId** for cross-account
  trust. The ExternalId must be unique per consumer and stored as a secret
  in both accounts.
- Issue **session-scoped** credentials with `MaxSessionDuration ≤ 1h` for
  human roles and ≤ 12h for break-glass roles. Long-lived sessions defeat
  rotation.
- Separate **deploy** and **runtime** identities. The CI/CD pipeline gets a
  deploy role; the running service gets a distinct runtime role with no
  IAM-mutating permissions.
- For Kubernetes RBAC, scope `Role` / `RoleBinding` to a single namespace;
  use `ClusterRole` only for true cluster-wide objects. Audit
  `cluster-admin` bindings on every PR.
- Log every IAM-mutating call (CloudTrail / Cloud Audit Logs / Azure
  Activity Log) to a tamper-evident sink. Alert on policy changes,
  `iam:PassRole`, `iam:CreateAccessKey`, and `sts:AssumeRole` from
  unexpected principals.
- For **break-glass** access (root, owner, cluster-admin), require an
  out-of-band approval (e.g., PagerDuty incident + ticket) and emit an
  immediate alert on every use.
- Tag every IAM principal with `owner`, `environment`, and `purpose`. Use
  these tags in SCPs / org policies to constrain blast radius.

### NEVER
- Use the **root account** for day-to-day operations. Root credentials get a
  hardware MFA device, are stored offline, and are used only for the small
  set of root-only tasks (e.g., closing the account, changing the support
  plan).
- Embed long-lived access keys in source, container images, AMIs, or
  CI environment variables when a workload identity is available.
- Grant `iam:PassRole` with `Resource: "*"`. Always pin the role ARNs the
  caller may pass to downstream services.
- Grant `iam:*` or `sts:*` to a runtime workload — these are deploy-time
  permissions only.
- Share a single IAM user across multiple humans or services. One principal
  per identity is the audit invariant.
- Use `AdministratorAccess` (or any `*:*`) managed policy on a routine basis;
  treat it as a break-glass-only attachment.
- Trust any cross-account assume-role without an `ExternalId` condition for
  third-party integrations (Confused Deputy: AWS Security Bulletin 2021).
- Hard-code AWS / GCP / Azure ARNs / resource IDs in policy documents
  without a corresponding tag-based or organization-path scope (when the
  number of resources can grow).
- Disable MFA for a principal to "fix" a login problem — rotate the device,
  do not remove the requirement.
- Persist OIDC / SAML assertion tokens beyond their stated TTL. Refresh by
  re-assertion, not by storing the original token.

### KNOWN FALSE POSITIVES
- `Resource: "*"` is acceptable for inherently account-scoped read
  operations like `ec2:DescribeRegions` or `sts:GetCallerIdentity` — those
  APIs do not accept a resource ARN.
- Service-linked roles (e.g., `AWSServiceRoleForAutoScaling`) ship with
  broader permissions than your custom roles; that is by design and
  managed by the provider.
- One-time bootstrap operators (Terraform-runners in a fresh account) often
  need elevated permissions; gate by tag / SCP and revoke after the
  bootstrap is complete.
- Local-development emulators (LocalStack, GCS emulator) may accept any
  credentials; that is a property of the emulator, not a real grant.

## Context (for humans)

Identity & access management is the foundation of every cloud security
control. The recurring failure modes are: over-permissive policies
(`*:*`), long-lived access keys checked into git, missing MFA on
privileged accounts, and `AssumeRole` trust policies without an
`ExternalId`. These are the same root causes documented in the Capital
One (2019), Verkada (2021), and Uber (2022) breaches.

The high-leverage controls are:

1. **Workload identity over access keys.** A pod in EKS that assumes a
   role via IRSA never needs a credential to leak.
2. **MFA on every human, enforced by policy.** A leaked password alone
   is no longer sufficient to act.
3. **Least-privilege grants reviewed at PR time.** The cheapest moment
   to constrain a permission is when it's being added — not when an
   auditor asks six months later.
4. **Mandatory key rotation.** Static credentials silently age into
   liabilities; automate the rotation so it is not skipped.
5. **Cross-account trust with ExternalId.** The Confused Deputy class
   of attacks is fully mitigated by an ExternalId convention.

This skill enforces those controls when AI assistants generate IAM
policies, trust documents, or CI/CD identities.

## References

- `rules/iam_policy_invariants.json`
- `rules/key_rotation_policy.json`
- [AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html).
- [Google Cloud IAM recommender](https://cloud.google.com/iam/docs/recommender-overview).
- [NIST SP 800-53 Rev. 5](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-53r5.pdf).
- [CIS Controls v8](https://www.cisecurity.org/controls/v8).
- [CNCF Kubernetes RBAC Good Practices](https://kubernetes.io/docs/concepts/security/rbac-good-practices/).
