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-widerepo: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.
| Setting | Value | 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 |
- Run Checkov with
CKV_AWS_358,CKV_AWS_393,CKV_GCP_125andCKV_AZURE_249on the Terraform, and also read every subject condition, because the checks accept organization-wide subjects. - From a staging job, try to assume the production role and expect
AccessDenied; from production, expect success. - As the role, write under
north/, then trysouth/and a delete; expect success, then two denials, and check that the delete names the SCP in its error. - List who can push to the trusted workflow, approve its environment and edit the role, and record them as holders of the role.
- Simulate the permissions with the IAM policy simulator, then confirm with one real call per decision.
- Applies to
- Terraform 1.16.5 with AWS provider 6.68, Google provider 8.6 and AzureAD provider 3.10 (terraform validate passed)
- Checkov 3.3.26 (CKV_AWS_358, CKV_AWS_393, CKV_GCP_125, CKV_AZURE_249)
Cloud role assumption and effective permissions. Provider-specific policy evaluation and organization settings matter.
- Part of
- Learning path: Runtime and cloud defense (step 1 of 13). Workflows: Fix a finding, Review a design
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
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
Before you start
Platform and cloud engineers who write role trust policies and workload identity federation for CI pipelines and services
- Distinguish a role trust policy, identity policy, resource policy and organizational guardrail.
From a CI job to one allowed write
- Token requestThe job, granted
id-token: write, asks GitHub's OIDC provider for a token with the audiencests.amazonaws.com. Its subject names the repository and environment, such asrepo:company/reports:environment:production. - Role requestThe job sends the token to AWS STS in an
AssumeRoleWithWebIdentitycall for theExportNorthrole. - Trust checkSTS checks the token's signature against the registered issuer and the trust policy's
StringEqualsconditions on the exact audience and subject. A staging job's token fails here. - Allowed useWith the temporary credentials the job writes under
exports/north/, the only prefix the role policy allows. - Explicit denyA
DeleteObjectcall fails withAccessDenied: the organization SCP denies it, whatever the role's own policy says.
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.
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.
# 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
}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.
# 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"]
}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.
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.
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.
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 scenario material
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
ExportNorthbefore inspecting W, R or D.Expected result and reasoning
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.
Decide each action for identity A
Expected result and reasoning
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.
Expected result and reasoning
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.
Expected result and reasoning
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.
The exercise evaluates the supplied model by hand; the Terraform on this page expresses the same trust and permissions for real providers.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Repair the code
IAM trust policy lab: Evaluate a role trust policy against the confused deputy
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- AWS IAM security best practices
- AWS policy evaluation logic
- AWS Security Pillar
- GitHub Docs: OpenID Connect
- GitHub Docs: configuring OpenID Connect in Amazon Web Services
- AWS IAM: OIDC federation roles
- AWS IAM: the confused deputy problem
- Google Cloud: workload identity federation with deployment pipelines
- Microsoft Entra: workload identity federation
Reference information
- SDLC phase
- ReleaseMaintenance