AWS IAM
AWS Identity and Access Management (IAM) is the service that controls who can do what in an AWS account. Every API call to every AWS service — launching an EC2 instance, reading an S3 bucket, invoking a Lambda function — is authorized by IAM first. It's the foundation the entire account's security rests on, and the single most important AWS service to understand, because nearly every AWS security incident traces back to an IAM misconfiguration.
IAM's model is deny-by-default: nothing is allowed until a policy explicitly grants it. Get IAM right — least privilege, roles instead of long-lived keys, no wildcard admin everywhere — and most of AWS security follows. Get it wrong and a single leaked credential or over-permissive policy exposes everything.
TL;DR
- IAM controls access to every AWS API call. Default is deny — permissions must be explicitly granted by policies.
- Users are for humans/legacy long-lived credentials; roles are assumable identities with temporary credentials — prefer roles for almost everything (services, cross-account, federated humans).
- Policies are JSON documents listing
Allow/DenyonAction+Resource(+ optionalCondition). They attach to identities (identity-based) or resources (resource-based). - Evaluation logic: an explicit
Denyalways wins; otherwise you need an explicitAllow; no matching statement = implicit deny. - Least privilege is the core discipline — grant the minimum, scope resources tightly, avoid
"*"actions/resources. - The biggest real-world win: stop using IAM user access keys — use roles (for EC2/Lambda/etc.) and federated SSO (for humans) so there are no long-lived secrets to leak.
Core Concepts
Users, Roles, and Groups
The most important distinction: roles have no permanent credentials. When an EC2 instance, Lambda function, or federated user assumes a role, AWS issues short-lived credentials (via STS) that expire automatically. This is why roles are strongly preferred — there's no static key sitting in a config file to leak. An instance profile attaches a role to EC2; the service uses it transparently.
Policies
Permissions are JSON policy documents. The anatomy:
- Effect:
AlloworDeny. - Action: the API operations (
s3:GetObject,ec2:*). - Resource: which ARNs it applies to (scope this tightly —
"*"means everything). - Condition: optional constraints (source IP, MFA present, tags, time).
Policy types you'll meet: managed policies (reusable, AWS- or customer-managed), inline policies (embedded in one identity), resource-based policies (attached to the resource itself, like an S3 bucket policy — these enable cross-account access), and SCPs (Service Control Policies, org-wide guardrails that cap what accounts can do).
How Access Is Decided
When a request is made, IAM evaluates all applicable policies with a specific precedence:
The rules that trip people up: an explicit Deny overrides any Allow (useful for guardrails), and everything is denied unless something explicitly allows it. Permission boundaries and SCPs add ceilings — even an Allow doesn't work if a boundary/SCP forbids it. When "I have Allow but it's still denied," suspect an SCP, boundary, or resource-policy blocking it.
Roles in Practice: Assuming and Federation
Roles are assumed via STS (sts:AssumeRole), which returns temporary credentials. This powers:
- Service access: an EC2 instance profile / Lambda execution role lets the workload call AWS with no stored keys.
- Cross-account: account B's role trusts account A, so A's principals can assume it — the standard multi-account pattern.
- Human federation: users log in via SSO/IAM Identity Center or an external IdP and assume roles — no IAM users, no personal access keys. This is the modern, recommended way for people to access AWS.
Best Practices
Least Privilege
Grant only the permissions actually needed, scoped to specific resources. Start restrictive and add as required — the opposite of the tempting "Action": "*", "Resource": "*" that turns every credential into account-admin. Use IAM Access Analyzer to find unused permissions and generate least-privilege policies from actual usage.
Kill Long-Lived Access Keys
Static IAM user access keys are the #1 AWS credential-leak vector (committed to git, embedded in code, left in CI). Replace them:
- Workloads → IAM roles (instance profiles, Lambda execution roles, IRSA for EKS).
- Humans → IAM Identity Center / federated SSO with temporary credentials.
- CI/CD → OIDC federation (e.g., GitHub Actions assuming a role) instead of stored keys.
If a workload has no access key to leak, that entire attack class disappears — this is the highest-leverage IAM improvement most accounts can make.
Enforce MFA and Guardrails
Require MFA for humans (especially privileged actions), use SCPs in AWS Organizations to set account-wide guardrails (block regions, prevent disabling logging, deny root usage), and set permission boundaries to cap what roles others create can grant. Lock down the root account entirely — MFA on it, no access keys, use only for the rare tasks that require it.
Audit Everything
Enable CloudTrail so every IAM action is logged, review with Access Analyzer, and rotate any remaining credentials. IAM without an audit trail is security theater — you can't detect misuse you don't log.
Common Mistakes
Wildcard Admin Everywhere
Attaching AdministratorAccess (or "*":"*" policies) to users, roles, and services "to make it work" means every credential is a master key. A leaked key or compromised service then owns the account. Scope permissions to what's needed — the extra effort is the entire point of IAM.
Long-Lived Access Keys in Code/CI
Access keys in source, config files, or CI variables get leaked constantly (public repos, logs, breaches). Use roles and OIDC federation so there are no static keys. If you find AKIA... in a codebase, that's an incident.
Confusing Identity-Based and Resource-Based Policies
Cross-account and service-to-service access often needs both an identity policy (the caller may act) and a resource policy (the resource permits the caller). "I granted the role S3 access but it's denied" is frequently a missing bucket policy — or vice versa.
Ignoring Explicit Deny and SCP Interactions
An Allow that "doesn't work" is usually blocked by an explicit Deny, an SCP guardrail, or a permission boundary higher up. Understand the evaluation order before debugging — the problem is often a ceiling you forgot about.
Neglecting the Root Account
The root user can do anything, including bypass some controls. Leaving it without MFA, with active access keys, or using it routinely is a critical risk. Secure it, remove its keys, and use IAM/Identity Center for daily work.
FAQ
What's the difference between an IAM user and a role?
A user is a persistent identity with long-lived credentials (password/access keys). A role is an assumable identity that grants temporary, auto-expiring credentials when assumed. Roles are preferred for nearly everything — services, cross-account access, and federated humans — because there's no permanent secret to leak. Modern AWS best practice minimizes users almost to zero.
How do I let an EC2 instance or Lambda access other AWS services?
Attach an IAM role (instance profile for EC2, execution role for Lambda). The workload automatically receives temporary credentials scoped to that role — no access keys to store or rotate. This is the correct, secure pattern; embedding access keys in the instance/function is the mistake to avoid.
Why is my request denied even though I have an Allow policy?
Check for: an explicit Deny somewhere (it overrides Allow), a Service Control Policy capping the account, a permission boundary on the role, or (for cross-account/resource access) a missing resource-based policy. IAM is deny-by-default with ceilings, so an Allow is necessary but not always sufficient.
What's the best way for my team to access AWS?
IAM Identity Center (formerly AWS SSO) or federation with your identity provider — humans log in through SSO and assume roles for temporary credentials, with MFA enforced. Avoid creating IAM users with personal access keys for people; it's harder to manage and far easier to leak.
How does IAM relate to zero trust?
IAM is AWS's implementation of zero-trust principles for cloud access: identity-based authorization on every request, least privilege, temporary credentials, and full auditing. Roles + short-lived credentials + tight policies are exactly the "never trust, always verify, grant the minimum" model applied to AWS.
Related Topics
- AWS — The provider overview
- AWS EC2 — A common consumer of IAM roles
- AWS Lambda — Uses execution roles
- SSO — Human access via IAM Identity Center / federation
- Zero Trust — The security model IAM implements
- Secrets Management — Reducing reliance on static credentials
- Azure Entra ID / Google Cloud IAM — Equivalents on other clouds