What is an IAM policy - the JSON that grants everything
An IAM policy is a JSON document that tells AWS what a person or a system is allowed to do — and to what. That's it. No agent, no daemon, no background process "enforcing" anything in real time on a server somewhere. Every time someone clicks a button in the AWS console or runs a command from a laptop, AWS reads the JSON attached to that person and answers one question: does this document say yes? The counterintuitive part is this: the policy that hands over total control of an entire AWS account — every service, every resource, delete included — is one of the shortest policies AWS has ever published. It is shorter than the policy that only lets someone look at a list of storage buckets.
Jake found this out the hard way on a Tuesday. He runs a small phone shop, and he'd asked a cousin who "knows computers" to set up an AWS account so the shop's repair-tracking app could store customer photos. The cousin got stuck on a permissions error, searched for a fix, found a forum post, and pasted three lines of JSON into the console to make the red text go away. It worked. It also meant that the app's login now had the same power as Jake himself — the power to delete every file, spin up servers, and rack up a bill, with nothing standing between "logged in" and "everything."
What an IAM policy actually is..
IAM stands for Identity and Access Management — it's the part of AWS whose only job is deciding who you are and what you're allowed to touch. AWS's own documentation puts it simply: a policy is an object that, when attached to an identity or a resource, defines its permissions. AWS checks these documents every time a request is made — by a person, an application, or another AWS service — and the permissions inside decide whether the request is allowed or denied.
An analogy Jake actually used later, once Ethan had walked him through it: think of a policy as a laminated card taped to the inside of a supply closet door. The card doesn't lock anything by itself. It's a list — "Maria can take batteries and screens. Tom can take batteries only. Nobody takes the register key." Every time someone reaches for something, whoever's watching the closet checks the card. AWS is the person watching the closet, and the policy is the card.
♂️ Jake's Reality Check
"So if it's just a text file, why does everyone act like it's some deep, scary AWS mystery?"
Because the file is simple, but the consequences of getting it wrong aren't. A typo in a document doesn't usually delete a customer's database. A typo in this one can.
Two things make IAM policies different from a normal permissions checkbox in, say, a shared Google Drive folder. First, they're written in JSON — a plain-text format built from key-value pairs inside curly braces, the same format a lot of software uses to pass data around. You don't need to know how to code to read one; you just need to know that "key": "value" means "this setting equals this." Second, a policy by itself does nothing. It has to be attached — to a user, a group, a role, or in some cases directly to a resource like a storage bucket — before AWS pays any attention to it. A policy sitting in your account unattached to anything is inert. It's the card taped to nobody's door.
The reason this concept exists at all is that AWS accounts are, by default, locked down hard. When you create a new IAM user or role, it starts with essentially nothing — it can't view a single resource until a policy says it can. That default-deny stance is deliberate: AWS would rather you explicitly grant access than accidentally leave a door open. Policies are how you open exactly the doors you mean to, and nothing more.
The anatomy of a policy: every element, explained
Here's a real, complete policy — small enough to read in one glance, and it does something genuinely useful: it lets someone see the names of every storage bucket in the account, and nothing else.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListAllMyBuckets",
"Resource": "*"
}
]
}
Every element in that block has a job. Going through them one at a time, in the order most people actually encounter them:
Version
This isn't the version of your policy — it's the version of the policy language itself, and AWS documentation is blunt about it: there are only two values that ever go here, 2012-10-17 and an older 2008-10-17. You should always use 2012-10-17. It's the current version, and it's what unlocks newer features like policy variables — placeholders such as ${aws:username} that get swapped for the real value at request time. Use the old 2008 version and those variables stop being treated as variables; AWS reads them as literal, meaningless text instead.
What changed between versions
- Before (2008-10-17): the older policy language. You'll still see this on policies written years ago that nobody has touched since.
- Now (2012-10-17): the current, recommended version — it's required for policy variables to actually work as variables.
- What that means for you: if you ever open an old policy and see the 2008 date, that alone is a signal it's due for a review, not necessarily a rewrite.
Statement
This is the container for the actual rules. A policy can have one statement or dozens; each one is its own self-contained allow-or-deny rule. AWS documentation notes that the order the elements appear in doesn't matter inside a statement — you could write Resource before Action and nothing changes. What matters is which elements are present, not the order they're typed in.
Sid (optional)
A "statement ID" — a label you give a statement purely so a human reading the policy later knows what it's for, like "AllowBucketListing." AWS ignores it functionally; it's a sticky note, not a rule. If you skip it, nothing breaks — it just makes a long policy harder to skim six months from now.
Effect
Only two legal values: Allow or Deny. This is the entire verdict of the statement — every other element in the statement just decides when that verdict applies. There's no "maybe," no "ask the user," no partial grant — a statement always resolves to one of these two words.
Action
The specific operation being allowed or denied, written as service:ActionName — s3:ListAllMyBuckets, ec2:StartInstances, iam:CreateUser. You can list several, and you can use the asterisk (*) as a wildcard to mean "any action starting with this." s3:Get* covers every S3 action that starts with "Get." A bare * with no service prefix means literally every action, in every service, that AWS has ever published or will ever publish — including services that don't exist yet.
Resource
What the action applies to, specified as an ARN (Amazon Resource Name) — a structured address like arn:aws:s3:::my-bucket. AWS documentation is explicit that the ARN format varies by service, but the concept is constant: it's how you say "this action, but only against this one specific thing," instead of every thing that action could ever touch. Wildcards work here too, within a segment of the ARN — and AWS's own guidance recommends using them inside colon-separated segments rather than across them, since a wildcard at the very end of a segment can accidentally expand further than intended.
Condition (optional)
The part almost nobody uses until they need it badly. A Condition adds an "only if" to a statement, comparing something about the request — the requester's IP address, the time, whether they logged in with multi-factor authentication — against a value you set, using a condition operator. AWS documents several families of these operators: string operators like StringEquals for an exact, case-sensitive match; StringEqualsIgnoreCase when case shouldn't matter; and negated versions like StringNotEquals for the opposite. AWS's own example shows the plain version: "Condition": {"StringEquals": {"aws:username": "john"}} only matches a user actually named "john" — a user named "John" is denied, because the comparison is case-sensitive by default.
AWS's documentation gives a more concrete real-world example too: a policy can allow a user to deactivate their own MFA device, but only if they signed in with MFA within the last hour, checked using a condition on aws:MultiFactorAuthAge being under 3,600 seconds. Conditions are how "Allow" stops being all-or-nothing, and they're the single most underused tool in an ordinary policy-writer's toolkit.
Principal (only in some policies)
This one doesn't appear in a policy attached directly to a user — it appears in resource-based policies, where you're not saying "here's what I can do," you're saying "here's who's allowed to touch me." A Principal names the account, user, or role the statement applies to. An S3 bucket policy, for instance, uses Principal to say which outside AWS account is allowed to read its contents.
✅ Why this is the one to use
Always write "Version": "2012-10-17", never omit Effect, and always be as specific as the task allows in Action and Resource. A policy that names exact actions against exact ARNs is not more "advanced" than a wildcard one — it's just the version that doesn't wake you up at 3am.
The JSON that grants everything
Here it is, in full, exactly as AWS publishes it as a standalone managed policy called AdministratorAccess:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
That's the entire policy. One statement. Allow, every action, every resource. AWS's own description of it is equally blunt: it provides full access to AWS services and resources, and its reference documentation states plainly that it grants all actions for all AWS services and all resources in the account. There's no Condition, no Principal restriction, nothing narrowing it. If your account has this policy attached to you, there is not a single AWS action — create, read, update, delete, in any service that exists today or gets released next month — that this policy blocks.
π‘Recommended AWS Foundations Reading, incase if you are interested..
Master the core building blocks of AWS infrastructure, networking, and security:
- πΊ️ Regions vs. Availability Zones — The physical map that fixes half of your latency and redundancy bugs.
- π Decoding AWS ARNs — How to read the exact address syntax for any resource across accounts and regions.
- π‘️ Shared Responsibility Model — Who fixes what when an AWS service fails or leaks.
- π Public vs. Private Subnets — Visualizing traffic flow and network isolation in a VPC.
- πͺ Internet Gateway vs. NAT Gateway — Outbound internet routing without hidden cost surprises.
- π§± AWS Security Groups — Stateful firewall rules explained in plain English.
- π Service-Linked Roles — The permissions AWS automatically creates and manages for you.
- π₯️ EC2 Instance Profiles — How EC2 instances access services without hardcoded access keys.
- π¨ Root User vs. IAM User — Why you should lock away root credentials immediately.
- π AWS MFA Setup Guide — Clear the console nagging banner and secure your account in 3 minutes.
The asterisk in Action and the asterisk in Resource are doing separate jobs, and it's worth being precise about that because people often only notice one of them. The Action asterisk means "every operation." The Resource asterisk means "against everything that exists in the account." Either one alone would already be dangerous. Together, they're the entire account, undefended, forever — or at least until someone removes the policy.
⚠️ What this actually breaks
Someone with AdministratorAccess can create new IAM users with the same policy, delete the account's CloudTrail activity logs, disable MFA on other users, and empty every S3 bucket and database in the account — in whatever order they like, with no built-in delay or confirmation step. It's not "strong permissions." It's the absence of any ceiling at all.
Ethan put it to Jake this way: "It's not that AdministratorAccess is a bad policy — it's that calling it a 'policy' undersells what it is. Every other policy is a fence with a specific gate in it. This one is just a field with no fence." Jake pushed back: "But somebody in the account has to have that much power, right? Somebody has to be able to do everything." Ethan didn't disagree — he just narrowed it: "Sure. One or two people, for account-level setup and true emergencies, not the login your app uses to save a photo."
There's a related, smaller policy worth knowing about because people confuse the two: PowerUserAccess. It grants broad access to create and manage almost every AWS resource, but it deliberately carves out IAM itself — someone with PowerUserAccess can spin up servers and databases all day, but they can't create new users, change other people's permissions, or touch the account's Organizations settings. It's the difference between "can do almost anything to the resources" and "can also redefine who's allowed to do anything, including granting themselves more."
Identity-based vs. resource-based: two policies, one decision
Every policy you'll meet falls into one of two families, and the difference is simply where the JSON is attached.
An identity-based policy is attached to a person or a role — a user, a group of users, or a role that an application or service assumes. It answers "what is this identity allowed to do?" This is the kind covered in the anatomy section above, and the kind you'll write the most often.
A resource-based policy is attached directly to a resource instead — an S3 bucket, for example — and it answers a different question: "who is allowed to reach this specific thing?" That's why resource-based policies need the Principal element and identity-based ones usually don't: the resource has to name who it's letting in, because there's no identity attached to ask "what can I do" in the first place.
Here's the part that trips people up: when a request comes from an IAM user or role inside the same AWS account as the resource, AWS doesn't just check one policy and stop. Both apply, and according to AWS's own evaluation-logic documentation, the resulting permissions are the union of the two — if either the identity-based policy or the resource-based policy allows the action, AWS allows it. An explicit deny in either one overrides that allow. So a bucket policy can grant access that a user's own policy never mentioned, and a user's own policy can grant access a bucket policy never mentioned — either is enough, unless something explicitly says no.
| Policy type | Attached to | Answers | Needs Principal? |
|---|---|---|---|
| Identity-based | User, group, or role | What can this identity do? | No |
| Resource-based | A resource itself (e.g. a bucket) | Who can reach this resource? | Yes |
There are two other layers worth knowing exist, even before you write a single policy of your own: permissions boundaries and service control policies (SCPs). Neither one grants anything by itself — think of them as ceilings rather than doors. Covered fully a couple of sections down.
AWS managed, customer managed, and inline: which one to use
Within identity-based policies, there are three ways a policy can exist, and AWS documentation lays out the decision plainly.
AWS managed policies are written and maintained by AWS itself. AdministratorAccess is one. So are dozens of narrower ones built for specific services. They're standalone objects with their own ARN (like arn:aws:iam::aws:policy/IAMReadOnlyAccess), meaning you can attach the same one to many different users or roles at once. The upside is convenience — someone else already wrote it. The downside, and AWS says this about its own policies without any hedging: they're built for general use across every AWS customer, so they might not match least-privilege for your specific situation. You can't edit an AWS managed policy either; if AWS updates it, every identity it's attached to inherits the change automatically, whether you wanted that change or not.
Customer managed policies are also standalone, reusable objects — but you write and own them. AWS's own recommendation is to start by copying an existing AWS managed policy and then trim it down to exactly what your situation needs, rather than writing from a blank page. Customer managed policies also come with version history, so you can roll back a bad edit instead of trying to remember what the previous JSON looked like.
Inline policies are the odd one out: instead of being a separate object, an inline policy is embedded directly inside one specific user, group, or role. It has no ARN of its own, it can't be attached to anything else, and it's deleted the instant the identity it lives inside is deleted. AWS's guidance is straightforward: if a policy might apply to more than one identity, use a managed policy; inline policies are for the rare case where a permission is genuinely one-off and shouldn't outlive, or exist independently of, the specific identity it's tied to.
| Type | Who edits it | Reusable across identities? | Has version history? |
|---|---|---|---|
| AWS managed | AWS only | Yes | No (you can't edit it) |
| Customer managed | You | Yes | Yes |
| Inline | You | No — one identity only | No |
We used to tell readers, in an older post on this site, to "just attach an inline policy, it's faster." That's true for a one-off test account. It's genuinely bad advice for anything a second person will ever touch, because an inline policy is invisible to the "who has this permission" question — you can't search for it as a standalone object the way you can a managed policy. If more than one identity might ever need the same access, a customer managed policy is almost always the better default, and we were wrong to gloss over that trade-off.
How AWS actually decides: Allow, Deny, or nothing at all
This is the section that explains almost every "but I definitely allowed this" support ticket. AWS's evaluation logic runs on one absolute rule, stated in the enforcement documentation without exception: an explicit deny always overrides an explicit allow. It doesn't matter how many policies say yes. One policy saying no, anywhere in the chain that applies to the request, wins.
The second rule is quieter but just as important: by default, every request is denied. AWS calls this an implicit deny. Permission has to be granted; it is never assumed. So there are really three possible outcomes for any single request, not two:
- Explicit deny — some applicable policy says "Deny" for this action. Request denied, full stop, nothing overrides it.
- Explicit allow, no deny anywhere — at least one applicable policy says "Allow," and nothing says "Deny." Request allowed.
- Implicit deny — nothing says yes and nothing says no; the action was simply never granted. Request denied, by default, because that's IAM's starting position for everything.
Where this gets genuinely confusing is once you add permissions boundaries and organization-wide Service Control Policies (SCPs) into the mix, because they don't behave like ordinary policies — they behave like ceilings. AWS's documentation is precise about the math: when a permissions boundary is set on a user, the resulting permission is the intersection of the identity-based policy and the boundary, not the union. Same with SCPs at the organization level — an action has to be allowed by both the identity-based policy and the SCP for it to actually happen.
♂️ Jake's Reality Check
"So I could give someone AdministratorAccess and they still couldn't do something?"
Yes. If your AWS account belongs to an organization and a Service Control Policy at the organization level blocks an action, AdministratorAccess doesn't override that SCP. The SCP isn't a grant — it's a lid on the whole account, and AdministratorAccess only fills the space underneath the lid.
SCPs deserve their own callout because they're the most commonly misunderstood object in this whole picture. AWS Organizations' own syntax guide makes a point of correcting a natural assumption: an SCP statement that says "Effect": "Allow", "Action": "s3:*", "Resource": "*" looks exactly like an identity-based policy, but inside an SCP it does not actually grant anyone permission to do anything. SCPs act purely as filters that cap the maximum permissions available in an account or organizational unit — even a user with AdministratorAccess attached is limited to whatever the SCP still permits.
Permissions boundaries and SCPs: the other ceilings
A permissions boundary is a managed policy that you attach to a single user or role for one purpose only: capping what its own identity-based policies are allowed to grant. Ethan's way of explaining it to Jake: "It's like handing an employee a company credit card that's been set to a $500 monthly limit at the bank, separate from whatever the store's own return policy says they can spend. Two limits, and you're bound by whichever one is stricter." A user could have AdministratorAccess attached directly and still be capped hard by a permissions boundary that only allows read access — because the effective permission is the overlap between the two, not either one alone.
A Service Control Policy works at a higher altitude — across an entire AWS account, or a whole group of accounts inside AWS Organizations. It never grants anything on its own; it exists purely to say "no matter what any policy inside these accounts claims, nothing here is allowed to go past this line." This is why a company might let individual account owners hand out AdministratorAccess freely inside their own account, while an SCP at the top quietly guarantees nobody, anywhere in the organization, can touch billing settings or disable the security logging.
Writing your first policy instead of copy-pasting AdministratorAccess
The honest reason people reach for AdministratorAccess isn't laziness — it's that a permission error is frustrating, the fix is right there, and it works. But "it works" and "it's scoped to what you actually need" are different things, and closing that gap doesn't take long once you know the shape of it.
- Name the exact task, not the exact person. Not "what does Jake need" but "what does the repair-tracking app actually do" — in this case, upload a photo to one specific S3 bucket, and read it back. That's it.
- Find the real action names. For an upload-and-read task on S3, that's
s3:PutObjectands3:GetObject— not a wildcard, nots3:*, just the two verbs the app actually calls. - Scope the Resource to the one bucket, not every bucket. An ARN like
arn:aws:s3:::shop-repair-photos/*instead of a bare*. This single change is the difference between "this app can touch its own folder" and "this app can touch every bucket in the account, including ones that don't exist yet." - Set Effect to Allow and leave Deny out unless you have a specific reason to explicitly block something — remember, everything is implicitly denied already, so you rarely need to write a Deny by hand for a narrow, single-purpose policy.
- Paste it into the JSON editor in the IAM console, or use the visual policy editor if you'd rather build it by clicking through drop-downs of services and actions instead of typing braces by hand — both produce the same document.
- Attach it to a role, not a person, whenever the thing doing the work is an application or a service — a role can be assumed temporarily and doesn't carry a permanent password or access key the way a user can.
✅ Why this is the one to use
A narrow, purpose-built customer managed policy attached to a role beats AdministratorAccess for almost every real workload, because the blast radius of a mistake — a leaked key, a bug, a compromised laptop — is limited to exactly what that policy names, and nothing else.
The size limits nobody warns you about
JSON policies aren't infinitely long, and this catches people mid-project more often than any other quota in IAM. AWS's own quota reference lists hard character limits that can't be raised with a support ticket, on top of separate adjustable quotas that can.
| Limit | Value | Can it be raised? |
|---|---|---|
| Managed policy size | 6,144 characters | No — hard limit |
| Inline policy, per user (total) | 2,048 characters | No — hard limit |
| Managed policies attached per user | 10 (default) | Yes, up to 20 |
| Managed policies attached per role | 10 (default) | Yes, up to 25 |
One detail from the AWS quota documentation that surprises people: IAM does not count white space when measuring a policy against these limits. Adding line breaks and indentation to make a policy readable to a human costs nothing against the quota — it's purely the meaningful characters that count. If you're bumping into the 6,144-character ceiling on a managed policy, the standard workaround is to split it into several smaller managed policies and attach more than one, rather than trying to compress the JSON by stripping whitespace, since the whitespace was never counted in the first place.
There's a related, separate limit that only shows up when an application assumes a role through AWS STS and passes along session tags or an inline session policy: the "packed policy size." AWS's own troubleshooting guidance explains that session tags and inline session policies are serialized and compressed into the session token, and the token itself has a finite size limit — regardless of how many individual session tag or policy quotas you're technically still under. If you exceed it, the AssumeRole call fails outright, and AWS's CloudTrail logs for that call record the exact PackedPolicySize percentage that was used, which is the fastest way to confirm this is what happened rather than guessing.
Common mistakes that break policies — or worse, don't
Some mistakes throw an obvious error the moment you try to save. Others save perfectly fine and quietly grant far more than intended — those are the dangerous ones, because nothing tells you it happened.
| Mistake | What actually happens |
|---|---|
Using both Action and NotAction in one statement | Rejected outright — AWS documentation lists these as mutually exclusive; the policy won't save. |
Using both Principal and NotPrincipal | Also mutually exclusive — same result, rejected at save time. |
Leaving Resource as a bare * "just for now" | Saves fine. Silently grants the action against every resource in the account, forever, until someone remembers to narrow it. |
| Forgetting a Condition on a sensitive Deny statement | Saves fine. A Deny meant to only apply "without MFA" instead applies to everyone, including people who did use MFA — locking out the wrong group. |
| Attaching a permissions boundary to a user who already has broad policies | Saves fine, and can silently shrink what that user can do — AWS's own documentation flags this directly: adding a boundary to an existing user might reduce the actions they can perform. |
Mixing the 2008-10-17 Version with policy variables like ${aws:username} | Saves fine. The variable is treated as a literal string instead of being substituted, so the policy quietly matches the wrong thing, or nothing at all. |
Assuming a StringEquals condition is case-insensitive | Saves fine. A user named "John" is silently excluded from a condition written for "john," because StringEquals is case-sensitive by default — use StringEqualsIgnoreCase if that's not what you meant. |
The popular advice you'll see repeated everywhere — "if it's not working, just widen the policy until it does" — is exactly backwards, and it's worth saying plainly: that approach trades a visible, annoying error for an invisible, much larger risk. A denied request tells you something. An over-broad Allow tells you nothing, right up until it's used for something you didn't intend.
Tools that write and check the policy for you
You don't have to build every policy from a blank text editor, and AWS has spent years building tools specifically because writing precise JSON by hand doesn't scale.
IAM policy validation runs automatically the moment you create or edit a policy in the console — it checks for plain JSON syntax errors before you can even save. Layered on top of that, IAM Access Analyzer adds a second pass of checks with specific, actionable recommendations for tightening a policy further.
The more powerful feature is generating a policy from real activity. Point Access Analyzer at an existing user or role, give it a time window of up to 90 days, and it reads that identity's actual AWS CloudTrail history — the log of every action it genuinely performed — and produces a policy template containing only the services and actions that were actually used. This is the practical way most teams walk an over-permissioned role back down to size: give a test account broad access temporarily, have someone use it exactly the way production will, then let Access Analyzer read the trail and hand you a policy scoped to what actually happened, rather than what might theoretically be needed one day.
♂️ Jake's Reality Check
"Can't it just guess what my app needs without me watching it run first?"
No, and this is a real limit worth naming. Access Analyzer only knows what already happened in CloudTrail. If a task only runs once a quarter and your analysis window doesn't cover that quarter, the generated policy won't include it — you have to run the workload through its full range of behavior first, or check back after it has.
There's a fourth tool worth knowing about specifically: the IAM policy simulator, a dedicated console separate from the main IAM screen, reachable at its own URL. It lets you test identity-based policies, permissions boundaries, resource-based policies, and organization SCPs against a chosen action and resource, without actually sending the request to a live AWS service — so you can check "would this be allowed" before you attach anything. The one honest caveat AWS's own documentation gives about it: simulator results can differ from your live AWS environment, so it recommends confirming against your real account after simulating, rather than treating the simulator's answer as the final word.
Managing policies at scale instead of clicking through the console
One policy, attached to one role, is easy to manage by hand in the console. A dozen applications each needing their own scoped permissions is not — and this is the adjacent task most readers run into thirty seconds after they've written their first policy: how do you keep track of all of them without it turning into an unmanaged pile of JSON nobody remembers the reasoning for?
AWS's own answer is to define the policy as part of your infrastructure instead of clicking it into existence. AWS CloudFormation, AWS's infrastructure-as-code service, has a dedicated resource type for exactly this: AWS::IAM::ManagedPolicy. You describe the policy's PolicyDocument, along with which users, groups, or roles it should attach to, inside a template written in JSON or YAML, and CloudFormation creates the policy and wires it up the same way the console would — except now it's version-controlled, reviewable, and repeatable across environments. AWS's own documentation on the resource notes a detail worth knowing if you're troubleshooting a stack deletion: if another resource depends on a role that this managed policy is also attached to, you need an explicit DependsOn so CloudFormation deletes things in the right order and doesn't leave the role's dependent resource stranded mid-teardown.
The payoff isn't abstract. A policy defined in a template gets reviewed by a second person before it ships, the same way application code does, and a bad Resource wildcard shows up as a change in a pull request instead of a silent click in a console that nobody else sees. For a shop like Jake's with one app and one bucket, this is overkill. For a growing environment with more than a handful of roles, it's usually the difference between "we know exactly what every identity can do" and "we're not entirely sure anymore."
When the policy doesn't work: troubleshooting, in order
An "AccessDenied" error is one of the most common IAM messages, and the instinct to just widen permissions until it goes away is the wrong first move. Work down this list instead — cheapest and most informative checks first.
- Confirm the policy is actually attached to the right identity. A perfectly correct policy that's sitting unattached, or attached to the wrong user, does nothing. This sounds obvious and is still the single most common cause.
- Check for a Deny anywhere in the chain — the identity's own policies, any permissions boundary, and (if the account belongs to an organization) any Service Control Policy above it. Remember: one Deny anywhere wins, no matter how many Allows exist elsewhere.
- Check whether an SCP is filtering the account entirely, independent of the user's own permissions. If your account is part of an AWS Organization, ask whoever manages it whether an SCP restricts this action — AdministratorAccess can't override an organization-level SCP.
- Re-read the Action name for a typo. A misspelled service prefix or action name —
s2:GetObjectinstead ofs3:GetObject— simply never matches anything, and the request falls through to the default implicit deny with no error pointing at the typo itself. - Check the Resource ARN format for the specific service you're calling, since ARN structure varies service to service; a Resource that's shaped correctly for one service but pasted into a policy for a different one won't match.
- If it's a resource-based policy, confirm the Principal is correct — a resource-based policy with the wrong account ID or user ARN in Principal simply won't recognize the requester at all.
- Run the exact request through the IAM policy simulator before assuming anything else is wrong. It will tell you, statement by statement, which policy is producing the Allow, Deny, or implicit-deny result — far faster than re-reading JSON by eye a fourth time.
If a request is still denied after all seven of those check out clean, the honest next step is one this post can't shortcut for you: because simulator results can differ from a live account, confirm the fix against your actual AWS environment, not just the simulator's verdict. Nobody in this chain can see your account's specific policies from the outside, so past a certain point, checking directly beats guessing every time.
Root user, break-glass admins, and other edge cases
A question that comes up constantly once someone understands AdministratorAccess: is the AWS account's root user the same thing? It isn't, and the distinction matters. Root is the identity created the moment you first sign up for AWS — it's not an IAM user at all, and no IAM policy is what makes root powerful. Root's authority is built into the account itself and can't be fully restricted by a policy the way a normal user's can. That's exactly why the standard advice everywhere is to avoid using root for daily work: create an IAM user or role with AdministratorAccess (or something narrower) for actual admin tasks, and keep root locked away, MFA-protected, for the handful of account-level actions that genuinely require it.
That leads to the "break-glass admin" pattern some teams use for real emergencies — a stranded EC2 instance whose owning role expired overnight, say. Rather than granting broad access permanently to whoever might need it, a small number of admin credentials with AdministratorAccess are created, kept sealed, and used only when something urgent needs fixing outside normal channels — then rotated or deleted once the emergency passes. It's the same principle as a fire extinguisher behind glass: available, but deliberately inconvenient to reach for casually.
Another edge case: session policies. When an application assumes a role temporarily through AWS STS, it can pass along an additional inline policy that further restricts what that specific session is allowed to do — on top of, never instead of, the role's own permissions. These session policies count against the packed-policy-size limit described earlier, and if the combination of tags and inline session data is too large, the request to assume the role fails outright rather than silently truncating the policy.
Worth naming plainly, since it's the kind of gap that only shows up once someone's actually relying on a tool: automated checks like the policy simulator and Access Analyzer are genuinely useful, but neither one replaces reading the JSON yourself before it goes anywhere near a real account. Treat their output as a second opinion, not a signature of approval.
Frequently asked questions
What is an IAM policy in the simplest possible terms?
It's a JSON document that says who's allowed to do what to which AWS resources. Attach it to a user, group, or role, and AWS reads it every time that identity makes a request. On its own, sitting unattached, it does nothing at all.
What does the "Version" field in a policy actually do?
It sets which version of the policy language itself is used — always 2012-10-17 for new policies. It has nothing to do with tracking your own edits over time; that's what a customer managed policy's version history does separately, and the two shouldn't be confused.
What's the difference between an IAM policy and an IAM role?
A policy is the rules document. A role is an identity that policies get attached to — one designed to be temporarily assumed by a person, an application, or another AWS service, rather than owned permanently the way a user is. A policy without a role or user to attach to does nothing; a role without any policy attached can't do anything either.
Can one IAM policy be attached to more than one user?
Yes, if it's an AWS managed or customer managed policy — those are standalone objects designed to be reused across many users, groups, and roles. An inline policy can't; it's embedded in exactly one identity and disappears with it.
What happens if two policies conflict?
An explicit Deny in any applicable policy always wins over an explicit Allow anywhere else, no matter how many policies allow the action. If nothing explicitly denies or allows an action, it's denied by default — there's no tie-breaking vote involved.
Why does AWS deny me even though my policy clearly says Allow?
Something else in the chain is either denying it explicitly or narrowing it down to nothing — a permissions boundary intersecting your permissions down, a Service Control Policy capping the whole account, or a typo in the Action or Resource that means the Allow you wrote never actually matches the request you're making.
What's the difference between identity-based and resource-based policies?
Identity-based policies are attached to a user, group, or role and describe what that identity can do. Resource-based policies are attached to the resource itself — like an S3 bucket — and describe who's allowed to reach it, which is why they need a Principal element that identity-based policies usually don't.
Is AdministratorAccess the same as root user access?
No. AdministratorAccess is an IAM policy you attach to a user or role. The root user's power comes from the account itself, not from any policy, and root can't be fully restricted the same way a policy restricts an ordinary IAM identity. Best practice is to lock root away and do daily admin work through an IAM identity instead.
How many policies can I attach to one user?
By default, up to 10 managed policies per user, adjustable up to a maximum of 20. Roles default to 10 as well, adjustable up to 25. Inline policies don't count against that limit, but they have their own character-size ceiling instead.
What's the maximum size of an IAM policy?
A managed policy is capped at 6,144 characters. An inline policy attached to a single user is capped at 2,048 characters total across all of that user's inline policies combined. Neither of these hard limits can be raised through a support request; the workaround is splitting a large managed policy into several smaller ones.
Can I write an IAM policy without typing raw JSON?
Yes — the IAM console has a visual policy editor that lets you pick a service, check off actions from a list, and specify resources through drop-downs and search fields, generating the equivalent JSON behind the scenes. You can switch back and forth between the visual view and the raw JSON view for the same policy at any time.
What does "NotAction" do, and why would anyone use it?
It's the inverse of Action — instead of listing what's allowed, it lists what's excluded, with everything else implied. It's mutually exclusive with Action in the same statement, and it's mostly used in narrow, specific cases where it's genuinely easier to say "everything except these three things" than to enumerate every action you do want.
How do I find out what a policy actually lets someone do, without reading raw JSON line by line?
The IAM console generates a plain-language policy summary next to the JSON view, breaking permissions down by service and by access level (like "Write" or "List"). For a deeper check, IAM Access Analyzer's policy validation adds specific findings and recommendations on top of that summary, and the IAM policy simulator can test a specific action directly.
What's a permissions boundary, and how is it different from a regular policy?
A permissions boundary is itself just a managed policy, but it's used in a special role: capping the maximum permissions a user or role's own identity-based policies are allowed to grant. The effective permission is the overlap between the two, not the sum — a boundary can shrink what an identity can do even if its attached policies say otherwise.
Should I ever use AdministratorAccess in production?
For a small number of genuine account administrators, sometimes yes. For application roles, automated pipelines, or day-to-day developer accounts, essentially never — a policy scoped to the specific actions and resources that workload actually touches keeps the damage from any single mistake or leaked credential contained to exactly that scope.
How do I undo giving someone too much access?
Detach the overly broad managed policy from the user, group, or role, and attach a narrower customer managed policy instead — or use IAM Access Analyzer to generate that narrower policy automatically from the identity's actual CloudTrail activity over a chosen time window, so you're replacing the broad grant with exactly what's been used rather than guessing.
Also Read:
AWS IAM Policy Size Limit Exceeded: 7 Fixes + Calculator
AWS IAM: iam:PassRole Access Denied - The Complete Guide to Fixing the Most Confusing Error
AWS IAM: Leaked Access Key - First 30 Minutes Response Guide
AWS IAM: Lost MFA Device - Complete Recovery Guide
AWS IAM MalformedPolicyDocument: 12 Causes & Fixes
Revision note. Written September 2026,.This will need a fresh look if AWS ever revises the policy language version or changes the managed-policy size ceiling. If a permissions error sent you here at midnight, take a breath — the fix is almost always smaller and safer than the three lines everyone reaches for first.Happy learning!