From 739be07077f8aac649e49a646406b58aefed7d2e Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Thu, 14 May 2026 08:10:20 +0100 Subject: [PATCH] chore(aws): skip unattached IAM policies unless --scan-unused-services (#11150) --- .../cli/tutorials/scan-unused-services.mdx | 12 +++ prowler/CHANGELOG.md | 1 + ...ustom_policy_permissive_role_assumption.py | 2 + .../iam_policy_allows_privilege_escalation.py | 2 + ...iam_policy_no_full_access_to_cloudtrail.py | 2 + .../iam_policy_no_full_access_to_kms.py | 2 + ...olicy_no_wildcard_marketplace_subscribe.py | 2 + ..._policy_permissive_role_assumption_test.py | 80 ++++++++++++++++++ ...policy_allows_privilege_escalation_test.py | 83 +++++++++++++++++++ ...olicy_no_full_access_to_cloudtrail_test.py | 75 +++++++++++++++++ .../iam_policy_no_full_access_to_kms_test.py | 75 +++++++++++++++++ ..._no_wildcard_marketplace_subscribe_test.py | 81 ++++++++++++++++++ 12 files changed, 417 insertions(+) diff --git a/docs/user-guide/cli/tutorials/scan-unused-services.mdx b/docs/user-guide/cli/tutorials/scan-unused-services.mdx index 6d675e159f..5dc2f61a07 100644 --- a/docs/user-guide/cli/tutorials/scan-unused-services.mdx +++ b/docs/user-guide/cli/tutorials/scan-unused-services.mdx @@ -56,6 +56,18 @@ Prowler scans only attached security groups to report vulnerabilities in activel - `ec2_networkacl_allow_ingress_X_port` +#### AWS Identity and Access Management (IAM) + +Customer-managed IAM policies that are not attached to any user, group, or role grant no effective permissions until a principal is bound to them. Prowler treats such policies as dormant by default and skips the content-evaluation checks below when `--scan-unused-services` is not set. Enable the flag to surface findings on unattached policies as well. + +- `iam_policy_allows_privilege_escalation` +- `iam_policy_no_full_access_to_cloudtrail` +- `iam_policy_no_full_access_to_kms` +- `iam_policy_no_wildcard_marketplace_subscribe` +- `iam_no_custom_policy_permissive_role_assumption` + +The dedicated `iam_customer_unattached_policy_no_administrative_privileges` check still inspects unattached policies regardless of the flag, since its purpose is to highlight dormant administrator privileges. + #### AWS Glue AWS Glue best practices recommend encrypting metadata and connection passwords in Data Catalogs. diff --git a/prowler/CHANGELOG.md b/prowler/CHANGELOG.md index 3ad6664fd7..21ad902f4c 100644 --- a/prowler/CHANGELOG.md +++ b/prowler/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to the **Prowler SDK** are documented in this file. ### 🔄 Changed - `entra_emergency_access_exclusion` check for M365 provider now scopes the exclusion requirement to enabled Conditional Access policies with a `Block` grant control instead of every enabled policy, focusing on the lockout-relevant policy set [(#10849)](https://github.com/prowler-cloud/prowler/pull/10849) +- AWS IAM customer-managed policy checks no longer emit `FAIL` on unattached policies unless `--scan-unused-services` is enabled [(#11150)](https://github.com/prowler-cloud/prowler/pull/11150) --- 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 6e310d7ca9..fa0058f401 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 @@ -16,6 +16,8 @@ class iam_no_custom_policy_permissive_role_assumption(Check): for policy in iam_client.policies.values(): # Check only custom policies if policy.type == "Custom": + if not policy.attached and not iam_client.provider.scan_unused_services: + continue report = Check_Report_AWS(metadata=self.metadata(), resource=policy) report.region = iam_client.region report.status = "PASS" diff --git a/prowler/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation.py b/prowler/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation.py index c867292b22..7406ec8332 100644 --- a/prowler/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation.py +++ b/prowler/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation.py @@ -11,6 +11,8 @@ class iam_policy_allows_privilege_escalation(Check): for policy in iam_client.policies.values(): if policy.type == "Custom": + if not policy.attached and not iam_client.provider.scan_unused_services: + continue report = Check_Report_AWS(metadata=self.metadata(), resource=policy) report.region = iam_client.region report.status = "PASS" diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py index 4887bbcf6b..844be7f87a 100644 --- a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail.py @@ -11,6 +11,8 @@ class iam_policy_no_full_access_to_cloudtrail(Check): for policy in iam_client.policies.values(): # Check only custom policies if policy.type == "Custom": + if not policy.attached and not iam_client.provider.scan_unused_services: + continue report = Check_Report_AWS(metadata=self.metadata(), resource=policy) report.region = iam_client.region report.status = "PASS" diff --git a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py index adad5d0d1d..e4bf1151da 100644 --- a/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py +++ b/prowler/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms.py @@ -11,6 +11,8 @@ class iam_policy_no_full_access_to_kms(Check): for policy in iam_client.policies.values(): # Check only custom policies if policy.type == "Custom": + if not policy.attached and not iam_client.provider.scan_unused_services: + continue report = Check_Report_AWS(metadata=self.metadata(), resource=policy) report.region = iam_client.region report.status = "PASS" diff --git a/prowler/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe.py b/prowler/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe.py index fe0e740a5e..a1299f3d70 100644 --- a/prowler/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe.py +++ b/prowler/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe.py @@ -10,6 +10,8 @@ class iam_policy_no_wildcard_marketplace_subscribe(Check): findings = [] for policy in iam_client.policies.values(): if policy.type == "Custom": + if not policy.attached and not iam_client.provider.scan_unused_services: + continue report = Check_Report_AWS(metadata=self.metadata(), resource=policy) report.region = iam_client.region report.status = "PASS" 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 6a9576050c..1fbebe6f63 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 @@ -408,3 +408,83 @@ class Test_iam_no_custom_policy_permissive_role_assumption: assert search( "allows permissive STS Role assumption", result[0].status_extended ) + + @mock_aws + def test_unattached_policy_skipped_when_scan_unused_services_disabled(self): + iam_client = client("iam") + policy_name = "unattached_permissive_assume_role" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*"}, + ], + } + iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + ) + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + + 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 == [] + + @mock_aws + def test_attached_policy_fails_when_scan_unused_services_disabled(self): + iam_client = client("iam") + user_name = "test_user_assume_role" + policy_name = "attached_permissive_assume_role" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + iam_client.create_user(UserName=user_name) + iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn) + + from prowler.providers.aws.services.iam.iam_service import IAM + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + + 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 len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn + assert search( + "allows permissive STS Role assumption", result[0].status_extended + ) diff --git a/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py b/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py index 79e1b25955..850a5ba2cb 100644 --- a/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py +++ b/tests/providers/aws/services/iam/iam_policy_allows_privilege_escalation/iam_policy_allows_privilege_escalation_test.py @@ -1261,3 +1261,86 @@ class Test_iam_policy_allows_privilege_escalation: permissions ]: assert search(permission, finding.status_extended) + + @mock_aws + def test_unattached_policy_skipped_when_scan_unused_services_disabled(self): + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "unattached_privilege_escalation" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "iam:CreateAccessKey", "Resource": "*"}, + ], + } + iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + ) + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert result == [] + + @mock_aws + def test_attached_policy_fails_when_scan_unused_services_disabled(self): + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + user_name = "test_user_privesc" + policy_name = "attached_privilege_escalation" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "iam:CreateAccessKey", "Resource": "*"}, + ], + } + policy_arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + iam_client.create_user(UserName=user_name) + iam_client.attach_user_policy(UserName=user_name, PolicyArn=policy_arn) + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + from prowler.providers.aws.services.iam.iam_service import IAM + + with ( + mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), + mock.patch( + "prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation.iam_client", + new=IAM(aws_provider), + ), + ): + from prowler.providers.aws.services.iam.iam_policy_allows_privilege_escalation.iam_policy_allows_privilege_escalation import ( + iam_policy_allows_privilege_escalation, + ) + + check = iam_policy_allows_privilege_escalation() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_arn == policy_arn + assert search( + f"Custom Policy {policy_arn} allows privilege escalation", + result[0].status_extended, + ) diff --git a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py index a29b130f27..38f5a77e19 100644 --- a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py +++ b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_cloudtrail/iam_policy_no_full_access_to_cloudtrail_test.py @@ -207,3 +207,78 @@ class Test_iam_policy_no_full_access_to_cloudtrail: assert result[0].resource_id == "policy_no_cloudtrail_full_no_actions" assert result[0].resource_arn == arn assert result[0].region == "us-east-1" + + @mock_aws + def test_unattached_policy_skipped_when_scan_unused_services_disabled(self): + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + policy_name = "unattached_cloudtrail_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "cloudtrail:*", "Resource": "*"}, + ], + } + iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import ( + iam_policy_no_full_access_to_cloudtrail, + ) + + check = iam_policy_no_full_access_to_cloudtrail() + result = check.execute() + assert result == [] + + @mock_aws + def test_attached_policy_fails_when_scan_unused_services_disabled(self): + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam", region_name=AWS_REGION_US_EAST_1) + user_name = "test_user_cloudtrail" + policy_name = "attached_cloudtrail_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "cloudtrail:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + iam_client.create_user(UserName=user_name) + iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_cloudtrail.iam_policy_no_full_access_to_cloudtrail import ( + iam_policy_no_full_access_to_cloudtrail, + ) + + check = iam_policy_no_full_access_to_cloudtrail() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Custom Policy {policy_name} allows 'cloudtrail:*' privileges." + ) + assert result[0].resource_arn == arn diff --git a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py index 514a83b935..15ff9f80ae 100644 --- a/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py +++ b/tests/providers/aws/services/iam/iam_policy_no_full_access_to_kms/iam_policy_no_full_access_to_kms_test.py @@ -329,6 +329,81 @@ class Test_iam_policy_no_full_access_to_kms_with_unicode: assert result[0].resource_arn == arn assert result[0].region == "us-east-1" + @mock_aws + def test_unattached_policy_skipped_when_scan_unused_services_disabled(self): + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam") + policy_name = "unattached_kms_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "kms:*", "Resource": "*"}, + ], + } + iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import ( + iam_policy_no_full_access_to_kms, + ) + + check = iam_policy_no_full_access_to_kms() + result = check.execute() + assert result == [] + + @mock_aws + def test_attached_policy_fails_when_scan_unused_services_disabled(self): + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam") + user_name = "test_user_kms" + policy_name = "attached_kms_full" + policy_document_full_access = { + "Version": "2012-10-17", + "Statement": [ + {"Effect": "Allow", "Action": "kms:*", "Resource": "*"}, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document_full_access) + )["Policy"]["Arn"] + iam_client.create_user(UserName=user_name) + iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_full_access_to_kms.iam_policy_no_full_access_to_kms import ( + iam_policy_no_full_access_to_kms, + ) + + check = iam_policy_no_full_access_to_kms() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Custom Policy {policy_name} allows 'kms:*' privileges." + ) + assert result[0].resource_arn == arn + @mock_aws def test_policy_full_access_and_full_deny_to_kms(self): aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) diff --git a/tests/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe_test.py b/tests/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe_test.py index b667e7dbfb..b3b41f5e7a 100644 --- a/tests/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe_test.py +++ b/tests/providers/aws/services/iam/iam_policy_no_wildcard_marketplace_subscribe/iam_policy_no_wildcard_marketplace_subscribe_test.py @@ -507,3 +507,84 @@ class Test_iam_policy_no_wildcard_marketplace_subscribe: check = iam_policy_no_wildcard_marketplace_subscribe() result = check.execute() assert len(result) == 0 + + @mock_aws + def test_unattached_policy_skipped_when_scan_unused_services_disabled(self): + """No FAIL for an unattached risky policy when --scan-unused-services is off.""" + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam") + policy_name = "unattached_marketplace_subscribe" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "aws-marketplace:Subscribe", + "Resource": "*", + }, + ], + } + iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + f"{CHECK_MODULE_PATH}.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import ( + iam_policy_no_wildcard_marketplace_subscribe, + ) + + check = iam_policy_no_wildcard_marketplace_subscribe() + result = check.execute() + assert result == [] + + @mock_aws + def test_attached_policy_fails_when_scan_unused_services_disabled(self): + """Attached risky policy still FAILs when --scan-unused-services is off.""" + aws_provider = set_mocked_aws_provider( + [AWS_REGION_US_EAST_1], scan_unused_services=False + ) + iam_client = client("iam") + user_name = "test_user_marketplace" + policy_name = "attached_marketplace_subscribe" + policy_document = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "aws-marketplace:Subscribe", + "Resource": "*", + }, + ], + } + arn = iam_client.create_policy( + PolicyName=policy_name, PolicyDocument=dumps(policy_document) + )["Policy"]["Arn"] + iam_client.create_user(UserName=user_name) + iam_client.attach_user_policy(UserName=user_name, PolicyArn=arn) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + f"{CHECK_MODULE_PATH}.iam_client", + new=IAM(aws_provider), + ): + from prowler.providers.aws.services.iam.iam_policy_no_wildcard_marketplace_subscribe.iam_policy_no_wildcard_marketplace_subscribe import ( + iam_policy_no_wildcard_marketplace_subscribe, + ) + + check = iam_policy_no_wildcard_marketplace_subscribe() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].resource_arn == arn