What is an IAM policy?
Put simply, an IAM policy is a document (usually written in JSON) that describes who can perform which operations, on which resources, and under what conditions. It is a set of rules that define permissions — nothing more, nothing less.
AWS Identity and Access Management (IAM) is the service that controls access to resources in Amazon's cloud. Its central building block is exactly this — the policy. According to the AWS documentation, a policy is an object that, when associated with an identity (a user, group, or role) or a resource, defines their permissions. When a principal sends a request to AWS, the service evaluates the applicable policies and decides whether to allow or deny the operation.
It helps to state upfront what IAM is not. It is not a network firewall or an encryption mechanism. It is an authorization layer ("who is allowed to perform this action on this resource") that runs after authentication ("who is this in the first place"). Policies themselves store no data — they describe rules. Most of them are JSON documents with a strictly defined structure.
Here is what the simplest policy looks like in practice:
This policy allows reading all objects in the photos bucket — and nothing else. A key property: a permission applies regardless of how the operation is performed. If a policy allows the GetUser action, the user can invoke it through the AWS Console, the CLI, or the API — the rule is identical.
Who is behind it?
IAM is an Amazon Web Services offering, provided at no additional charge as part of the AWS platform. The permission model is built on a policy language whose current version is labeled 2012-10-17 — this is the language version, not the launch date of the service, and AWS still recommends using it today. The entire mechanism described here comes directly from the official AWS IAM documentation.
How does it work?
The logic of evaluating a request looks simple but has several layers. It runs in three steps: authenticating the principal, gathering the request context (which action, which resource, which conditions), and the actual policy evaluation.
After authentication, AWS holds four elements describing the request — and only then does it run policy evaluation:
- Principal — the authenticated identity behind the request: an IAM user, role, account or AWS service.
- Action — the specific API operation the principal is asking for, e.g.
s3:GetObjectorec2:StartInstances. - Resource — the resource (or resources) the action targets, identified by an ARN.
- Context — extra request attributes checked via condition keys: IP address, time, MFA presence, region or transport encryption.
The foundation is the default deny rule: if no policy explicitly allows an action, the request is rejected. For an operation to succeed, there must be an explicit allow. Above everything sits the explicit deny — if any applicable policy contains a Deny, the request is blocked no matter how many allows exist. This is a hierarchy without exceptions: deny always beats allow.
Once several policy types are involved, the logic grows more nuanced. Identity-based?Identity-based policies: Policies attached to an IAM identity — a user, group or role. They grant it permissions. and resource-based?Resource-based policies: Policies attached directly to a resource (e.g. an S3 bucket). They define who may perform which actions on it. policies combine as a union — an action is allowed if either one permits it. By contrast, permissions boundaries, SCP?SCP: Service Control Policy — an AWS Organizations policy that sets the maximum permissions for accounts in the organization and RCP?RCP: Resource Control Policy — the counterpart of an SCP, applied at the resource level rather than the identity level policies combine as an intersection: the action must be allowed by all applicable layers at once. This is spelled out on the policy evaluation logic page.
An example makes it concrete. When every layer allows:
But it takes only one of the intersection layers to deny:
A single Deny in the SCP cancels every allow — that is what "intersection" means in practice.
A typical authorization flow
In practice, the whole path — from the application to the decision — looks like this:
What are its key components?
A policy JSON document is built from statements. Each element of the Statement array describes one independent permission rule. A single statement contains a set of fields:
- Version — the policy language version (recommended:
2012-10-17). - Effect —
AlloworDeny. - Action — a list of actions. Each AWS service defines its own set of actions, e.g.
s3:GetObject,ec2:StartInstances, ordynamodb:PutItem. - Resource — the resources the actions apply to, identified by an ARN?ARN: Amazon Resource Name — a unique identifier for a resource in AWS. Not all actions let you point to a specific resource — some require
"*". This does not automatically mean access to everything: for many actions AWS simply does not allow a more granular resource to be specified. - Principal — states who is granted permissions: an AWS account, an IAM user, an IAM role, or an AWS service. Required in resource-based policies, and omitted in identity-based ones, where the principal is implied.
- Condition — optional constraints based on so-called condition keys, e.g.
aws:SourceIp,aws:CurrentTime,aws:PrincipalArn,aws:RequestedRegion, oraws:SecureTransport. They let you narrow access — for example to a specific IP address or to encrypted connections only.
A rule in practice: a statement with the condition "Bool": {"aws:MultiFactorAuthPresent": "true"} takes effect only when the user has signed in with a second authentication factor. If the condition is not met, the entire statement is skipped.
When a policy holds multiple statements — or when multiple policies are attached to a principal — AWS evaluates all applicable statements. A single matching Allow is enough for the action to be permitted — unless a matching Deny appears first, which always takes precedence. AWS recommends splitting permissions into separate functional policies (for example, S3 management apart from user management) rather than one overloaded document.
How to read an IAM policy
You can read a simple policy by asking five questions in order:
| Field | Question | Example |
|---|---|---|
| Effect | Allow or deny? | Allow / Deny |
| Action | What are we doing? | s3:GetObject |
| Resource | On what? | resource ARN |
| Principal | Who? | resource-based policies only |
| Condition | When / under what condition? | e.g. MFA, IP address |
What can it be used for?
IAM policies govern essentially every access in AWS. In practice you meet a few main types.
Identity-based policies
They attach to users, groups, and roles. They come in two kinds:
- Managed — reusable; they include AWS-provided policies and your own customer-managed ones.
- Inline — bound to a single identity and deleted along with it.
Resource-based policies
These attach directly to a resource — the most common examples are Amazon S3 bucket policies and IAM role trust policies. The latter does not define what a role may do, but who may assume it (the sts:AssumeRole action) — one of the most commonly confused distinctions in IAM. Resource-based policies also enable cross-account access: the Principal element can name an account or entity in another AWS account.
The restricting layers
These are higher-level controls that grant nothing and only narrow permissions:
- Permissions boundaries — set the ceiling that an identity-based policy can grant to an entity.
- SCPs and RCPs — guardrails in AWS Organizations, applied across an entire organization or organizational unit (OU?OU: Organizational Unit — a group of accounts in AWS Organizations that can share common policies (e.g. SCPs).):
- SCP (Service Control Policy) — the maximum permissions for identities (users and roles) in the organization's accounts.
- RCP (Resource Control Policy) — an equivalent ceiling, but applied to resources.
- Session policies — passed when temporarily assuming a role through STS?STS: AWS Security Token Service — the service that issues temporary access credentials — they can only narrow the permissions the role grants, never widen them.
How does it differ from other approaches?
The most common misunderstanding concerns the difference between policies that grant permissions and those that restrict them. Only identity-based and resource-based policies actually grant access. Permissions boundaries, SCPs, and RCPs never grant anything — they only narrow what other policies have granted. This distinction is the source of many misconfigurations: an admin adds an SCP and is puzzled that a user can do nothing, because a granting policy is missing.
The second axis is how policies combine — union versus intersection. The table sums it up:
| Policy type | Grants or restricts? | How it combines |
|---|---|---|
| Identity-based | grants | union |
| Resource-based | grants | union |
| Permissions boundary | restricts | intersection |
| SCP / RCP | restricts | intersection |
| Session | restricts | intersection |
In the intersection model, every added layer can only take permissions away, never add them.
It's also worth separating policies from access control lists (ACLs) — the only policy type in AWS that does not use JSON syntax, used for cross-account access (for example, in older S3 configurations). For most new deployments, AWS recommends resource-based policies over ACLs.
Key limitations and challenges
Policies have physical size limits, so complex permissions often must be split across several documents. The bigger challenge, though, is the model's own complexity: with several overlapping layers (identity-based + resource-based + boundary + SCP + session), working out the actual, effective permissions can be far from trivial. It's easy to fall into either of two opposite errors — over-permissioning (a security risk) or over-restricting (breaking functionality).
AWS provides tools to ease this. IAM Access Analyzer offers more than 100 policy validation checks and can generate a proposed least-privilege policy based on real activity recorded in CloudTrail?CloudTrail: an AWS service that records the history of API calls made in an account. "Last accessed" information helps remove unused permissions. Even so, responsibility for correct configuration rests with the team — it is part of the shared responsibility model.
The root account is a special case: by default it has full access to the account and IAM policies do not restrict it — you cannot attach an identity-based policy to it or set a permissions boundary for it. It is, however, still subject to SCPs and RCPs within an organization. That's why AWS recommends using root access only in exceptional cases.
Why does it matter?
IAM is, in practice, the single most important security layer in AWS — and at the same time the most frequent source of incidents. Most public cloud data leaks stem not from "hacking" in the classic sense, but from access granted too broadly: an open bucket, a role with * permissions, an access key that should never have existed. Grasping that deny always beats allow, that an SCP grants nothing, and that effective access is the intersection of many layers translates directly into whether an environment is secure.
For a junior stepping into the cloud, this is foundational knowledge. AWS consistently promotes the principle of least privilege and a preference for temporary credentials (roles) over long-term access keys — both recommendations follow directly from the IAM security best practices guide. Mastering the policy language is therefore not a technical detail but a precondition for using the cloud responsibly.
IAM policies are not a standalone product but the shared permission language of all of AWS. A handful of concepts — allow, deny, principal, action, resource, condition — and a few evaluation rules are enough to describe access to almost any service. It is a rare case where a small, coherent model drives an enormous, complex system.
