AWS IAM Policy Structure: Effect, Action, Resource, and Condition Explained
Understanding AWS IAM policy structure is essential for every engineer who needs to grant or restrict access without creating security gaps or silent permission failures.
TL;DR: IAM Policy Elements at a Glance
| Element | What It Controls | Valid Values | Required? |
|---|---|---|---|
Effect |
Whether the statement permits or denies | Allow or Deny |
Yes |
Action |
Which API operations are covered | Service prefix + action name (e.g., s3:GetObject) |
Yes |
Resource |
Which AWS resources the statement applies to | ARN, ARN with wildcard, or * |
Yes |
Condition |
Contextual constraints that must be satisfied | Condition operator + key + value | No |
What Is an AWS IAM Policy? A Direct Answer
An IAM policy is a JSON document that defines permissions. Each policy contains one or more statements, and each statement is the atomic unit of permission logic. A statement answers four questions: should this be allowed or denied (Effect), what operations (Action), on what resources (Resource), and under what circumstances (Condition). AWS evaluates all applicable statements together using a defined precedence model before granting or denying a request.
IAM Policy Evaluation Flow
Before examining each element individually, it helps to see how AWS processes a policy statement end-to-end. The diagram below shows the evaluation path from an incoming API call to a final access decision.
- API call arrives — AWS identifies the caller's identity and the requested action.
- Explicit Deny check — If any policy attached to the principal contains an explicit
Denyfor this action and resource, access is denied immediately. No other statement can override this. - Explicit Allow check — AWS looks for at least one
Allowstatement covering the action and resource. If aConditionis present, it must also evaluate to true. - Implicit Deny — If no explicit
Allowis found, access is denied by default. IAM is deny-by-default.
Element 1: Effect — The Binary Decision Gate
Effect accepts exactly two values: Allow or Deny. This is the only element with no ambiguity. An explicit Deny in any attached policy — identity-based, resource-based, permissions boundary, or SCP — overrides any number of Allow statements for the same action and resource. Engineers who rely solely on identity-based Allow policies without auditing resource-based policies or SCPs frequently encounter unexpected access denials in multi-account environments.
{
"Effect": "Deny",
"Action": "s3:DeleteBucket",
"Resource": "*"
}
Explicit Deny is absolute — it cannot be overridden by any Allow, including those granted by an administrator.
Element 2: Action — Mapping to AWS API Operations
The Action element specifies one or more AWS API operations the statement covers. Each action follows the format service-prefix:OperationName, where the service prefix matches the namespace defined in the AWS Service Authorization Reference. Wildcards are supported: s3:Get* matches all S3 read operations, and * alone matches every action across every service.
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
]
}
Notice that s3:ListBucket applies to the bucket ARN itself, while s3:GetObject applies to objects inside it. Mixing these up is one of the most common causes of AccessDenied errors when an application can authenticate but cannot list or read objects.
Think of the Action element as a whitelist of verbs. The service prefix is the noun category — you cannot use an EC2 prefix to authorize an S3 operation, regardless of how the resource ARN is written.
Element 3: Resource — Scoping the IAM Policy Structure to Specific ARNs
The Resource element defines which AWS resources the statement applies to, expressed as ARNs. A value of * means all resources. Partial wildcards within an ARN (e.g., arn:aws:s3:::logs-*) are also valid and useful for environment-based scoping.
Critical constraint: not all IAM actions support resource-level restrictions. Certain List and Describe actions — such as s3:ListAllMyBuckets or ec2:DescribeInstances — require "Resource": "*" because they operate at the account or region scope, not on a specific resource. If you specify a concrete ARN for one of these actions, the policy will not grant the permission and the caller will receive an AccessDenied error, even though the action appears to be allowed. Always verify resource-level support in the Service Authorization Reference before scoping to a specific ARN.
- Resource-level supported — The action can be restricted to a specific ARN (e.g.,
s3:GetObjectonarn:aws:s3:::example-bucket/*). - Resource-level NOT supported — The action requires
"Resource": "*". Specifying a concrete ARN here causes anAccessDeniederror at runtime, not a policy validation error. This failure is silent during policy creation. - Wildcard scoping — Partial ARN wildcards (e.g.,
arn:aws:s3:::prod-*) are valid where resource-level restrictions are supported.
In practice, teams often write overly broad "Resource": "*" for every action to avoid AccessDenied errors, then never revisit the policy. The correct approach is to check the Service Authorization Reference once per action and apply the narrowest valid scope.
Element 4: Condition — Contextual Constraints in IAM Policy Structure
The Condition element is optional but powerful. It adds contextual constraints that must evaluate to true for the statement to apply. A condition block contains one or more condition operators, each mapping one or more condition keys to expected values.
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::example-bucket/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
},
"Bool": {
"aws:SecureTransport": "true"
}
}
}
This statement allows s3:GetObject only when the request originates from us-east-1 AND uses HTTPS. Multiple conditions within the same block are evaluated with a logical AND. Multiple values within a single condition key are evaluated with a logical OR.
Condition keys fall into two categories: global condition keys (prefixed aws:, available for all services) and service-specific condition keys (prefixed with the service namespace, e.g., s3:prefix). Not every condition key is available for every action — verify availability in the Service Authorization Reference before relying on a condition key in a production policy.
Putting It Together: A Complete IAM Policy Example
The following policy demonstrates all four elements working together. It allows an application role to read objects from a specific S3 bucket, but only over HTTPS and only from the us-east-1 region. It also contains an explicit Deny for delete operations, which cannot be overridden.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSecureRead",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "true"
},
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
},
{
"Sid": "DenyDelete",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::example-bucket/*"
}
]
}
Two separate ARNs are required for s3:ListBucket and s3:GetObject because they operate at different resource scopes — bucket and object respectively. Combining them into a single ARN with /* would cause s3:ListBucket to fail with AccessDenied.
Diagnosing IAM Policy Structure Failures in Production
Step 1: Use IAM Policy Simulator to isolate the denying policy
Production Gotcha: An engineer sees AccessDenied on s3:ListBucket. They verify the identity policy contains an explicit Allow for s3:ListBucket with the correct bucket ARN. The diagnosis stops there — but the actual cause is a Service Control Policy at the organization level that restricts S3 actions to specific regions, and the request is coming from eu-west-1.
— Why this step: AccessDenied errors do not identify which policy layer caused the denial, so the simulator is the only tool that surfaces the denying policy without manual inspection of every attached policy.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/ExampleRole \
--action-names s3:ListBucket \
--resource-arns arn:aws:s3:::example-bucket
Step 2: Verify resource-level support for the failing action
— Why this step: specifying a concrete ARN for an action that requires "Resource": "*" produces an AccessDenied error at runtime with no policy validation warning — the policy saves successfully but never grants the permission.
Check the Service Authorization Reference for the action in question. If the "Resource types" column shows only *, the action does not support resource-level restrictions. Correct the policy by replacing the specific ARN with "Resource": "*" for that action, ideally in a separate statement scoped only to that action.
aws iam get-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/ExamplePolicy \
--version-id v1
Step 3: Check for explicit Deny statements across all attached policies
— Why this step: an explicit Deny in any attached policy — including resource-based policies, permissions boundaries, and SCPs — overrides any Allow, and the simulator output will identify the source policy only if all policy types are included in the simulation.
aws iam list-attached-role-policies \
--role-name ExampleRole
aws iam list-role-policies \
--role-name ExampleRole
Run both commands. Inline policies and managed policies are stored separately and both must be reviewed. A Deny in an inline policy is just as absolute as one in a managed policy.
IAM Policy Structure: Glossary
- Statement
- The atomic unit of an IAM policy. A policy document contains one or more statements in the
Statementarray. - Effect
- Specifies whether the statement results in an
AlloworDeny. ExplicitDenyalways takes precedence. - Action
- One or more AWS API operations in
service-prefix:OperationNameformat. Wildcards are supported. - Resource
- The ARN or ARN pattern identifying which resources the statement applies to. Some actions require
*and do not support specific ARNs. - Condition
- Optional contextual constraints using condition operators, global keys (
aws:), or service-specific keys. Multiple conditions in a block are AND-evaluated. - Condition Operator
- The comparison function applied to a condition key and value (e.g.,
StringEquals,Bool,ArnLike). - Global Condition Key
- Condition keys prefixed with
aws:that are available across all AWS services (e.g.,aws:SecureTransport,aws:RequestedRegion). - Implicit Deny
- The default outcome when no explicit
Allowmatches a request. IAM is deny-by-default — absence of an Allow is a denial. - Resource-Level Permission
- The ability to restrict an IAM action to a specific ARN rather than
*. Not all actions support this — verify in the Service Authorization Reference. - SCP (Service Control Policy)
- An AWS Organizations policy that sets maximum permission boundaries for accounts. An SCP
Denyoverrides identity-basedAllowpolicies in the affected account.
Comments
Post a Comment