---
key: "cloud-iam-trust"
title: "Review cloud permissions and role trust together"
url: "https://softwaresecurity.com/domains/runtime/cloud-posture/cloud-iam-trust/"
api: "https://softwaresecurity.com/api/v1/articles/cloud-iam-trust.json"
domain: "runtime"
topic: "Runtime, cloud \u0026 APIs"
type: "Guide"
identifiers: ["CWE-863","CWE-284","A02:2025","A01:2025"]
tags: ["Access control","Workload identity","CI/CD security","Cloud security","Infrastructure as code"]
stages: ["release","operate"]
roles: ["developer","platform-sre"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Review cloud permissions and role trust together

Distinguish who can obtain a cloud identity from what that identity can do, then test the effective access path.

Canonical URL: https://softwaresecurity.com/domains/runtime/cloud-posture/cloud-iam-trust/

## In short

Review who can obtain a cloud identity separately from what it can do. For CI federation, trust one issuer and require the exact audience and the exact subject, such as `repo:company/reports:environment:production`, never a prefix like `repo:company/*`. Grant only the actions and resources the job uses, rely on explicit denies for guardrails, and add `sts:ExternalId` or `aws:SourceArn` conditions against confused deputies. Then treat everyone who can change the trusted pipeline as holding the role.

- A narrow permission policy is useless if the trust policy lets the wrong workload obtain the role, and the reverse is equally true.
- GitHub uses one issuer for every repository, so the subject condition is what limits trust to your repository and environment.
- An explicit deny, such as an SCP, wins over any allow in AWS policy evaluation.
- Checkov failed `repo:*` and a subject without a repository but passed the organization-wide `repo:company/*`; review subjects yourself.
- Anyone who can change the trusted workflow can use the role without touching IAM, so protect the pipeline like the role.

## Recommended defaults

| Setting | Value | Note | Source |
| --- | --- | --- | --- |
| AWS trust: `token.actions.githubusercontent.com:aud` | `StringEquals` `sts.amazonaws.com` | — | AWS IAM: OIDC federation roles |
| AWS trust: `token.actions.githubusercontent.com:sub` | `StringEquals` one exact subject, such as `repo:ORG/REPO:environment:NAME` | — | GitHub Docs: OpenID Connect |
| Google Cloud provider `attribute_condition` | Required for GitHub; use the exact subject or at least the owner | — | Google Cloud: workload identity federation with deployment pipelines |
| Entra ID federated credential | Issuer `https://token.actions.githubusercontent.com`, exact subject, audience `api://AzureADTokenExchange` | — | Microsoft Entra: workload identity federation |
| Third-party role trust | `sts:ExternalId` condition with the value the vendor issues to you | — | AWS IAM: the confused deputy problem |
| AWS service principal access | `aws:SourceArn` and `aws:SourceAccount` conditions | — | AWS IAM: the confused deputy problem |

## Verify it

1. Run Checkov with `CKV_AWS_358`, `CKV_AWS_393`, `CKV_GCP_125` and `CKV_AZURE_249` on the Terraform, and also read every subject condition, because the checks accept organization-wide subjects.
2. From a staging job, try to assume the production role and expect `AccessDenied`; from production, expect success.
3. As the role, write under `north/`, then try `south/` and a delete; expect success, then two denials, and check that the delete names the SCP in its error.
4. List who can push to the trusted workflow, approve its environment and edit the role, and record them as holders of the role.
5. Simulate the permissions with the IAM policy simulator, then confirm with one real call per decision.

## What you will learn

- Evaluate credential acquisition separately from use
- Find the effect of an explicit deny in the supplied policy model
- Recognize indirect privilege through policy/deployment administration

Audience: Platform and cloud engineers who write role trust policies and workload identity federation for CI pipelines and services

Before you start:

- Distinguish a role trust policy, identity policy, resource policy and organizational guardrail.

Time: About 25 minutes: 6 to read and 19 for the exercise

## A useful permission needs an authorized way to acquire it

A role may have a narrow resource policy but an overly broad trust relationship, allowing an unintended workload to obtain its credentials. Conversely, a carefully restricted trust policy can lead to a role that has excessive authority. Review identity acquisition and resource access as separate, connected decisions.

Cloud providers express these decisions differently. AWS role trust and permission policies, Azure identities and role assignments, and Google Cloud principals and IAM bindings are not interchangeable configuration formats. Begin with one provider and a concrete task, such as allowing a production export worker to write to its designated object prefix.

## Worked access graph: a narrow role with a broad entrance

Start with two edges: workload → role, then role → resource action. In this example a production invoice exporter should assume `ExportNorth` and write objects under `north/`. A staging runner should obtain neither that role nor its production capability. The role name and a successful test under production do not constrain staging. The trust policy must enforce the intended workload identity using claims the configured issuer authenticates.

The Terraform below states the whole model for AWS. GitHub Actions signs every job's token with the same issuer, `https://token.actions.githubusercontent.com`, so trusting the issuer alone would admit every repository on GitHub. The trust policy therefore requires the audience `sts.amazonaws.com` and the exact subject `repo:company/reports:environment:production` with `StringEquals`; a prefix such as `repo:company/*` would admit every repository in the organization. Subject formats depend on the provider and on how the job runs, so check the claims your jobs actually present.

On the use side, compare action, resource and applicable conditions. The role may only `PutObject` under `exports/north/`, and an organization SCP explicitly denies `DeleteObject` on every export object; real AWS evaluation can also involve resource policies, session policies and permissions boundaries. Here `PutObject` `north/a.csv` is allowed, `GetObject` is not granted, and `DeleteObject` is explicitly denied. Adding an Allow for `DeleteObject` does not override that explicit deny. The configuration passed `terraform validate` and the four Checkov trust checks.

Finally review who can alter either edge. Someone allowed to change a trusted pipeline or deploy arbitrary code under its service account may gain its role without modifying IAM. Someone allowed to update `ExportNorth`’s permissions can expand its capability. These are administration paths, not harmless metadata rights. Record them as privileged access and protect their change process and credentials accordingly; the GitHub Actions hardening guide at /domains/supply-chain/ci-cd/github-actions-hardening/ covers the workflow side.

```hcl
# GitHub Actions jobs in the production environment of company/reports, and nothing else,
# may assume ExportNorth, which can only write objects under exports/north/.
data "aws_caller_identity" "current" {}

resource "aws_iam_openid_connect_provider" "github" {
  url            = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
}

data "aws_iam_policy_document" "export_north_trust" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/token.actions.githubusercontent.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:company/reports:environment:production"]
    }
  }
}

resource "aws_iam_role" "export_north" {
  name                 = "ExportNorth"
  assume_role_policy   = data.aws_iam_policy_document.export_north_trust.json
  max_session_duration = 3600
}

data "aws_iam_policy_document" "export_north" {
  statement {
    actions   = ["s3:PutObject"]
    resources = ["arn:aws:s3:::exports/north/*"]
  }
}

resource "aws_iam_role_policy" "export_north" {
  role   = aws_iam_role.export_north.id
  policy = data.aws_iam_policy_document.export_north.json
}

# Organization guardrail: no principal in member accounts may delete export objects,
# whatever its own policies allow. SCPs do not restrict the management account.
data "aws_iam_policy_document" "deny_export_delete" {
  statement {
    effect    = "Deny"
    actions   = ["s3:DeleteObject"]
    resources = ["arn:aws:s3:::exports/*"]
  }
}

resource "aws_organizations_policy" "deny_export_delete" {
  name    = "deny-export-delete"
  type    = "SERVICE_CONTROL_POLICY"
  content = data.aws_iam_policy_document.deny_export_delete.json
}
```

## Constrain the principal and its context

Prefer provider-supported workload identities and temporary credentials over distributing long-lived keys; the workload identity models guide at /domains/identity-crypto/identity/workload-identity-models/ compares them. Identify the exact issuer, intended audience, subject or workload binding used to obtain credentials. Restrict trust to the required repository, environment, service account or external principal as the provider supports; accepting an entire organization or generic issuer can be far broader than the feature needs.

Review who can change the trusted workload, identity binding or deployment definition. An attacker able to deploy code under a trusted identity may inherit its permissions without changing IAM. For cross-account or third-party access, defend against the confused deputy: require `sts:ExternalId` in a role a vendor assumes for you, and add `aws:SourceArn` and `aws:SourceAccount` conditions when an AWS service acts on your resources, so another customer cannot direct the same service at your role or bucket. Do not treat a descriptive role name as an enforced environment boundary.

Google Cloud and Microsoft Entra ID express the same trust differently. A workload identity pool provider for GitHub must carry an attribute condition, and Google asks at least for the owner; the one below requires the exact subject. An Entra ID federated identity credential matches one exact subject and the audience `api://AzureADTokenExchange`. Checkov passed both and failed a Google provider whose condition only checked the repository owner. Its AWS check failed `repo:*` and a subject without a repository, but passed `repo:company/*`, and it skipped the policy entirely until the federated principal was written as an ARN it could recognize, so a passing scan is not a review.

```hcl
# Google Cloud: the pool provider admits only tokens from that repository and environment
resource "google_iam_workload_identity_pool_provider" "github" {
  workload_identity_pool_id          = "ci"
  workload_identity_pool_provider_id = "github"
  attribute_mapping = {
    "google.subject"       = "assertion.sub"
    "attribute.repository" = "assertion.repository"
  }
  attribute_condition = "assertion.sub == 'repo:company/reports:environment:production'"
  oidc {
    issuer_uri = "https://token.actions.githubusercontent.com"
  }
}

# Microsoft Entra ID: a federated credential matches one exact subject
resource "azuread_application_federated_identity_credential" "github_production" {
  application_id = "/applications/00000000-0000-0000-0000-000000000000"
  display_name   = "github-reports-production"
  issuer         = "https://token.actions.githubusercontent.com"
  subject        = "repo:company/reports:environment:production"
  audiences      = ["api://AzureADTokenExchange"]
}
```

## Evaluate the complete access decision

List required actions and resources, including indirect operations such as passing a role, changing a policy or reading an encryption key. Remove wildcard access where the service supports meaningful scoping. On AWS, effective access can involve identity policies, resource policies, permissions boundaries, organization controls and session policies; explicit denies and service-specific rules matter.

Review public resource exposure separately from authenticated role permissions. A private application role cannot compensate for a broadly readable storage resource. Protect deployment state and audit logs, and identify which administrators can weaken the controls. Provider analysis tools help discover access paths, but their scope and modeled conditions must be understood.

## Test allowed, unrelated and expired identities

Use a disposable environment or an explicitly scoped simulator to test the intended action and forbidden neighboring resources. Attempt identity acquisition from the wrong environment and test behavior after revocation or credential expiry. Record principal, resource, action, context and outcome without storing credential material. A successful policy simulation should be followed by a controlled service call where the simulator cannot model the full resource path.

## Diagnose incomplete fixes

Temporary credentials reduce the duration of some exposures but are not harmless. A stolen active session can exercise its granted permissions until the provider’s relevant controls take effect. Removing future role-assumption permission and invalidating already-issued sessions are separate response questions; verify the provider’s revocation semantics and credential lifetime.

A simulator that evaluates one identity policy cannot establish the complete effective path if it omits resource-policy grants or conditions. Save the modeled policy set and context with its result. Follow with a controlled service request where necessary, and distinguish authentication failure, explicit denial and absence of a grant when diagnosing the result.

## Check the result against concrete cases

These outcomes belong to the worked model, which the Terraform above encodes; confirm them with real calls in a disposable account.

| Case | Expected result | What it establishes |
| --- | --- | --- |
| Production exact assertion | Role acquisition allowed | Trusted origin and workload match |
| Staging assertion | Acquisition denied | Environment isolation |
| Correct subject, wrong audience | Acquisition denied | Token recipient is checked |
| `ExportNorth` `PutObject` `north/a` | Allowed in this model | Required capability |
| `ExportNorth` `PutObject` `south/a` | Denied | No neighboring resource grant |
| `DeleteObject` after adding an Allow | Denied by explicit deny | Deny takes precedence in this model |

## Guided practice

Use the complete simplified policy model above to evaluate three identities and three operations. Do not infer policies that the fixture does not contain.

### Supplied material

```text
Identity A: expected issuer, expected audience, production subject.
Identity B: expected issuer/audience, staging subject.
Identity C: expected issuer/production subject, audience=other-service.
Actions: W=PutObject north/new.csv; R=GetObject north/new.csv; D=DeleteObject north/old.csv.
Change 1 adds Allow DeleteObject north/*. Organization deny remains.
Change 2 allows developer Dev to update the production pipeline that runs as A.
Assume keys/signatures/lifetimes were validated by the trusted exchange for A–C; that does not override the audience/subject conditions.
```

### 1. Evaluate acquisition first

Determine which identities may obtain `ExportNorth` before inspecting W, R or D.

Worked answer: A passes all stated trust conditions. B fails the subject condition and C fails audience. Neither can reach the role-use decision merely because the issuer signature is valid.

### 2. Evaluate the role capability

For A, decide W, R and D and give the exact policy reason.

Worked answer: W is allowed on the north object prefix. R lacks an applicable allow. D meets the explicit organizational deny. The reasons are different even though R and D both produce denial.

### 3. Apply the policy change

Decide whether Change 1 makes D available and explain the misleading partial test to avoid.

Worked answer: D remains denied. Testing only that an Allow statement exists would be misleading because the applicable explicit deny still controls the result. Retain the combined-policy evaluation context.

### 4. Follow indirect authority

Explain why Change 2 is security relevant although Dev is absent from the role trust policy.

Worked answer: Dev can change code executed by the trusted production workload and thereby cause that workload to use A’s capability. Review pipeline modification as privileged delegation, with appropriate branch/environment controls and approval; an unchanged IAM document does not make this access path disappear.

Deliverable: Acquisition decisions for A, B and C, the W, R and D results with their policy reasons, the effect of Change 1 and why Change 2 is privileged.

Scope: The exercise evaluates the supplied model by hand; the Terraform on this page expresses the same trust and permissions for real providers.

## Executable practice

- [IAM trust policy lab: Evaluate a role trust policy against the confused deputy](https://softwaresecurity.com/labs/iam-trust-policy/)

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [AWS IAM security best practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
- [AWS policy evaluation logic](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html)
- [AWS Security Pillar](https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html)
- [GitHub Docs: OpenID Connect](https://docs.github.com/en/actions/concepts/security/openid-connect)
- [GitHub Docs: configuring OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws)
- [AWS IAM: OIDC federation roles](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html)
- [AWS IAM: the confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.html)
- [Google Cloud: workload identity federation with deployment pipelines](https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines)
- [Microsoft Entra: workload identity federation](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation)