Back to Checks
IAM role does not grant ReadOnlyAccess to external AWS accounts
iam_role_cross_account_readonlyaccess_policy
Service: iamSeverity: high
OfficialBuilt-inVerified
Categories:trust-boundariesidentity-access
Resource Type:AwsIamRole
IAM roles are assessed for the AWS-managed ReadOnlyAccess policy combined with a trust policy that allows external AWS principals or *. This identifies roles that expose broad read permissions to other accounts.
Risk
Granting cross-account read access can expose sensitive data and metadata, impacting confidentiality. External principals can read S3/DynamoDB contents and enumerate resources, policies, and logs, enabling targeted recon and easier privilege escalation paths.
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks iam_role_cross_account_readonlyaccess_policyRecommendation
Avoid attaching ReadOnlyAccess to roles trusted by other accounts. Apply least privilege with custom, tightly scoped policies. Restrict trust to explicit principals, avoid *, and use conditions like aws:PrincipalOrgID and sts:ExternalId for defense in depth.
Remediation
CLI
aws iam detach-role-policy --role-name <ROLE_NAME> --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccessTerraform
# Terraform snippet to prevent cross-account access on a role
resource "aws_iam_role" "<example_resource_name>" {
name = "<example_resource_name>"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::<example_account_id>:root" # Critical: trust only the same account to avoid cross-account access
}
Action = "sts:AssumeRole"
}]
})
}NativeIaC
# CloudFormation snippet to prevent cross-account access on a role
Resources:
<example_resource_name>:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: arn:aws:iam::<example_account_id>:root # Critical: restrict trust to this account only to avoid cross-account access
Action: sts:AssumeRoleOther
1Open the AWS Management Console > IAM > Roles
2Select the role granting external access
3On the Permissions tab, locate the policy ReadOnlyAccess
4Click Detach policy and confirm
5Verify the role no longer lists ReadOnlyAccess
Check Code
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client
class iam_role_cross_account_readonlyaccess_policy(Check):
def execute(self) -> Check_Report_AWS:
findings = []
if iam_client.roles:
for role in iam_client.roles:
if (
not role.is_service_role
): # Avoid service roles since they cannot be modified by the user
report = Check_Report_AWS(metadata=self.metadata(), resource=role)
report.region = iam_client.region
report.status = "PASS"
report.status_extended = (
f"IAM Role {role.name} does not have ReadOnlyAccess policy."
)
for policy in role.attached_policies:
if policy["PolicyName"] == "ReadOnlyAccess":
report.status_extended = f"IAM Role {role.name} has read-only access but is not cross account."
cross_account_access = False
if isinstance(role.assume_role_policy["Statement"], list):
for statement in role.assume_role_policy["Statement"]:
if not cross_account_access:
if (
statement["Effect"] == "Allow"
and "AWS" in statement["Principal"]
):
if isinstance(
statement["Principal"]["AWS"], list
):
for aws_account in statement[
"Principal"
]["AWS"]:
if (
iam_client.audited_account
not in aws_account
or "*" == aws_account
):
cross_account_access = True
break
else:
if (
iam_client.audited_account
not in statement["Principal"]["AWS"]
or "*"
== statement["Principal"]["AWS"]
):
cross_account_access = True
else:
break
else:
statement = role.assume_role_policy["Statement"]
if (
statement["Effect"] == "Allow"
and "AWS" in statement["Principal"]
):
if isinstance(statement["Principal"]["AWS"], list):
for aws_account in statement["Principal"][
"AWS"
]:
if (
iam_client.audited_account
not in aws_account
or "*" == aws_account
):
cross_account_access = True
break
else:
if (
iam_client.audited_account
not in statement["Principal"]["AWS"]
or "*" == statement["Principal"]["AWS"]
):
cross_account_access = True
if cross_account_access:
report.status = "FAIL"
report.status_extended = f"IAM Role {role.name} gives cross account read-only access."
findings.append(report)
return findings