Least-privilege cloud IAM: policy scope, privilege-escalation paths, permissions boundaries, workload identity over static keys, MFA enforced by policy, cross-account trust, and separating deploy from runtime. Use when generating IAM policies, roles, or trust documents, wiring CI/CD service accounts or workload identities, delegating role creation, or designing cross-account access.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add ShieldNet-360/secure-vibe --skill iam-best-practices --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Iam Best Practices?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-iam-best-practices-c68d033c)More formats (shields.io, HTML) on the badges page.
---
id: iam-best-practices
version: "2.0.0"
title: "Identity & Access Management Best Practices"
description: "Least-privilege cloud IAM: policy scope, privilege-escalation paths, permissions boundaries, workload identity over static keys, MFA enforced by policy, cross-account trust, and separating deploy from runtime. Use when generating IAM policies, roles, or trust documents, wiring CI/CD service accounts or workload identities, delegating role creation, or designing cross-account access."
category: prevention
severity: critical
applies_to:
- "when generating IAM policies, roles, or trust documents"
- "when wiring CI/CD service accounts or workload identities"
- "when delegating role or policy creation to another team"
- "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: 1300
compact: 1800
full: 2200
rules_path: "rules/"
related_skills: ["auth-security", "iac-security", "cicd-security", "secret-detection"]
last_updated: "2026-08-12"
sources:
- "NIST SP 800-53 Rev. 5 (AC-2, AC-3, AC-6, AC-17, IA-2, IA-5)"
- "CIS Controls v8 (Controls 5 and 6)"
- "AWS IAM Best Practices"
- "Google Cloud IAM recommender"
- "CNCF Kubernetes RBAC Good Practices"
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 the workload's stated job requires: deny by default,
then add concrete actions against concrete resources. A wildcard action on a
wildcard resource — in a custom policy, in `AdministratorAccess`, or in `iam:*`
attached to something that runs continuously — is the same finding wearing three
different names.
- Read a policy for the **privileges it can reach**, not only the ones it names. A
principal that can write policy (`iam:PutRolePolicy`, `iam:AttachRolePolicy`,
`iam:CreatePolicyVersion`) can grant itself anything, and a principal that can pass
a role into a compute service it may also create (`iam:PassRole` with
`lambda:CreateFunction`, `ec2:RunInstances`, `glue:CreateJob`) inherits that role's
privileges. These read as narrow grants and are administrative ones.
`references/escalation-paths.md` lists the combinations worth failing a review over.
- Bound delegated permissions with a **permissions boundary** or an organization-level
policy (SCP, GCP org policy, Azure management-group policy) when teams may create
their own roles. The boundary caps what any role they create can do, which is what
makes self-service role creation safe rather than a privilege-escalation primitive.
- Prefer **workload identity** — IRSA on EKS, GKE Workload Identity, Azure Managed
Identity, OIDC federation from CI — over a static key. The goal is that no
long-lived credential exists to leak. Where one is genuinely unavoidable, give it a
documented rotation cadence and automatic disablement on inactivity, and treat that
as a temporary state rather than the steady state.
- Enforce MFA for human principals through a **policy condition**
(`aws:MultiFactorAuthPresent`), not only a directory setting, so a credential that
bypasses the directory still cannot act.
- Separate the **deploy** identity from the **runtime** identity. The pipeline gets
permission to create and change infrastructure; the running service gets permission
to do its job and nothing that mutates IAM. `cicd-security` owns hardening the
pipeline that holds the deploy role.
- Scope cross-account trust to the actual counterparty. Inside your own organization
that means a specific principal ARN or `aws:PrincipalOrgID`; for a **third party**
acting on your behalf it additionally means an `ExternalId` — a per-consumer value
the third party supplies, which defeats the confused-deputy case where another of
their customers can name your account. Treat `ExternalId` as an identifier, not a
secret: it is not one, and relying on its confidentiality is a false assurance.
- Keep sessions short and make the length a deliberate choice. Human and CI roles
should use the shortest session the workflow tolerates. A longer window on a
break-glass role is a defensible operational trade — an incident should not expire
mid-response — but it has to be stated as a decision, paired with alerting, and not
copied onto ordinary roles.
- For Kubernetes RBAC, scope `Role` and `RoleBinding` to one namespace and reserve
`ClusterRole` for genuinely cluster-wide objects. Review every `cluster-admin`
binding at pull-request time, and remember that permission to create workloads in a
namespace is permission to use every service account in it.
- Log IAM-mutating calls to a tamper-evident sink and alert on them: policy changes,
`iam:PassRole`, access-key creation, and role assumption from an unexpected
principal. `logging-security` owns the shape of the record.
- Require out-of-band approval for break-glass use (root, organization owner,
`cluster-admin`) and alert on every invocation, not on a sample.
### NEVER
- Use the root or organization-owner account for day-to-day work. It gets a hardware
MFA device, offline storage, and only the tasks that genuinely require it.
- Embed a long-lived access key in source, a container image, a machine image, or a
CI environment variable where a workload identity or OIDC federation is available.
- Grant `iam:PassRole` with `Resource: "*"`. Pin the exact role ARNs the caller may
pass, and read that grant together with what the caller can launch.
- Share one principal between multiple humans or multiple services. One identity per
actor is what makes the audit log mean anything.
- Disable MFA for a principal to resolve a login problem. Rotate the device; the
requirement stays.
- Persist an OIDC or SAML assertion beyond its stated lifetime. Refresh by
re-assertion rather than storing the original token.
### KNOWN FALSE POSITIVES
- `Resource: "*"` on APIs that accept no resource ARN — `sts:GetCallerIdentity`,
`ec2:DescribeRegions` and their equivalents. The wildcard is the only expressible
value, so the finding is the *action* being unnecessary, not the resource scope.
- Provider-managed service-linked roles carry broader permissions than a role you
would write. That breadth is the provider's contract and is not yours to narrow.
- A bootstrap operator in a fresh account often needs elevated permissions before the
guardrails it installs exist. Gate it by tag or organization policy and revoke on
completion — a bootstrap role that survives bootstrap is a finding again.
- Local development emulators accept any credential. That is a property of the
emulator, not a grant in your account.
- Enumerating explicit resource ARNs is correct for a bounded set. Attribute-based
scoping (tags, organization paths) is the answer when the set grows, not a
security upgrade over naming resources you can name.
## Context (for humans)
Cloud IAM has one property that makes reviewing it different from reviewing other
access control: **the effective permission set is not the stated one**. A role that
can attach a policy can attach `AdministratorAccess` to itself. A role that can pass
another role into a service it can also create runs as that role. Neither policy
contains a wildcard, and both are administrative. Reading a policy for what it says,
rather than for what it can reach, is the failure that produces most
over-permissioned estates.
The second property is that the scalable answer is rarely a better-written policy.
Permissions boundaries and organization policies are what let a platform team hand
role creation to product teams without handing over the account, and attribute-based
scoping is what keeps a policy correct as the resource set grows. Enumerating ARNs
more carefully does not scale past the point where someone starts pasting `*` to make
the deploy pass.
Capital One (2019) and Uber (2022) are the canonical illustrations of the two halves:
an over-permissioned role reached through a server-side request, and a static
credential found in a script after an MFA-fatigue login.
## References
- `references/verifying-findings.md` — confirm or refute a finding, then lock it
- `references/escalation-paths.md` — the permission combinations that are
administrative without containing a wildcard, and how boundaries cap them
- `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).
- [CNCF Kubernetes RBAC good practices](https://kubernetes.io/docs/concepts/security/rbac-good-practices/).
- [NIST SP 800-53 Rev. 5](https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final).
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!