-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(iam): add new check
iam_policy_cloudshell_admin_not_attached
(#…
…5437) Co-authored-by: Sergio <[email protected]>
- Loading branch information
1 parent
1aca7a7
commit 415c319
Showing
6 changed files
with
420 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
34 changes: 34 additions & 0 deletions
34
...licy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"Provider": "aws", | ||
"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" | ||
], | ||
"ServiceName": "iam", | ||
"SubServiceName": "", | ||
"ResourceIdTemplate": "arn:aws:iam::{account-id}:{resource-type}/{resource-id}", | ||
"Severity": "medium", | ||
"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", | ||
"Remediation": { | ||
"Code": { | ||
"CLI": "aws iam detach-user/role/group-policy --user/role/group-name <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": "" | ||
} |
34 changes: 34 additions & 0 deletions
34
.../iam/iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from prowler.lib.check.models import Check, Check_Report_AWS | ||
from prowler.providers.aws.services.iam.iam_client import iam_client | ||
|
||
|
||
class iam_policy_cloudshell_admin_not_attached(Check): | ||
def execute(self) -> Check_Report_AWS: | ||
findings = [] | ||
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" | ||
entities = iam_client.entities_attached_to_cloudshell_policy | ||
|
||
if entities["Users"] or entities["Groups"] or entities["Roles"]: | ||
report.status = "FAIL" | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
269 changes: 269 additions & 0 deletions
269
...iam_policy_cloudshell_admin_not_attached/iam_policy_cloudshell_admin_not_attached_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_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]) | ||
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_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_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_policy_cloudshell_admin_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_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_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( | ||
iam_policy_cloudshell_admin_not_attached, | ||
) | ||
|
||
check = iam_policy_cloudshell_admin_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_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_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( | ||
iam_policy_cloudshell_admin_not_attached, | ||
) | ||
|
||
check = iam_policy_cloudshell_admin_not_attached() | ||
result = check.execute() | ||
assert result[0].status == "FAIL" | ||
assert ( | ||
result[0].status_extended | ||
== f"AWS CloudShellFullAccess policy attached to IAM Roles: {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_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_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( | ||
iam_policy_cloudshell_admin_not_attached, | ||
) | ||
|
||
check = iam_policy_cloudshell_admin_not_attached() | ||
result = check.execute() | ||
assert result[0].status == "FAIL" | ||
assert ( | ||
result[0].status_extended | ||
== f"AWS CloudShellFullAccess policy attached to IAM Users: {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_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_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( | ||
iam_policy_cloudshell_admin_not_attached, | ||
) | ||
|
||
check = iam_policy_cloudshell_admin_not_attached() | ||
result = check.execute() | ||
assert result[0].status == "FAIL" | ||
assert ( | ||
result[0].status_extended | ||
== f"AWS CloudShellFullAccess policy attached to IAM Groups: {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_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_policy_cloudshell_admin_not_attached.iam_policy_cloudshell_admin_not_attached import ( | ||
iam_policy_cloudshell_admin_not_attached, | ||
) | ||
|
||
check = iam_policy_cloudshell_admin_not_attached() | ||
result = check.execute() | ||
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}." | ||
) | ||
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 |
Oops, something went wrong.