Overview
IAM is the identity and access control system underneath every AWS API call: users are long-term identities typically used by humans, roles are assumed identities that yield short-lived credentials and are the preferred mechanism for workloads, and policies are JSON documents that define what an identity or resource can do. Every request starts from an implicit deny, and an explicit Deny found anywhere in the applicable policies always wins over an explicit Allow, no matter how specific or how many Allow statements also apply. Getting comfortable with that evaluation order, and with reaching for roles instead of long-lived access keys, is most of what building securely on AWS actually comes down to.
Quick Reference
| Concept | Nature | Typical use |
|---|---|---|
| IAM user | Long-term identity, own credentials | Human console/CLI access, legacy programmatic access |
| IAM role | Temporary identity, assumed via STS | Workloads on EC2/Lambda/ECS, cross-account access |
| IAM group | Collection of users sharing policies | Managing permissions for many humans at once |
| IAM policy | JSON permission document | Attached to a user, role, group, or resource |
Syntax
aws iam create-role \
--role-name lambda-orders-exec \
--assume-role-policy-document file://trust-policy.json
aws sts assume-role \
--role-arn arn:aws:iam::111122223333:role/lambda-orders-exec \
--role-session-name orders-invocationExamples
// s3-read-orders-bucket.json - this statement grants access only to
// objects in orders-prod-bucket; other applicable policies may grant
// or restrict access to other resources independently of this one.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::orders-prod-bucket/*"
}
]
}An explicit Deny always wins
It doesn't matter how specific or recent an Allow statement is, or which policy it lives in, identity policy, resource policy, permission boundary, or SCP. If any applicable policy contains an explicit Deny that matches the request, the request is denied.
Visual Diagram
Common Mistakes
- Creating IAM users with long-lived access keys for a workload running on EC2 or Lambda, instead of attaching a role that yields short-lived credentials automatically.
- Writing policies with
"Action": "*"or"Resource": "*"out of convenience, instead of scoping to the specific actions and ARNs the identity actually needs. - Attaching policies directly to individual users one at a time instead of managing permissions through groups (for humans) or roles (for workloads), making access hard to audit at scale.
- Forgetting that an explicit Deny anywhere, including in an SCP, overrides every Allow, and spending time debugging the wrong policy as a result.
Performance
- IAM policy evaluation happens on the request path but is effectively instantaneous; the real cost of a large policy set is audit and maintenance complexity, not runtime latency.
- Assuming a role via STS adds a small round trip the first time, but AWS SDKs cache and automatically refresh the resulting temporary credentials, so steady-state overhead is negligible.
Best Practices
- Default to IAM roles, not IAM users with static access keys, for anything running on AWS compute: EC2 instance profiles, Lambda execution roles, ECS task roles.
- Scope policies to specific actions and resource ARNs, adding conditions where useful, rather than reaching for wildcards.
- Manage human permissions through IAM groups so access can be audited and changed in one place instead of per user.
- Use permission boundaries or SCPs as an organization-wide ceiling on top of individual policies, rather than relying on individual policy authors to always get scope right.
- Periodically audit and remove unused long-lived access keys; IAM Access Analyzer and credential reports both surface exactly this.