Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iam): add new check iam_policy_cloudshell_admin_not_attached #5437

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "AwsIamRole, AwsIamUser, AwsIamGroup",
MarioRgzLpz marked this conversation as resolved.
Show resolved Hide resolved
"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": ""
}
Original file line number Diff line number Diff line change
@@ -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_policy_cloudshell_admin_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
40 changes: 40 additions & 0 deletions prowler/providers/aws/services/iam/iam_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
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"))
Expand Down Expand Up @@ -685,6 +691,40 @@
finally:
return roles

def _list_entities_for_policy(self, policy_arn):
logger.info("IAM - List Entities Role For Policy...")
try:
entities = []
MarioRgzLpz marked this conversation as resolved.
Show resolved Hide resolved

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", [])
)
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(

Check warning on line 713 in prowler/providers/aws/services/iam/iam_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_service.py#L713

Added line #L713 was not covered by tests
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
entities = None

Check warning on line 716 in prowler/providers/aws/services/iam/iam_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_service.py#L716

Added line #L716 was not covered by tests
else:
logger.error(
f"{self.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
except Exception as error:
logger.error(

Check warning on line 722 in prowler/providers/aws/services/iam/iam_service.py

View check run for this annotation

Codecov / codecov/patch

prowler/providers/aws/services/iam/iam_service.py#L721-L722

Added lines #L721 - L722 were not covered by tests
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:
Expand Down
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 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_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 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_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 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_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 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
Loading