How to Fix IAM Access Analyzer 'Publicly Accessible' Finding on Roles with AWS:* Principal
Threat/Impact Level: CRITICAL | Exploitability/Downtime Risk: HIGH | Time to Fix: 5–15 mins
TL;DR
- What broke: An IAM role trust policy contains
"Principal": {"AWS": "*"}, making the role assumable by any AWS principal on the internet — Access Analyzer correctly flags this as a public finding. - How to fix it: Replace the wildcard principal with a specific account ID, role ARN, or service principal, and add a
Conditionblock scoped to your AWS Organization ID (aws:PrincipalOrgID) or specific account. - Fast path: Use our Client-Side Sandbox above to auto-refactor this — paste your trust policy and get a hardened replacement without sending your ARNs to a third-party server.
The Incident (What Does the Error Mean?)
AWS IAM Access Analyzer performs cross-account reachability analysis using automated reasoning. When it sees this in a role's trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole"
}
]
}
It generates a PUBLIC finding with status ACTIVE and resource type AWS::IAM::Role. The finding detail reads:
Finding type: Public
Condition: none
Principal: *
Action: sts:AssumeRole
Resource: arn:aws:iam::123456789012:role/YourRoleName
Immediate consequence: Any authenticated AWS principal — including accounts you have zero relationship with — can call sts:AssumeRole against this role. If sts:AssumeRole succeeds, they inherit every permission attached to this role's permission policies.
The Attack Vector / Blast Radius
This is not theoretical. The attack chain is four API calls:
- Attacker enumerates your account ID (leaked in a public S3 bucket policy, CloudTrail log export, or error message — account IDs are not secret).
- Attacker calls
aws sts assume-role --role-arn arn:aws:iam::YOUR_ACCOUNT:role/YourRoleName --role-session-name pwnedfrom any AWS account they control. - If no
Conditionblock restricts the call, AWS STS returns a validAccessKeyId,SecretAccessKey, andSessionToken. - Attacker uses those credentials to execute whatever the role's attached policies permit — S3 data exfil, EC2 instance launch, Secrets Manager read, RDS snapshot export.
Blast radius scales directly with the role's attached policies. An AdministratorAccess-attached role with this trust policy is full account takeover. Even a read-only role leaks your entire infrastructure topology, data lake contents, and secret names.
Why Condition blocks alone are insufficient without fixing the Principal: A Condition on aws:PrincipalOrgID is only evaluated after the principal match. With Principal: *, AWS still processes the request — a misconfigured or missing condition is a single point of failure. The correct fix is both: a scoped principal AND a condition.
How to Fix It (The Solution)
Basic Fix — Scope the Principal to a Specific Account
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
- "Principal": {
- "AWS": "*"
- },
+ "Principal": {
+ "AWS": "arn:aws:iam::123456789012:root"
+ },
"Action": "sts:AssumeRole"
}
]
}
This alone removes the Access Analyzer public finding. Replace 123456789012 with the specific trusted account ID.
Enterprise Best Practice — Org-Scoped Principal with Condition Defense-in-Depth
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
- "Principal": {
- "AWS": "*"
- },
- "Action": "sts:AssumeRole"
+ "Principal": {
+ "AWS": "arn:aws:iam::123456789012:role/SpecificCallerRole"
+ },
+ "Action": "sts:AssumeRole",
+ "Condition": {
+ "StringEquals": {
+ "aws:PrincipalOrgID": "o-exampleorgid11"
+ },
+ "Bool": {
+ "aws:MultiFactorAuthPresent": "true"
+ }
+ }
}
]
}
Key hardening decisions:
aws:PrincipalOrgID— even if the specific role ARN is somehow spoofed or the account is moved, this condition ensures the caller must be inside your AWS Organization.aws:MultiFactorAuthPresent— for human-assumed roles, require MFA session. Remove this for service-to-service automation roles.- Specific role ARN, not
:root—:roottrusts all principals in that account subject to their own IAM policies. A specific role ARN is the minimum required surface. - If this is a cross-service role (e.g., Lambda execution), use a service principal instead:
"Service": "lambda.amazonaws.com"— not an AWS principal at all.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
This misconfiguration should never reach production. Gate it at three layers:
1. Checkov (Terraform / CloudFormation static analysis)
Checkov rule CKV_AWS_110 flags IAM trust policies with wildcard principals. Add to your pipeline:
checkov -d ./terraform --check CKV_AWS_110 --hard-fail-on HIGH
2. OPA / Conftest policy for Terraform plan JSON
package iam.trust
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_role"
statement := resource.change.after.assume_role_policy
policy := json.unmarshal(statement)
principal := policy.Statement[_].Principal
principal.AWS == "*"
msg := sprintf("Role '%v' has wildcard AWS principal in trust policy — Access Analyzer will flag PUBLIC.", [resource.name])
}
Run in CI:
terraform show -json tfplan.binary | conftest test -p policies/ -
3. AWS Config Rule (runtime continuous compliance)
Deploy the managed rule IAM_NO_INLINE_POLICY_CHECK and a custom Config rule backed by a Lambda that calls access-analyzer:list-findings filtered by findingType: Public and resourceType: AWS::IAM::Role. Alert to Security Hub and auto-remediate via SSM Automation.
4. Access Analyzer as a deployment gate (aws CLI)
# After terraform apply, fail the pipeline if any public IAM findings exist
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/ConsoleAnalyzer \
--filter '{"findingType":{"eq":["Public"]},"resourceType":{"eq":["AWS::IAM::Role"]},"status":{"eq":["ACTIVE"]}}' \
--query 'findings[*].id' \
--output text | grep -q . && echo "FAIL: Public IAM role findings detected" && exit 1
Insert this as a post-deploy step in your GitHub Actions, GitLab CI, or Jenkins pipeline.