Policies describe permissions
An IAM policy is JSON with Version: "2012-10-17" and one or more Statement objects. Each statement uses Effect, Action, and Resource, with optional Condition. Principal belongs in a resource-based policy or trust policy; an identity-based policy already knows its principal from the attachment.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::reports", "arn:aws:s3:::reports/*"],
"Condition": {"IpAddress": {"aws:SourceIp": "203.0.113.0/24"}}
}]
}
How AWS decides
flowchart LR
Q["Request<br/>s3:GetObject"] --> D{"Explicit Deny<br/>anywhere?"}
D -- "yes" --> X["Denied"]
D -- "no" --> A{"Explicit Allow?"}
A -- "yes" --> Y["Allowed"]
A -- "no" --> IMP["Implicit deny<br/>default"]
%% step Q: Every API call is evaluated against all policies that apply to it.
%% step D: Deny wins: one matching Deny statement in any policy blocks the request.
%% step X: The caller gets AccessDenied, whatever Allows exist elsewhere.
%% step A: Without a Deny, AWS looks for a statement that allows the action on the resource.
%% step Y: An explicit Allow grants access.
%% step IMP: With no matching Allow, the request is denied by default.
The default is implicit deny. An explicit Allow grants access, but an applicable explicit Deny overrides every Allow. A user receives permissions from groups, managed policies, and inline policies; group order does not decide the result.
Policy types
| Type | Attached to | Notes |
|---|---|---|
| AWS managed | Users, groups, roles | Written by AWS, e.g. ReadOnlyAccess; broad, updated by AWS |
| Customer managed | Users, groups, roles | Your own reusable policies; versioned |
| Inline | One identity | Deleted with the identity; use sparingly |
| Resource-based | A resource (S3 bucket, SQS queue, KMS key) | Has a Principal; can grant cross-account access |
| Permissions boundary | User or role | Caps the maximum permissions; grants nothing itself |
| SCP (AWS Organizations) | Account or OU | Guardrail on what any principal in the account can do |
An action is allowed only if every applicable layer allows it: for example an SCP and the identity policy must both permit it.
Writing narrow policies
Constrain Action and Resource. A * wildcard is convenient but frequently broader than necessary. Notice the S3 example needs two ARNs: ListBucket applies to the bucket, GetObject to the objects (/*). Conditions add context checks such as source IP, MFA (aws:MultiFactorAuthPresent), tags, or Region (aws:RequestedRegion).
Use IAM Access Analyzer to validate policy grammar and suggest fixes, and the policy simulator to test whether an action would be allowed before you deploy.
| Command | Purpose |
|---|---|
aws iam create-policy --policy-name s3-reports-read --policy-document file://policy.json |
Create a customer managed policy |
aws iam attach-group-policy ... |
Attach a managed policy to a group |
aws iam list-attached-user-policies ... |
View directly attached policies |
aws iam simulate-principal-policy ... |
Test an action against a principal's policies |
Traps: thinking a later or "higher" group overrides a Deny; using only bucket/* for ListBucket; adding Principal to an identity-based policy.