Skip to content

Commit

Permalink
feat(iam): add new check iam_policy_cloudshell_admin_not_attached (#…
Browse files Browse the repository at this point in the history
…5437)

Co-authored-by: Sergio <[email protected]>
  • Loading branch information
MarioRgzLpz and sergargar authored Oct 21, 2024
1 parent 1aca7a7 commit 415c319
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 0 deletions.
Empty file.
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": ""
}
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
44 changes: 44 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 @@ 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"))
Expand Down Expand Up @@ -685,6 +691,44 @@ 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 = {
"Users": [],
"Groups": [],
"Roles": [],
}

paginator = self.client.get_paginator("list_entities_for_policy")
for response in paginator.paginate(PolicyArn=policy_arn):
entities["Users"].extend(
user["UserName"] for user in response.get("PolicyUsers", [])
)
entities["Groups"].extend(
group["GroupName"] for group in response.get("PolicyGroups", [])
)
entities["Roles"].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}"
)
entities = None
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:
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 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
Loading

0 comments on commit 415c319

Please sign in to comment.