From f5ecae6da1cda1b50605933a71e448d581875e50 Mon Sep 17 00:00:00 2001 From: Kay Agahd Date: Thu, 3 Jul 2025 17:09:48 +0200 Subject: [PATCH] fix(iam): detect wildcarded ARNs in sts:AssumeRole policy resources (#8164) Co-authored-by: Sergio Garcia --- prowler/CHANGELOG.md | 1 + ...ustom_policy_permissive_role_assumption.py | 40 ++-- ..._policy_permissive_role_assumption_test.py | 176 ++++++++++++++++++ 3 files changed, 198 insertions(+), 19 deletions(-) diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 506960fa19..944d7747f9 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### Changed ### Fixed +- fix(iam): detect wildcarded ARNs in sts:AssumeRole policy resources [(#8164)](https://github.com/prowler-cloud/prowler/pull/8164) --- diff --git a/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py b/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py index 881e362d22..48142c7068 100644 --- a/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py +++ b/prowler/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption.py @@ -5,6 +5,14 @@ from prowler.providers.aws.services.iam.iam_client import iam_client class iam_no_custom_policy_permissive_role_assumption(Check): def execute(self) -> Check_Report_AWS: findings = [] + + def resource_has_wildcard(resource): + if isinstance(resource, str): + return "*" in resource + if isinstance(resource, list): + return any("*" in r for r in resource) + return False + for policy in iam_client.policies: # Check only custom policies if policy.type == "Custom": @@ -12,6 +20,7 @@ class iam_no_custom_policy_permissive_role_assumption(Check): report.region = iam_client.region report.status = "PASS" report.status_extended = f"Custom Policy {policy.name} does not allow permissive STS Role assumption." + if policy.document: if not isinstance(policy.document["Statement"], list): policy_statements = [policy.document["Statement"]] @@ -19,30 +28,23 @@ class iam_no_custom_policy_permissive_role_assumption(Check): policy_statements = policy.document["Statement"] for statement in policy_statements: if ( - statement["Effect"] == "Allow" + statement.get("Effect") == "Allow" and "Action" in statement and "Resource" in statement - and "*" in statement["Resource"] + and resource_has_wildcard(statement["Resource"]) ): - if isinstance(statement["Action"], list): - for action in statement["Action"]: - if ( - action == "sts:AssumeRole" - or action == "sts:*" - or action == "*" - ): - report.status = "FAIL" - report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption." - break - else: - if ( - statement["Action"] == "sts:AssumeRole" - or statement["Action"] == "sts:*" - or statement["Action"] == "*" - ): + actions = ( + statement["Action"] + if isinstance(statement["Action"], list) + else [statement["Action"]] + ) + for action in actions: + if action in ["sts:AssumeRole", "sts:*", "*"]: report.status = "FAIL" report.status_extended = f"Custom Policy {policy.name} allows permissive STS Role assumption." - break + break + if report.status == "FAIL": + break findings.append(report) diff --git a/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py b/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py index 78e3b38553..6a9576050c 100644 --- a/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py +++ b/tests/providers/aws/services/iam/iam_no_custom_policy_permissive_role_assumption/iam_no_custom_policy_permissive_role_assumption_test.py @@ -232,3 +232,179 @@ class Test_iam_no_custom_policy_permissive_role_assumption: result[1].status_extended, ) assert result[1].resource_id == policy_name_permissive + + @mock_aws + def test_policy_resource_with_embedded_wildcard_in_arn(self): + iam_client = client("iam") + policy_name = "policy_with_wildcard_in_arn" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": "arn:aws:iam::*:role/eks-terraform-*", + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + assert result[0].resource_id == policy_name + + @mock_aws + def test_policy_resource_list_containing_wildcard(self): + iam_client = client("iam") + policy_name = "policy_with_resource_list" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": ["arn:aws:iam::123456789012:role/SomeRole", "*"], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + assert result[0].resource_id == policy_name + + @mock_aws + def test_policy_resource_list_with_arn_wildcards_should_fail(self): + iam_client = client("iam") + policy_name = "policy_list_wildcard_arn" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [ + "arn:aws:iam::123456789012:role/eks-admin", + "arn:aws:iam::*:role/eks-*", + ], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) + + @mock_aws + def test_policy_resource_list_with_only_wildcarded_arns_should_fail(self): + iam_client = client("iam") + policy_name = "policy_list_scoped_wildcards_only" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "sts:AssumeRole", + "Resource": [ + "arn:aws:iam::*:role/team-*", + "arn:aws:iam::*:role/dev-*", + ], + } + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_no_custom_policy_permissive_role_assumption.iam_no_custom_policy_permissive_role_assumption import ( + iam_no_custom_policy_permissive_role_assumption, + ) + + check = iam_no_custom_policy_permissive_role_assumption() + result = check.execute() + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + )