Cloud Tech by Victor
DevOpsIntermediate

AWS IAM

How IAM users, roles, and policies control access in AWS, why roles with temporary credentials are preferred over long-lived access keys, and how policy evaluation actually decides allow versus deny.

Updated 2026-07-243 min read

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

ConceptNatureTypical use
IAM userLong-term identity, own credentialsHuman console/CLI access, legacy programmatic access
IAM roleTemporary identity, assumed via STSWorkloads on EC2/Lambda/ECS, cross-account access
IAM groupCollection of users sharing policiesManaging permissions for many humans at once
IAM policyJSON permission documentAttached to a user, role, group, or resource

Syntax

bash
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-invocation

Examples

json
// 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.

Interview questions

What is the difference between an IAM user and an IAM role?

An IAM user is a long-term identity with its own credentials, a password for console access, or access keys for programmatic access, typically representing a human or a legacy application that needs standing access. An IAM role has no long-term credentials of its own; it is assumed, by a user, an application, or an AWS service, and yields short-lived, automatically-expiring temporary credentials via AWS STS. Roles are the preferred identity for anything running on AWS compute (EC2, Lambda, ECS) precisely because there is no long-lived secret to leak or rotate.

How does IAM decide whether a request is allowed, and what wins if there is a conflict between an Allow and a Deny?

IAM evaluation starts from an implicit deny, nothing is allowed unless some applicable policy explicitly allows it. It then checks every applicable policy for an explicit Deny; if one exists anywhere, identity-based, resource-based, a permission boundary, an SCP, or an RCP, the request is denied regardless of how many Allow statements also apply. Beyond that, identity-based and resource-based policies are the only policy types that actually grant permissions, an Allow from either is required. Permission boundaries, SCPs, and RCPs never grant anything themselves, they only cap what a grant can reach, so the request also needs every applicable one of those to independently allow the action; any one of them failing to allow it denies the request even with a valid Allow in place. An explicit Deny always wins over an explicit Allow, no matter which policy or how specific the Allow is.

Why are IAM roles preferred over long-lived access keys for workloads running on EC2 or Lambda?

A long-lived access key is a static secret that must be stored somewhere, an environment variable, a config file, a secrets manager, and rotated manually or via automation; if it leaks, it remains valid until someone notices and revokes it. An IAM role attached to an EC2 instance profile or a Lambda execution role is assumed automatically by the AWS SDK, yielding short-lived credentials that are rotated transparently and expire on their own even if never explicitly revoked. This removes the entire class of "leaked long-lived credential" risk for workloads that run on AWS compute, which is why roles are the default recommendation over access keys whenever the workload runs inside AWS.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement