Back to Checks
aws

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 Cloud
Run
prowler aws --checks iam_role_cross_account_readonlyaccess_policy

Recommendation

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/ReadOnlyAccess

Terraform

# 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:AssumeRole

Other

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