From 7e92146d65cfd9ee30db082a8119e8f5701dd68b Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Wed, 16 Oct 2024 18:50:22 +0200 Subject: [PATCH 1/6] feat(iam): Add new attribute entities_attached_to_cloudshell_policy with a method to describe every user,group and role that use cloudshell policy and test for the method --- .../providers/aws/services/iam/iam_service.py | 50 +++++++++++++++++++ .../aws/services/iam/iam_service_test.py | 41 +++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index b32113bce7..433beb500e 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -80,6 +80,12 @@ def __init__(self, provider): self.entities_role_attached_to_securityaudit_policy = ( self._list_entities_role_for_policy(securityaudit_policy_arn) ) + cloudshell_admin_policy_arn = ( + f"arn:{self.audited_partition}:iam::aws:policy/AWSCloudShellFullAccess" + ) + self.entities_attached_to_cloudshell_policy = self._list_entities_for_policy( + cloudshell_admin_policy_arn + ) # List both Customer (attached and unattached) and AWS Managed (only attached) policies self.policies = [] self.policies.extend(self._list_policies("AWS")) @@ -685,6 +691,50 @@ def _list_entities_role_for_policy(self, policy_arn): finally: return roles + def _list_entities_for_policy(self, policy_arn): + logger.info("IAM - List Entities Role For Policy...") + try: + entities = [] + + response = self.client.list_entities_for_policy(PolicyArn=policy_arn) + + entities.extend(user["UserName"] for user in response.get("PolicyUsers")) + entities.extend( + group["GroupName"] for group in response.get("PolicyGroups") + ) + entities.extend(role["RoleName"] for role in response.get("PolicyRoles")) + + while response.get("IsTruncated"): + response = self.client.list_entities_for_policy( + PolicyArn=policy_arn, Marker=response["Marker"] + ) + entities.extend( + user["UserName"] for user in response.get("PolicyUsers") + ) + entities.extend( + group["GroupName"] for group in response.get("PolicyGroups") + ) + entities.extend( + role["RoleName"] for role in response.get("PolicyRoles") + ) + + return entities + except ClientError as error: + if error.response["Error"]["Code"] == "AccessDenied": + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + else: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + finally: + return entities + def _list_policies(self, scope): logger.info("IAM - List Policies...") try: diff --git a/tests/providers/aws/services/iam/iam_service_test.py b/tests/providers/aws/services/iam/iam_service_test.py index 35d04c3bac..12b86ea313 100644 --- a/tests/providers/aws/services/iam/iam_service_test.py +++ b/tests/providers/aws/services/iam/iam_service_test.py @@ -984,3 +984,44 @@ def test_get_user_temporary_credentials_usage(self): ) assert iam.user_temporary_credentials_usage[(username, user_arn)] + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_list_entities_attached_to_cloudshell_policy(self): + iam_client = client("iam") + user_name = "test_cloudshell_policy_user" + iam_client.create_user( + UserName=user_name, + ) + iam_client.attach_user_policy( + UserName=user_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + group_name = "test_cloudshell_policy_group" + iam_client.create_group( + GroupName=group_name, + ) + iam_client.attach_group_policy( + GroupName=group_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + role_name = "test_cloudshell_policy_role" + role_policy = { + "Version": "2012-10-17", + } + iam_client.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(role_policy), + ) + iam_client.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + iam = IAM(aws_provider) + assert len(iam.entities_attached_to_cloudshell_policy) == 3 + assert iam.entities_attached_to_cloudshell_policy == [ + user_name, + group_name, + role_name, + ] From 1e4398a961908510a3d162bd2a1d29ddf888f31c Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Wed, 16 Oct 2024 18:51:53 +0200 Subject: [PATCH 2/6] feat(iam): Add check logic with respective unit tests. Add metadata too --- .../__init__.py | 0 ...ll_admin_policy_not_attached.metadata.json | 34 +++ ...am_cloudshell_admin_policy_not_attached.py | 21 ++ ...oudshell_admin_policy_not_attached_test.py | 269 ++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/__init__.py create mode 100644 prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json create mode 100644 prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py create mode 100644 tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/__init__.py b/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json b/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json new file mode 100644 index 0000000000..e8d93b6b08 --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "iam_cloudshell_admin_policy_not_attached", + "CheckTitle": "Check if IAM identities (users,groups,roles) have the AWSCloudShellFullAccess policy attached.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/CIS AWS Foundations Benchmark" + ], + "ServiceName": "iam", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:iam::{account-id}:{resource-type}/{resource-id}", + "Severity": "medium", + "ResourceType": "AwsIamRole, AwsIamUser, AwsIamGroup", + "Description": "This control checks whether an IAM identity (user, role, or group) has the AWS managed policy AWSCloudShellFullAccess attached. The control fails if an IAM identity has the AWSCloudShellFullAccess policy attached.", + "Risk": "Attaching the AWSCloudShellFullAccess policy to IAM identities grants broad permissions, including internet access and file transfer capabilities, which can lead to security risks such as data exfiltration. The principle of least privilege should be followed to avoid excessive permissions.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/iam-policy-blacklisted-check.html", + "Remediation": { + "Code": { + "CLI": "aws iam detach-user/role/group-policy --user/role/group-name --policy-arn arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/iam-controls.html#iam-27", + "Terraform": "" + }, + "Recommendation": { + "Text": "Detach the AWSCloudShellFullAccess policy from the IAM identity to restrict excessive permissions and adhere to the principle of least privilege.", + "Url": "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage-attach-detach.html" + } + }, + "Categories": [ + "trustboundaries" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py b/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py new file mode 100644 index 0000000000..3382d6a7bd --- /dev/null +++ b/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.iam.iam_client import iam_client + + +class iam_cloudshell_admin_policy_not_attached(Check): + def execute(self) -> Check_Report_AWS: + findings = [] + if iam_client.entities_attached_to_cloudshell_policy is not None: + report = Check_Report_AWS(self.metadata()) + report.region = iam_client.region + report.resource_id = iam_client.audited_account + report.resource_arn = f"arn:{iam_client.audited_partition}:iam::aws:policy/AWSCloudShellFullAccess" + report.status = "PASS" + report.status_extended = ( + "AWS CloudShellFullAccess policy is not attached to any IAM entity." + ) + if iam_client.entities_attached_to_cloudshell_policy: + report.status = "FAIL" + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM entities: {', '.join(iam_client.entities_attached_to_cloudshell_policy)}." + findings.append(report) + return findings diff --git a/tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py b/tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py new file mode 100644 index 0000000000..277822e26b --- /dev/null +++ b/tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py @@ -0,0 +1,269 @@ +from json import dumps +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from prowler.providers.aws.services.iam.iam_service import IAM +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + + +class Test_iam_cloudshell_admin_policy_not_attached: + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_access_denied(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + 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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ) as service_client: + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + service_client.entities_attached_to_cloudshell_policy = None + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert len(result) == 0 + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_nocloudshell_policy(self): + iam = client("iam") + role_name = "test_nocloudshell_policy" + role_policy = { + "Version": "2012-10-17", + } + iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(role_policy), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/SecurityAudit", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "AWS CloudShellFullAccess policy is not attached to any IAM entity." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_role_cloudshell_policy(self): + iam = client("iam") + role_name = "test_cloudshell_policy_role" + role_policy = { + "Version": "2012-10-17", + } + iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(role_policy), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS CloudShellFullAccess policy attached to IAM entities: {role_name}." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_user_cloudshell_policy(self): + iam = client("iam") + user_name = "test_cloudshell_policy_user" + iam.create_user( + UserName=user_name, + ) + iam.attach_user_policy( + UserName=user_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS CloudShellFullAccess policy attached to IAM entities: {user_name}." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_group_cloudshell_policy(self): + iam = client("iam") + group_name = "test_cloudshell_policy_group" + iam.create_group( + GroupName=group_name, + ) + iam.attach_group_policy( + GroupName=group_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS CloudShellFullAccess policy attached to IAM entities: {group_name}." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 + + @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) + def test_user_role_group_cloudshell_policy(self): + iam = client("iam") + user_name = "test_cloudshell_policy_user" + iam.create_user( + UserName=user_name, + ) + iam.attach_user_policy( + UserName=user_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + group_name = "test_cloudshell_policy_group" + iam.create_group( + GroupName=group_name, + ) + iam.attach_group_policy( + GroupName=group_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + role_name = "test_cloudshell_policy_role" + role_policy = { + "Version": "2012-10-17", + } + iam.create_role( + RoleName=role_name, + AssumeRolePolicyDocument=dumps(role_policy), + ) + iam.attach_role_policy( + RoleName=role_name, + PolicyArn="arn:aws:iam::aws:policy/AWSCloudShellFullAccess", + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_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_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + new=IAM(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( + iam_cloudshell_admin_policy_not_attached, + ) + + check = iam_cloudshell_admin_policy_not_attached() + result = check.execute() + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"AWS CloudShellFullAccess policy attached to IAM entities: {user_name}, {group_name}, {role_name}." + ) + assert result[0].resource_id == AWS_ACCOUNT_NUMBER + assert ( + result[0].resource_arn + == "arn:aws:iam::aws:policy/AWSCloudShellFullAccess" + ) + assert result[0].region == AWS_REGION_EU_WEST_1 From bbbfe1287747f3fe8d448a571297b56954f34156 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 17 Oct 2024 11:37:11 -0400 Subject: [PATCH 3/6] chore: revision --- .../__init__.py | 0 ...oudshell_admin_not_attached.metadata.json} | 2 +- ...m_policy_cloudshell_admin_not_attached.py} | 2 +- .../providers/aws/services/iam/iam_service.py | 22 +++----- ...icy_cloudshell_admin_not_attached_test.py} | 50 +++++++++---------- 5 files changed, 33 insertions(+), 43 deletions(-) rename prowler/providers/aws/services/iam/{iam_cloudshell_admin_policy_not_attached => iam_policy_cloudshell_admin_not_attached}/__init__.py (100%) rename prowler/providers/aws/services/iam/{iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json => iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json} (97%) rename prowler/providers/aws/services/iam/{iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py => iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py} (95%) rename tests/providers/aws/services/iam/{iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py => iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py} (78%) diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/__init__.py b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/__init__.py similarity index 100% rename from prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/__init__.py rename to prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/__init__.py diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json similarity index 97% rename from prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json rename to prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json index e8d93b6b08..a40e91a06d 100644 --- a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.metadata.json +++ b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json @@ -1,6 +1,6 @@ { "Provider": "aws", - "CheckID": "iam_cloudshell_admin_policy_not_attached", + "CheckID": "iam_policy_cloudshell_admin_not_attached", "CheckTitle": "Check if IAM identities (users,groups,roles) have the AWSCloudShellFullAccess policy attached.", "CheckType": [ "Software and Configuration Checks/AWS Security Best Practices/CIS AWS Foundations Benchmark" diff --git a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py similarity index 95% rename from prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py rename to prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py index 3382d6a7bd..3116a92ee5 100644 --- a/prowler/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached.py +++ b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py @@ -2,7 +2,7 @@ from prowler.providers.aws.services.iam.iam_client import iam_client -class iam_cloudshell_admin_policy_not_attached(Check): +class iam_policy_cloudshell_admin_not_attached(Check): def execute(self) -> Check_Report_AWS: findings = [] if iam_client.entities_attached_to_cloudshell_policy is not None: diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index 433beb500e..dddfc88c60 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -696,34 +696,24 @@ def _list_entities_for_policy(self, policy_arn): try: entities = [] - response = self.client.list_entities_for_policy(PolicyArn=policy_arn) - - entities.extend(user["UserName"] for user in response.get("PolicyUsers")) - entities.extend( - group["GroupName"] for group in response.get("PolicyGroups") - ) - entities.extend(role["RoleName"] for role in response.get("PolicyRoles")) - - while response.get("IsTruncated"): - response = self.client.list_entities_for_policy( - PolicyArn=policy_arn, Marker=response["Marker"] - ) + paginator = self.client.get_paginator("list_entities_for_policy") + for response in paginator.paginate(PolicyArn=policy_arn): entities.extend( - user["UserName"] for user in response.get("PolicyUsers") + user["UserName"] for user in response.get("PolicyUsers", []) ) entities.extend( - group["GroupName"] for group in response.get("PolicyGroups") + group["GroupName"] for group in response.get("PolicyGroups", []) ) entities.extend( - role["RoleName"] for role in response.get("PolicyRoles") + role["RoleName"] for role in response.get("PolicyRoles", []) ) - return entities except ClientError as error: if error.response["Error"]["Code"] == "AccessDenied": logger.error( f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + entities = None else: logger.error( f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" diff --git a/tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py similarity index 78% rename from tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py rename to tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py index 277822e26b..5836f742b3 100644 --- a/tests/providers/aws/services/iam/iam_cloudshell_admin_policy_not_attached/iam_cloudshell_admin_policy_not_attached_test.py +++ b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py @@ -12,7 +12,7 @@ ) -class Test_iam_cloudshell_admin_policy_not_attached: +class Test_iam_policy_cloudshell_admin_not_attached: @mock_aws(config={"iam": {"load_aws_managed_policies": True}}) def test_access_denied(self): aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) @@ -22,16 +22,16 @@ def test_access_denied(self): "prowler.providers.common.provider.Provider.get_global_provider", return_value=aws_provider, ), mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ) as service_client: - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) service_client.entities_attached_to_cloudshell_policy = None - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert len(result) == 0 @@ -58,15 +58,15 @@ def test_nocloudshell_policy(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ): # Test Check - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert result[0].status == "PASS" assert ( @@ -103,15 +103,15 @@ def test_role_cloudshell_policy(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ): # Test Check - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert result[0].status == "FAIL" assert ( @@ -144,15 +144,15 @@ def test_user_cloudshell_policy(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ): # Test Check - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert result[0].status == "FAIL" assert ( @@ -185,15 +185,15 @@ def test_group_cloudshell_policy(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ): # Test Check - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert result[0].status == "FAIL" assert ( @@ -246,15 +246,15 @@ def test_user_role_group_cloudshell_policy(self): return_value=aws_provider, ): with mock.patch( - "prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached.iam_client", + "prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached.iam_client", new=IAM(aws_provider), ): # Test Check - from prowler.providers.aws.services.iam.iam_cloudshell_admin_policy_not_attached.iam_cloudshell_admin_policy_not_attached import ( - iam_cloudshell_admin_policy_not_attached, + from prowler.providers.aws.services.iam.iam_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( + iam_policy_cloudshell_admin_not_attached, ) - check = iam_cloudshell_admin_policy_not_attached() + check = iam_policy_cloudshell_admin_not_attached() result = check.execute() assert result[0].status == "FAIL" assert ( From e31f73b7edf98318231f4b0043ada613f28ccba6 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Mon, 21 Oct 2024 14:13:12 +0200 Subject: [PATCH 4/6] chore(iam): Change ResourceType --- .../iam_policy_cloudshell_admin_not_attached.metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json index a40e91a06d..9e174a0ebd 100644 --- a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json +++ b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json @@ -9,7 +9,7 @@ "SubServiceName": "", "ResourceIdTemplate": "arn:aws:iam::{account-id}:{resource-type}/{resource-id}", "Severity": "medium", - "ResourceType": "AwsIamRole, AwsIamUser, AwsIamGroup", + "ResourceType": "AwsIamPolicy", "Description": "This control checks whether an IAM identity (user, role, or group) has the AWS managed policy AWSCloudShellFullAccess attached. The control fails if an IAM identity has the AWSCloudShellFullAccess policy attached.", "Risk": "Attaching the AWSCloudShellFullAccess policy to IAM identities grants broad permissions, including internet access and file transfer capabilities, which can lead to security risks such as data exfiltration. The principle of least privilege should be followed to avoid excessive permissions.", "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/iam-policy-blacklisted-check.html", From 1ee24fc407ed10fc7a10f812a073b597ae811992 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Mon, 21 Oct 2024 14:15:02 +0200 Subject: [PATCH 5/6] chore(iam): Change check logic and service to show the entity type in the status extended --- ...am_policy_cloudshell_admin_not_attached.py | 45 ++++++++++++++++++- .../providers/aws/services/iam/iam_service.py | 12 +++-- ...licy_cloudshell_admin_not_attached_test.py | 8 ++-- .../aws/services/iam/iam_service_test.py | 8 ++-- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py index 3116a92ee5..921d34c5a2 100644 --- a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py +++ b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py @@ -14,8 +14,49 @@ def execute(self) -> Check_Report_AWS: report.status_extended = ( "AWS CloudShellFullAccess policy is not attached to any IAM entity." ) - if iam_client.entities_attached_to_cloudshell_policy: + if ( + iam_client.entities_attached_to_cloudshell_policy["Users"] + or iam_client.entities_attached_to_cloudshell_policy["Groups"] + or iam_client.entities_attached_to_cloudshell_policy["Roles"] + ): report.status = "FAIL" - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM entities: {', '.join(iam_client.entities_attached_to_cloudshell_policy)}." + if ( + len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 + and len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) + > 0 + and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) + > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Groups {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 + and len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) + > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Groups {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 + and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) + > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) > 0 + and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) + > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Groups: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Groups: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}." + elif ( + len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) > 0 + ): + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Roles: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." findings.append(report) return findings diff --git a/prowler/providers/aws/services/iam/iam_service.py b/prowler/providers/aws/services/iam/iam_service.py index dddfc88c60..b7f1ef0c3d 100644 --- a/prowler/providers/aws/services/iam/iam_service.py +++ b/prowler/providers/aws/services/iam/iam_service.py @@ -694,17 +694,21 @@ def _list_entities_role_for_policy(self, policy_arn): def _list_entities_for_policy(self, policy_arn): logger.info("IAM - List Entities Role For Policy...") try: - entities = [] + entities = { + "Users": [], + "Groups": [], + "Roles": [], + } paginator = self.client.get_paginator("list_entities_for_policy") for response in paginator.paginate(PolicyArn=policy_arn): - entities.extend( + entities["Users"].extend( user["UserName"] for user in response.get("PolicyUsers", []) ) - entities.extend( + entities["Groups"].extend( group["GroupName"] for group in response.get("PolicyGroups", []) ) - entities.extend( + entities["Roles"].extend( role["RoleName"] for role in response.get("PolicyRoles", []) ) return entities diff --git a/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py index 5836f742b3..5a7fdf4737 100644 --- a/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py +++ b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py @@ -116,7 +116,7 @@ def test_role_cloudshell_policy(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS CloudShellFullAccess policy attached to IAM entities: {role_name}." + == f"AWS CloudShellFullAccess policy attached to IAM Roles: {role_name}." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER assert ( @@ -157,7 +157,7 @@ def test_user_cloudshell_policy(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS CloudShellFullAccess policy attached to IAM entities: {user_name}." + == f"AWS CloudShellFullAccess policy attached to IAM Users: {user_name}." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER assert ( @@ -198,7 +198,7 @@ def test_group_cloudshell_policy(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS CloudShellFullAccess policy attached to IAM entities: {group_name}." + == f"AWS CloudShellFullAccess policy attached to IAM Groups: {group_name}." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER assert ( @@ -259,7 +259,7 @@ def test_user_role_group_cloudshell_policy(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS CloudShellFullAccess policy attached to IAM entities: {user_name}, {group_name}, {role_name}." + == f"AWS CloudShellFullAccess policy attached to IAM Users: {user_name}, Groups {group_name}, Roles {role_name}." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER assert ( diff --git a/tests/providers/aws/services/iam/iam_service_test.py b/tests/providers/aws/services/iam/iam_service_test.py index 12b86ea313..615d25dec6 100644 --- a/tests/providers/aws/services/iam/iam_service_test.py +++ b/tests/providers/aws/services/iam/iam_service_test.py @@ -1020,8 +1020,6 @@ def test_list_entities_attached_to_cloudshell_policy(self): aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) iam = IAM(aws_provider) assert len(iam.entities_attached_to_cloudshell_policy) == 3 - assert iam.entities_attached_to_cloudshell_policy == [ - user_name, - group_name, - role_name, - ] + assert iam.entities_attached_to_cloudshell_policy["Users"] == [user_name] + assert iam.entities_attached_to_cloudshell_policy["Groups"] == [group_name] + assert iam.entities_attached_to_cloudshell_policy["Roles"] == [role_name] From d28837686c4e621b48ad64d8bf7fad1daa1bdbea Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 21 Oct 2024 06:56:23 -0700 Subject: [PATCH 6/6] chore: revision --- ...am_policy_cloudshell_admin_not_attached.py | 68 ++++++------------- ...licy_cloudshell_admin_not_attached_test.py | 2 +- 2 files changed, 21 insertions(+), 49 deletions(-) diff --git a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py index 921d34c5a2..bb40835acf 100644 --- a/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py +++ b/prowler/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py @@ -5,58 +5,30 @@ class iam_policy_cloudshell_admin_not_attached(Check): def execute(self) -> Check_Report_AWS: findings = [] - if iam_client.entities_attached_to_cloudshell_policy is not None: + if iam_client.entities_attached_to_cloudshell_policy: report = Check_Report_AWS(self.metadata()) report.region = iam_client.region report.resource_id = iam_client.audited_account report.resource_arn = f"arn:{iam_client.audited_partition}:iam::aws:policy/AWSCloudShellFullAccess" - report.status = "PASS" - report.status_extended = ( - "AWS CloudShellFullAccess policy is not attached to any IAM entity." - ) - if ( - iam_client.entities_attached_to_cloudshell_policy["Users"] - or iam_client.entities_attached_to_cloudshell_policy["Groups"] - or iam_client.entities_attached_to_cloudshell_policy["Roles"] - ): + entities = iam_client.entities_attached_to_cloudshell_policy + + if entities["Users"] or entities["Groups"] or entities["Roles"]: report.status = "FAIL" - if ( - len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 - and len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) - > 0 - and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) - > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Groups {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 - and len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) - > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Groups {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 - and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) - > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) > 0 - and len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) - > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Groups: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}, Roles {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Users"]) > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Users: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Users'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Groups"]) > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Groups: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Groups'])}." - elif ( - len(iam_client.entities_attached_to_cloudshell_policy["Roles"]) > 0 - ): - report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM Roles: {', '.join(iam_client.entities_attached_to_cloudshell_policy['Roles'])}." + attached_entities = [ + (key, ", ".join(entities[key])) + for key in ["Users", "Groups", "Roles"] + if entities[key] + ] + entity_strings = [ + f"{entity[0]}: {entity[1]}" for entity in attached_entities + ] + report.status_extended = f"AWS CloudShellFullAccess policy attached to IAM {', '.join(entity_strings)}." + else: + report.status = "PASS" + report.status_extended = ( + "AWS CloudShellFullAccess policy is not attached to any IAM entity." + ) + findings.append(report) + return findings diff --git a/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py index 5a7fdf4737..123683c9f5 100644 --- a/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py +++ b/tests/providers/aws/services/iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py @@ -259,7 +259,7 @@ def test_user_role_group_cloudshell_policy(self): assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"AWS CloudShellFullAccess policy attached to IAM Users: {user_name}, Groups {group_name}, Roles {role_name}." + == f"AWS CloudShellFullAccess policy attached to IAM Users: {user_name}, Groups: {group_name}, Roles: {role_name}." ) assert result[0].resource_id == AWS_ACCOUNT_NUMBER assert (