Skip to content

Identity, Permission, Trust and Resource Policies

graph LR subgraph Identity["Identity-based policies"] U[IAM user] G[IAM group] IBP[Identity-based policy] U -->|attached to| IBP G -->|attached to| IBP end subgraph Role["Role policies"] R[IAM role] TP[Trust policy] PP[Permission policy] R -->|has one| TP R -->|has one or more| PP TP -->|defines who may assume the role| R PP -->|defines what the role may do| R end subgraph Resource["Resource-based policies"] S3[S3 bucket] RBP[Resource-based policy] S3 -->|has| RBP RBP -->|controls access to| S3 end IBP -->|can permit assuming| R R -->|can access| S3 RBP -->|can grant direct access to| U

Four kinds of policy appear in a typical design, and confusing them is the usual reason an access decision is not what was expected.

Attached to IAM users, groups or roles. They define what those identities may do across AWS services.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Environment": "Production"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances"
],
"Resource": "*"
}
]
}

Attached to a user or group, this permits starting and stopping instances tagged Environment=Production, and describing all of them. ec2:DescribeInstances is in a separate statement because it does not support resource-level permissions or the resource tag condition — a detail that catches people out when a single-statement version silently denies everything.

Every IAM role carries two kinds of policy, doing different jobs.

The IAM console showing a role's Trust relationships tab, with the trusted entities JSON document listing a Statement whose Principal is a Service and whose Action is sts:AssumeRole.
A role's trust policy in the IAM console, on the Trust relationships tab.
  • Defines who may assume the role
  • Sometimes called the trust relationship policy
  • Uses the sts:AssumeRole action (or sts:AssumeRoleWithSAML / sts:AssumeRoleWithWebIdentity for federation)
  • Must include a Principal element
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
  • Define what the role may do
  • List allowed or denied actions and resources
  • No Principal element; the principal is the role itself
  • Several can be attached to one role, and they combine
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}

The short version: the trust policy answers who may use this role, the permission policy answers what this role may do. Both are required. A role with no trust policy cannot be created; a role with no permission policy can be assumed but can do nothing.

Attached to a resource — an S3 bucket, an SQS queue, an SNS topic, a KMS key, a Lambda function — rather than to an identity. A resource-based policy must declare a Principal saying who may access the resource.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/CrossAccountRole"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}

A role’s trust policy is itself a resource-based policy — the resource being the role.

A realistic arrangement uses several at once:

  1. A developer has an identity-based policy permitting them to assume particular roles.
  2. Those roles have permission policies defining which services and resources they reach.
  3. Some of the target resources have resource-based policies that allow access from those roles, particularly where the resource is in another account.

The request succeeds only where the identity-based policy allows it, no explicit deny applies anywhere, and — for a cross-account call — the resource’s own policy accepts the caller. See IAM policy types and evaluation for the full evaluation logic.