All notes

A trust policy answers a different question from a permission policy

A role's trust policy controls who may obtain a session for it. Its permissions policies control what that session may request. Secure role design requires both questions to be answered narrowly.

about 17 minutes min read

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.