I had been reading IAM role definitions as though every policy attached to a role answered the same question:
What is this role allowed to do?
That worked until I encountered a role with two very different kinds of policy.
One policy mentioned cloud actions and resources:
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::bfstore-release-artifacts/*"
]
}
Another mentioned a principal and role assumption:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/BFStoreDeployPipeline"
},
"Action": "sts:AssumeRole"
}
Both belonged to the same role.
But they described different relationships.
The first policy said:
A session using this role
may read these release artefacts.
The second said:
This principal may request
a session using this role.
That distinction now feels foundational:
A trust policy controls who may obtain a session for the role. The role’s permissions policies define what that session may request, subject to the other policies and guardrails that apply.
One controls entry into the authority.
The other controls how that authority may be used.
A role has two sides
Suppose bfstore has a production deployment role:
BFStoreProductionDeploy
The role must answer two separate questions.
Who may assume it?
Perhaps only:
the protected production deployment pipeline
This belongs in the role’s trust policy.
What may its sessions do?
Perhaps they may:
update approved application workloads
read deployment metadata
inspect rollout status
This belongs in the role’s permissions policies.
The complete relationship is:
DEPLOYMENT PIPELINE
│
│ trusted to obtain a session
▼
PRODUCTION DEPLOY ROLE
│
│ permitted to request
▼
APPROVED DEPLOYMENT ACTIONS
The role is secure only when both sides are narrow.
NARROW TRUST
+
NARROW PERMISSIONS
=
CONSTRAINED AUTHORITY
Weakness on either side changes the risk.
Trust controls who can obtain the authority
A role’s trust policy is attached to the role itself.
In AWS, it is a resource-based policy whose protected resource is the role. Its central action is usually:
sts:AssumeRole
or another AWS Security Token Service operation used for federation.
A simplified trust policy might allow one role in another account:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/BFStoreDeployPipeline"
},
"Action": "sts:AssumeRole"
}
]
}
This policy does not say that the deployment pipeline may update Kubernetes workloads.
It says that the named principal may request temporary credentials representing:
BFStoreProductionDeploy
If assumption succeeds, AWS creates a role session. The permissions of that session are evaluated separately.
A trust policy can also name an account:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": "sts:AssumeRole"
}
Despite the root suffix, this identifies the AWS account as the principal. It does not restrict trust to that account’s root user.
Trusting an account can therefore make principals in that account eligible when their own permissions allow them to request the role. If only one deployment role needs access, naming that role creates a smaller trust boundary.
TRUST THE DEPLOYMENT PRINCIPAL
not
EVERY PRINCIPAL THAT MIGHT EXIST
IN THE DEPLOYMENT ACCOUNT
Permissions control what the session may request
A permissions policy attached to the role might allow:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:DescribeCluster"
],
"Resource": "arn:aws:eks:eu-west-2:444455556666:cluster/bfstore-prod"
}
]
}
Another statement might allow access to one deployment secret or object-store path.
These policies answer:
What API actions may a session
using this role request?
They do not answer:
Who may assume the role?
That remains the trust policy’s responsibility.
The permissions policy is also not the whole effective-permissions calculation. Resource policies, session policies, permissions boundaries, organisation guardrails and explicit denies may further constrain the request.
The useful distinction is still:
TRUST
who may obtain the session
ROLE PERMISSIONS
what that session is allowed to request
Both sides must be narrow
Suppose a role trusts every eligible principal in an external account but can read only one artefact path.
TRUST
entire external account
PERMISSIONS
read one artefact path
The immediate authority is limited, but the caller population is broader than necessary. More identities can attempt to enter the role, and future permission changes could make that exposure more serious.
Now reverse the design.
The role trusts only one protected pipeline, but its permissions allow:
Action:
*
Resource:
*
The front door is narrow.
The room behind it is enormous.
A compromised pipeline, unsafe workflow change or mistaken command now has account-wide consequences.
Trust precision does not compensate for excessive permissions, and narrow permissions do not justify unnecessary trust.
A practical review has several dimensions:
| Question | Security concern |
|---|---|
| Who may assume the role? | Trust exposure |
| What may the session request? | Permission exposure |
| Under which conditions? | Contextual exposure |
| How long does the session last? | Time exposure |
Role assumption may require permission on both sides
The trust policy is one part of role assumption.
In a typical cross-account design, the caller also needs an identity-based policy permitting:
sts:AssumeRole
against the target role.
Conceptually:
CALLER POLICY
I may request this role.
TARGET TRUST POLICY
This role accepts me.
If the caller is allowed to request the role but the target does not trust it, assumption fails.
If the target trusts the caller but the caller’s account does not permit the request, assumption also fails.
Within the same account, directly naming a principal in a role’s trust policy can sometimes grant assumption without a separate identity-based allow. Other controls and explicit denies may still prevent access.
The operational lesson is not that every design has identical policy requirements. It is that assumption must be authorised along the complete request path.
Assumption creates a new security identity
Suppose role A may assume role B.
ROLE A
│
▼
ROLE B
Role A does not silently inherit role B’s permissions while continuing to act as role A.
It must request a role B session:
ROLE A SESSION
│
│ AssumeRole
▼
ROLE B SESSION
│
▼
ROLE B PERMISSIONS
The resulting requests use role B’s authority, subject to the policies and constraints that apply to that session.
This matters for auditing. Activity should preserve enough information to identify:
the original principal
the assumed role
the session name
the requested action
Role assumption is a change in active security identity, not a policy copy-and-paste operation.
Service and federated trust need context
Some roles are intended for use by an AWS service.
For example, an EC2 role might trust:
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
The permissions policy might then allow a workload to read one application secret.
The service principal does not mean that every virtual machine in every account receives those permissions. The service’s association model and the surrounding request context still matter.
This leads to a broader review question:
How does AWS know that this trusted service is acting for the intended bfstore resource?
Where the service supports them, trust conditions can restrict context such as:
expected source account
expected source resource
expected organisation
expected external relationship
Without those checks, a trusted service can become a confused deputy. The service itself is legitimate, but another party may try to direct it towards the role.
Third-party access has a similar problem. A provider’s AWS account may be trusted only when it supplies a bfstore-specific external ID. The external ID does not replace narrow role permissions. It narrows the circumstances under which the provider may obtain the session.
Federated workloads
For OpenID Connect federation, the trust policy names an identity provider and evaluates token claims.
Conceptually:
WORKLOAD
│
▼
OIDC TOKEN
│
▼
ROLE TRUST POLICY
│
▼
TEMPORARY ROLE SESSION
Useful claims may identify:
issuer
audience
repository
approved environment
approved branch or ref
workload identity
The exact claims depend on the provider and the subject format it issues.
For a GitHub Actions deployment, a role might trust:
repository:
mantrobuslawal/bfstore-platform
and either:
environment:
production
or:
ref:
main
With GitHub’s default subject formats, an environment-based subject and a branch-based subject are different forms. A policy should match the actual token claims rather than assume every value appears together.
A Kubernetes workload follows the same pattern. The trust relationship might admit only:
cluster:
bfstore-prod
namespace:
application
service account:
catalog
The role permissions might allow only:
read bfstore/prod/catalog/database
Trust narrows which workload may enter.
Permissions narrow what the resulting session may do.
Other controls answer other questions
Trust and role permissions are central, but they are not the complete privilege path.
Who may modify trust?
An engineer who cannot assume a production administrator role may still be able to update its trust policy.
They add their own principal:
BEFORE
engineer not trusted
TRUST POLICY CHANGE
engineer added
AFTER
engineer may assume administrator role
The ability to modify trust is therefore highly privileged.
The same is true of permissions that can attach policies, create identity providers, change boundaries or create new credentials. IAM administration can create indirect routes to authority even when direct access is denied.
Who may pass the role?
Suppose a deployment identity cannot read production secrets directly but can attach any role to a workload it controls.
It attaches:
SecretAdministrator
The workload then reads the secrets.
DEPLOYMENT IDENTITY
│
│ passes powerful role
▼
CONTROLLED WORKLOAD
│
▼
PRODUCTION SECRET
The ability to pass or associate a role must be restricted to approved roles and services.
This control is not expressed solely through the target role’s trust policy or permissions policy. It is another link in the complete privilege path.
Which ceilings apply?
A permissions boundary sets the maximum permissions that identity-based policies can grant to a role or user.
It does not establish trust.
It does not grant permissions by itself.
TRUST POLICY
who may obtain a role session
PERMISSIONS POLICY
what identity-based permissions request
PERMISSIONS BOUNDARY
maximum identity-based permissions
An organisation policy such as an AWS Organizations service control policy answers another question:
Which actions remain unavailable
inside this account or organisation?
It does not make an external principal trusted. A caller can satisfy a trust policy and still be blocked by an organisation-level deny.
Resource policies may also participate in access decisions for the target resource.
Debugging therefore requires checking the layer that controls the failed transition.
A practical role review
Suppose I am reviewing:
BFStoreProductionDeploy
Reading only its permissions policies might reveal:
update production workloads
read release artefacts
inspect deployment status
That tells me the consequences of obtaining the role.
It does not tell me who can obtain it or how that access might be created indirectly.
A complete review follows the access path.
Entry
Which exact principals are trusted?
Is an account trusted where one role would suffice?
Which token subjects or service contexts are accepted?
How long may the session last?
Authority
Which actions and resources are allowed?
Can conditions narrow access?
Which wildcards include future actions or resources?
Which operations can destroy data or infrastructure?
Delegation
Who may modify the trust policy?
Who may attach permissions?
Who may pass the role to a service?
Can the role create or modify more powerful identities?
Constraints and evidence
Which boundaries and organisation guardrails apply?
Which resource policies participate?
Can assumption be traced to the original person or workload?
Which granted permissions are actually used?
A role is not adequately reviewed by reading whichever policy document contains the most dramatic action names.
A bfstore cross-account example
Suppose the bfstore shared-services account runs the infrastructure deployment system.
Production resources live in a separate account.
SHARED-SERVICES ACCOUNT
Terraform deployment runner
PRODUCTION ACCOUNT
bfstore production resources
The production account contains:
BFStoreProductionTerraformApply
Its trust policy names only the approved runner role:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/BFStoreTerraformRunner"
},
"Action": "sts:AssumeRole"
}
]
}
The runner has permission to request that specific target:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::444455556666:role/BFStoreProductionTerraformApply"
}
The production role may manage the approved infrastructure scope, but cannot:
modify organisation guardrails
read unrelated security secrets
administer identity federation
delete protected backup vaults
Additional boundaries and organisation policies provide ceilings:
TRUST
approved shared-services runner
ROLE PERMISSIONS
approved production infrastructure
BOUNDARY
no unrestricted IAM administration
ORGANISATION GUARDRAIL
no disabling central audit or protected backups
Each control answers a different question.
Together, they form the access path.
Trust policy and permissions policy compared
| Question | Trust policy | Permissions policy |
|---|---|---|
| Primary purpose | Controls who may obtain a role session | Defines what the role session may request |
| Protected relationship | Caller to role | Role session to actions and resources |
| Common principal field | Identifies trusted principals | Usually absent from a policy attached to the role |
| Common action | sts:AssumeRole or a federation equivalent |
AWS service API actions |
| Typical resource | The role receiving trust | Buckets, queues, databases, clusters and other resources |
| Common failure | Wrong caller can obtain the role | Valid caller receives excessive authority |
| Useful conditions | Token subject, audience, source, service or external ID | Tags, resource scope, region, transport or network context |
| Review question | Who can get in, and under what conditions? | What can the session reach after entering? |
The compact version is:
TRUST POLICY
Who may wear the uniform?
PERMISSIONS POLICY
What may someone wearing it do?
The uniform does not select its wearer.
The wearer does not invent the uniform’s authority.
The mental model I am keeping
My earlier model was:
ROLE POLICY
everything that controls the role
The stronger model is:
ORIGINAL PRINCIPAL
│
▼
TRUST POLICY
│
may this principal obtain a session?
│
▼
ROLE SESSION
│
▼
PERMISSIONS POLICIES
│
what may this session request?
│
▼
TARGET RESOURCE
Other controls still matter.
Caller permissions may authorise the request.
Resource policies may participate at the target.
Boundaries and organisation guardrails may reduce the available authority.
Audit records should connect the role session to the original person or workload.
A trust policy and a permissions policy may both contain Allow, but they allow different transitions.
The trust policy allows a recognised principal to obtain the role’s temporary credentials.
The permissions policies allow the resulting session to request actions against resources.
So when I review a role now, I no longer ask only:
What can this role do?
I ask two questions in order:
Who can obtain a session for it?
What can that session do?
The first protects the keys.
The second limits which locks they open.
References and further reading
AWS IAM roles Introduces IAM roles, trust policies, permissions policies and temporary role sessions.
Updating a role trust policy Explains how trust policies identify the principals allowed to assume a role.
AWS Security Token Service Documents the temporary-security-credential operations used for role assumption and federation.
AWS IAM policy evaluation logic Explains how identity policies, resource policies, permissions boundaries and organisation policies contribute to effective access.
AWS IAM policy principals Explains account principals, role principals, role sessions and service principals.
AWS IAM permissions boundaries Explains how permissions boundaries limit permissions granted by identity-based policies.
AWS IAM OIDC federation Documents trusting external OpenID Connect identity providers and evaluating token claims.
GitHub Actions OpenID Connect Explains the token claims and subject formats available to GitHub Actions workflows.
AWS confused deputy prevention Explains source conditions and external IDs used to narrow delegated trust relationships.