From 2ffe7f3ef74b37b00d121b1960c438665bf7f9ba Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Wed, 2 Oct 2024 22:50:20 +0200 Subject: [PATCH] feat(ecs): add new check `ecs_service_fargate_latest_platform_version` (#5258) Co-authored-by: Sergio --- prowler/config/config.yaml | 5 + .../providers/aws/services/ecs/ecs_service.py | 6 + .../__init__.py | 0 ...gate_latest_platform_version.metadata.json | 34 +++ ...service_fargate_latest_platform_version.py | 38 ++++ tests/config/config_test.py | 2 + tests/config/fixtures/config.yaml | 5 + ...ce_fargate_latest_platform_version_test.py | 195 ++++++++++++++++++ .../aws/services/ecs/ecs_service_test.py | 7 +- 9 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/__init__.py create mode 100644 prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json create mode 100644 prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py create mode 100644 tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py diff --git a/prowler/config/config.yaml b/prowler/config/config.yaml index a577f052fc..5572a958c8 100644 --- a/prowler/config/config.yaml +++ b/prowler/config/config.yaml @@ -57,6 +57,11 @@ aws: 8088, ] + # AWS ECS Configuration + # aws.ecs_service_fargate_latest_platform_version + fargate_linux_latest_version: "1.4.0" + fargate_windows_latest_version: "1.0.0" + # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. diff --git a/prowler/providers/aws/services/ecs/ecs_service.py b/prowler/providers/aws/services/ecs/ecs_service.py index af6bb0e2f1..d629d31551 100644 --- a/prowler/providers/aws/services/ecs/ecs_service.py +++ b/prowler/providers/aws/services/ecs/ecs_service.py @@ -120,6 +120,9 @@ class ECS(AWSService): .get("assignPublicIp", "DISABLED") == "ENABLED" ), + launch_type=service_desc.get("launchType", ""), + platform_version=service_desc.get("platformVersion", ""), + platform_family=service_desc.get("platformFamily", ""), tags=service_desc.get("tags", []), ) cluster.services[service_arn] = service_obj @@ -195,6 +198,9 @@ class Service(BaseModel): name: str arn: str region: str + launch_type: str = "" + platform_version: Optional[str] + platform_family: Optional[str] assign_public_ip: Optional[bool] tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/__init__.py b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json new file mode 100644 index 0000000000..b3d25e494c --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "ecs_service_fargate_latest_platform_version", + "CheckTitle": "ECS Fargate services should run on the latest Fargate platform version", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ecs", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ecs:{region}:{account-id}:service/{service-name}", + "Severity": "medium", + "ResourceType": "AwsEcsService", + "Description": "This control checks if Amazon ECS Fargate services are running the latest Fargate platform version. The control fails if the platform version is not the latest.", + "Risk": "Not running the latest Fargate platform version may expose your services to security vulnerabilities and bugs that are resolved in newer versions.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ecs-fargate-latest-platform-version.html", + "Remediation": { + "Code": { + "CLI": "aws ecs update-service --cluster --service --platform-version LATEST", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ecs-controls.html#ecs-10", + "Terraform": "" + }, + "Recommendation": { + "Text": "Update your ECS Fargate services to the latest platform version to ensure they are running in a secure and optimized environment.", + "Url": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html" + } + }, + "Categories": [ + "vulnerabilities" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py new file mode 100644 index 0000000000..e6a20e6b4c --- /dev/null +++ b/prowler/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ecs.ecs_client import ecs_client + + +class ecs_service_fargate_latest_platform_version(Check): + def execute(self): + findings = [] + for service in ecs_client.services.values(): + if service.launch_type == "FARGATE": + report = Check_Report_AWS(self.metadata()) + report.region = service.region + report.resource_id = service.name + report.resource_arn = service.arn + report.resource_tags = service.tags + fargate_latest_linux_version = ecs_client.audit_config.get( + "fargate_linux_latest_version", "1.4.0" + ) + fargate_latest_windows_version = ecs_client.audit_config.get( + "fargate_windows_latest_version", "1.0.0" + ) + report.status = "PASS" + report.status_extended = f"ECS Service {service.name} is using latest FARGATE {service.platform_family} version {fargate_latest_linux_version if service.platform_family == 'Linux' else fargate_latest_windows_version}." + if ( + service.platform_version != "LATEST" + and ( + service.platform_family == "Linux" + and service.platform_version != fargate_latest_linux_version + ) + or ( + service.platform_family == "Windows" + and service.platform_version != fargate_latest_windows_version + ) + ): + report.status = "FAIL" + report.status_extended = f"ECS Service {service.name} is not using latest FARGATE {service.platform_family} version {fargate_latest_linux_version if service.platform_family == 'Linux' else fargate_latest_windows_version}, currently using {service.platform_version}." + + findings.append(report) + return findings diff --git a/tests/config/config_test.py b/tests/config/config_test.py index 1e1d235ac2..d02e34b79c 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -93,6 +93,8 @@ config_aws = { 8080, 8088, ], + "fargate_linux_latest_version": "1.4.0", + "fargate_windows_latest_version": "1.0.0", "trusted_account_ids": [], "log_group_retention_days": 365, "max_idle_disconnect_timeout_in_seconds": 600, diff --git a/tests/config/fixtures/config.yaml b/tests/config/fixtures/config.yaml index 46e499453a..d2abd5a01e 100644 --- a/tests/config/fixtures/config.yaml +++ b/tests/config/fixtures/config.yaml @@ -57,6 +57,11 @@ aws: 8088, ] + # AWS ECS Configuration + # aws.ecs_service_fargate_latest_platform_version + fargate_linux_latest_version: "1.4.0" + fargate_windows_latest_version: "1.0.0" + # AWS VPC Configuration (vpc_endpoint_connections_trust_boundaries, vpc_endpoint_services_allowed_principals_trust_boundaries) # AWS SSM Configuration (aws.ssm_documents_set_as_public) # Single account environment: No action required. The AWS account number will be automatically added by the checks. diff --git a/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py b/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py new file mode 100644 index 0000000000..109fa99e01 --- /dev/null +++ b/tests/providers/aws/services/ecs/ecs_service_fargate_latest_platform_version/ecs_service_fargate_latest_platform_version_test.py @@ -0,0 +1,195 @@ +from unittest import mock + +from prowler.providers.aws.services.ecs.ecs_service import Service +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 + +SERVICE_ARN = ( + f"arn:aws:ecs:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:service/sample-service" +) +SERVICE_NAME = "sample-service" + + +class Test_ecs_service_fargate_latest_platform_version: + def test_no_services(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 0 + + def test_service_ec2_type(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + ecs_client.services[SERVICE_ARN] = Service( + name=SERVICE_NAME, + arn=SERVICE_ARN, + region=AWS_REGION_US_EAST_1, + launch_type="EC2", + assign_public_ip=False, + tags=[], + ) + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 0 + + def test_service_linux_latest_version(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + ecs_client.services[SERVICE_ARN] = Service( + name=SERVICE_NAME, + arn=SERVICE_ARN, + region=AWS_REGION_US_EAST_1, + launch_type="FARGATE", + platform_family="Linux", + platform_version="1.4.0", + assign_public_ip=False, + tags=[], + ) + + ecs_client.audit_config = { + "fargate_linux_latest_version": "1.4.0", + } + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"ECS Service {SERVICE_NAME} is using latest FARGATE Linux version 1.4.0." + ) + assert result[0].resource_id == SERVICE_NAME + assert result[0].resource_arn == SERVICE_ARN + + def test_service_windows_latest_version(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + ecs_client.services[SERVICE_ARN] = Service( + name=SERVICE_NAME, + arn=SERVICE_ARN, + region=AWS_REGION_US_EAST_1, + launch_type="FARGATE", + platform_family="Windows", + platform_version="1.0.0", + assign_public_ip=False, + tags=[], + ) + + ecs_client.audit_config = { + "fargate_windows_latest_version": "1.0.0", + } + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + f"ECS Service {SERVICE_NAME} is using latest FARGATE Windows version 1.0.0." + ) + assert result[0].resource_id == SERVICE_NAME + assert result[0].resource_arn == SERVICE_ARN + + def test_service_linux_no_latest_version(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + ecs_client.services[SERVICE_ARN] = Service( + name=SERVICE_NAME, + arn=SERVICE_ARN, + region=AWS_REGION_US_EAST_1, + launch_type="FARGATE", + platform_family="Linux", + platform_version="1.2.0", + assign_public_ip=False, + tags=[], + ) + + ecs_client.audit_config = { + "fargate_linux_latest_version": "1.4.0", + } + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"ECS Service {SERVICE_NAME} is not using latest FARGATE Linux version 1.4.0, currently using 1.2.0." + ) + assert result[0].resource_id == SERVICE_NAME + assert result[0].resource_arn == SERVICE_ARN + + def test_service_windows_no_latest_version(self): + ecs_client = mock.MagicMock + ecs_client.services = {} + ecs_client.services[SERVICE_ARN] = Service( + name=SERVICE_NAME, + arn=SERVICE_ARN, + region=AWS_REGION_US_EAST_1, + launch_type="FARGATE", + platform_family="Windows", + platform_version="0.9.0", + assign_public_ip=False, + tags=[], + ) + + ecs_client.audit_config = { + "fargate_windows_latest_version": "1.0.0", + } + + with mock.patch( + "prowler.providers.aws.services.ecs.ecs_service.ECS", + ecs_client, + ): + from prowler.providers.aws.services.ecs.ecs_service_fargate_latest_platform_version.ecs_service_fargate_latest_platform_version import ( + ecs_service_fargate_latest_platform_version, + ) + + check = ecs_service_fargate_latest_platform_version() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + f"ECS Service {SERVICE_NAME} is not using latest FARGATE Windows version 1.0.0, currently using 0.9.0." + ) + assert result[0].resource_id == SERVICE_NAME + assert result[0].resource_arn == SERVICE_ARN diff --git a/tests/providers/aws/services/ecs/ecs_service_test.py b/tests/providers/aws/services/ecs/ecs_service_test.py index ceaa96f661..76f7abbc77 100644 --- a/tests/providers/aws/services/ecs/ecs_service_test.py +++ b/tests/providers/aws/services/ecs/ecs_service_test.py @@ -44,7 +44,6 @@ def mock_make_api_call(self, operation_name, kwarg): { "serviceArn": "arn:aws:ecs:eu-west-1:123456789012:service/test_cluster_1/test_ecs_service", "serviceName": "test_ecs_service", - "launchType": "EC2", "networkConfiguration": { "awsvpcConfiguration": { "subnets": ["subnet-12345678"], @@ -52,6 +51,9 @@ def mock_make_api_call(self, operation_name, kwarg): "assignPublicIp": "ENABLED", } }, + "launchType": "FARGATE", + "platformVersion": "1.4.0", + "platformFamily": "Linux", } ] } @@ -218,3 +220,6 @@ class Test_ECS_Service: assert ecs.services[service_arn].region == AWS_REGION_EU_WEST_1 assert ecs.services[service_arn].assign_public_ip assert ecs.services[service_arn].tags == [] + assert ecs.services[service_arn].launch_type == "FARGATE" + assert ecs.services[service_arn].platform_version == "1.4.0" + assert ecs.services[service_arn].platform_family == "Linux"