diff --git a/prowler/providers/aws/services/ecs/ecs_service.py b/prowler/providers/aws/services/ecs/ecs_service.py index 3d0f531269..e688e72f00 100644 --- a/prowler/providers/aws/services/ecs/ecs_service.py +++ b/prowler/providers/aws/services/ecs/ecs_service.py @@ -77,8 +77,11 @@ class ECS(AWSService): environment=environment, ) ) + task_definition.pid_mode = response["taskDefinition"].get("pidMode", "") task_definition.tags = response.get("tags") - task_definition.network_mode = response["taskDefinition"].get("networkMode") + task_definition.network_mode = response["taskDefinition"].get( + "networkMode", "bridge" + ) except Exception as error: logger.error( f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" @@ -178,6 +181,7 @@ class TaskDefinition(BaseModel): revision: str region: str container_definitions: list[ContainerDefinition] = [] + pid_mode: Optional[str] tags: Optional[list] = [] network_mode: Optional[str] diff --git a/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/__init__.py b/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.metadata.json b/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.metadata.json new file mode 100644 index 0000000000..b296ca930c --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "ecs_task_definitions_host_namespace_not_shared", + "CheckTitle": "ECS task definitions should not share the host's process namespace", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ecs:{region}:{account-id}:task-definition/{task-definition-name}", + "Severity": "high", + "ResourceType": "AwsEcsTaskDefinition", + "Description": "This control checks if Amazon ECS task definitions are configured to share a host's process namespace with its containers. The control fails if the task definition shares the host's process namespace.", + "Risk": "Sharing the host's process namespace with containers exposes host processes, potentially allowing unauthorized access or manipulation of host-level processes, undermining container isolation.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ecs-task-definition-pid-mode-check.html", + "Remediation": { + "Code": { + "CLI": "aws ecs register-task-definition --family --container-definitions '[{\"name\":\"\",\"image\":\"\",\"pidMode\":\"task\"}]'", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ecs-controls.html#ecs-3", + "Terraform": "" + }, + "Recommendation": { + "Text": "Avoid sharing the host's process namespace with containers to maintain process isolation and enhance security.", + "Url": "https://docs.aws.amazon.com/AmazonECS/latest/userguide/task_definition_parameters.html#container_definition_pid_mode" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.py b/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.py new file mode 100644 index 0000000000..e85c9d1c17 --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared.py @@ -0,0 +1,20 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ecs.ecs_client import ecs_client + + +class ecs_task_definitions_host_namespace_not_shared(Check): + def execute(self): + findings = [] + for task_definition in ecs_client.task_definitions.values(): + report = Check_Report_AWS(self.metadata()) + report.region = task_definition.region + report.resource_id = f"{task_definition.name}:{task_definition.revision}" + report.resource_arn = task_definition.arn + report.resource_tags = task_definition.tags + report.status = "PASS" + report.status_extended = f"ECS task definition {task_definition.name} with revision {task_definition.revision} does not share a host's process namespace with its containers." + if task_definition.pid_mode == "host": + report.status = "FAIL" + report.status_extended = f"ECS task definition {task_definition.name} with revision {task_definition.revision} is configured to share a host's process namespace with its containers." + findings.append(report) + return findings diff --git a/tests/providers/aws/services/ecs/ecs_service_test.py b/tests/providers/aws/services/ecs/ecs_service_test.py index b83880e66c..e7d88c4fc8 100644 --- a/tests/providers/aws/services/ecs/ecs_service_test.py +++ b/tests/providers/aws/services/ecs/ecs_service_test.py @@ -28,6 +28,7 @@ def mock_make_api_call(self, operation_name, kwarg): } ], "networkMode": "host", + "pidMode": "host", "tags": [], } } @@ -157,6 +158,7 @@ class Test_ECS_Service: assert ecs.task_definitions[task_arn].network_mode == "host" assert not ecs.task_definitions[task_arn].container_definitions[0].privileged assert ecs.task_definitions[task_arn].container_definitions[0].user == "" + assert ecs.task_definitions[task_arn].pid_mode == "host" assert ( not ecs.task_definitions[task_arn] .container_definitions[0] diff --git a/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py b/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py new file mode 100644 index 0000000000..c88a9a6c7e --- /dev/null +++ b/tests/providers/aws/services/ecs/ecs_task_definitions_host_namespace_not_shared/ecs_task_definitions_host_namespace_not_shared_test.py @@ -0,0 +1,163 @@ +from unittest import mock + +from prowler.providers.aws.services.ecs.ecs_service import ( + ContainerDefinition, + ContainerEnvVariable, + TaskDefinition, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +task_name = "test-task" +task_revision = "1" +container_name = "test-container" +task_arn = f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}" + + +class Test_ecs_task_definitions_host_namespace_not_shared: + def test_no_task_definitions(self): + ecs_client = mock.MagicMock + ecs_client.task_definitions = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( + ecs_task_definitions_host_namespace_not_shared, + ) + + check = ecs_task_definitions_host_namespace_not_shared() + result = check.execute() + assert len(result) == 0 + + def test_task_definition_no_host_pid_mode(self): + ecs_client = mock.MagicMock + ecs_client.task_definitions = {} + ecs_client.task_definitions[task_arn] = TaskDefinition( + name=task_name, + arn=task_arn, + revision=task_revision, + region=AWS_REGION_US_EAST_1, + network_mode="bridge", + pid_mode="task", + container_definitions=[ + ContainerDefinition( + name=container_name, + privileged=False, + user="", + environment=[ + ContainerEnvVariable( + name="env_var_name_no_secrets", + value="env_var_value_no_secrets", + ) + ], + ) + ], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( + ecs_task_definitions_host_namespace_not_shared, + ) + + check = ecs_task_definitions_host_namespace_not_shared() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"ECS task definition {task_name} with revision {task_revision} does not share a host's process namespace with its containers." + ) + + def test_task_definition_host_pid_mode(self): + ecs_client = mock.MagicMock + ecs_client.task_definitions = {} + ecs_client.task_definitions[task_arn] = TaskDefinition( + name=task_name, + arn=task_arn, + revision=task_revision, + region=AWS_REGION_US_EAST_1, + network_mode="host", + pid_mode="host", + container_definitions=[ + ContainerDefinition( + name=container_name, + privileged=False, + user="root", + environment=[], + ) + ], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( + ecs_task_definitions_host_namespace_not_shared, + ) + + check = ecs_task_definitions_host_namespace_not_shared() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"ECS task definition {task_name} with revision {task_revision} is configured to share a host's process namespace with its containers." + ) + + def test_task_definition_no_pid_mode(self): + ecs_client = mock.MagicMock + ecs_client.task_definitions = {} + ecs_client.task_definitions[task_arn] = TaskDefinition( + name=task_name, + arn=f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:task-definition/{task_name}:{task_revision}", + revision=task_revision, + region=AWS_REGION_US_EAST_1, + network_mode="host", + pid_mode="", + container_definitions=[ + ContainerDefinition( + name=container_name, + privileged=True, + user="root", + environment=[], + ) + ], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_task_definitions_host_namespace_not_shared.ecs_task_definitions_host_namespace_not_shared import ( + ecs_task_definitions_host_namespace_not_shared, + ) + + check = ecs_task_definitions_host_namespace_not_shared() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"ECS task definition {task_name} with revision {task_revision} does not share a host's process namespace with its containers." + )