From 1a679f371f837ede15ce21443ebb176d1baf51be Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Thu, 26 Sep 2024 19:51:57 +0200 Subject: [PATCH] feat(ecr): Ensure ECR repositories have tag immutability configured (#5144) Co-authored-by: Sergio --- .../__init__.py | 0 ...epositories_tag_immutability.metadata.json | 32 ++++ .../ecr_repositories_tag_immutability.py | 27 +++ .../providers/aws/services/ecr/ecr_service.py | 4 + .../ecr_repositories_tag_immutability_test.py | 163 ++++++++++++++++++ .../aws/services/ecr/ecr_service_test.py | 3 + 6 files changed, 229 insertions(+) create mode 100644 prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/__init__.py create mode 100644 prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.metadata.json create mode 100644 prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.py create mode 100644 tests/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability_test.py diff --git a/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/__init__.py b/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.metadata.json b/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.metadata.json new file mode 100644 index 0000000000..99eb6a7fa0 --- /dev/null +++ b/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "ecr_repositories_tag_immutability", + "CheckTitle": "ECR repositories should have tag immutability configured", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "ecr", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:ecr:{region}:{account-id}:repository/{repository-name}", + "Severity": "medium", + "ResourceType": "AwsEcrRepository", + "Description": "This control checks whether a ECR repository has tag immutability enabled. The control fails if a ECR repository has tag immutability disabled and passes if tag immutability is set to IMMUTABLE.", + "Risk": "Disabling tag immutability in an ECR repository increases the risk of image tampering by allowing tags to be overridden, which can lead to deploying unintended images and security vulnerabilities.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/ecr-private-tag-immutability-enabled.html", + "Remediation": { + "Code": { + "CLI": "aws ecr put-image-tag-mutability --repository-name --image-tag-mutability IMMUTABLE", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/ecr-controls.html#ecr-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable tag immutability on your ECR repositories to prevent image tags from being overwritten.", + "Url": "https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.py b/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.py new file mode 100644 index 0000000000..c721c2c504 --- /dev/null +++ b/prowler/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.ecr.ecr_client import ecr_client + + +class ecr_repositories_tag_immutability(Check): + def execute(self): + findings = [] + for registry in ecr_client.registries.values(): + for repository in registry.repositories: + report = Check_Report_AWS(self.metadata()) + report.region = repository.region + report.resource_id = repository.name + report.resource_arn = repository.arn + report.resource_tags = repository.tags + + report.status = "PASS" + report.status_extended = ( + f"Repository {repository.name} has immutability configured." + ) + + if repository.immutability == "MUTABLE": + report.status = "FAIL" + report.status_extended = f"Repository {repository.name} does not have immutability configured." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/ecr/ecr_service.py b/prowler/providers/aws/services/ecr/ecr_service.py index a87e964d08..ea5d6486a8 100644 --- a/prowler/providers/aws/services/ecr/ecr_service.py +++ b/prowler/providers/aws/services/ecr/ecr_service.py @@ -47,6 +47,9 @@ class ECR(AWSService): scan_on_push=repository["imageScanningConfiguration"][ "scanOnPush" ], + immutability=repository.get( + "imageTagMutability", "MUTABLE" + ), policy=None, images_details=[], lifecycle_policy=None, @@ -372,6 +375,7 @@ class Repository(BaseModel): region: str registry_id = str scan_on_push: bool + immutability: Optional[str] policy: Optional[dict] images_details: Optional[list[ImageDetails]] lifecycle_policy: Optional[str] diff --git a/tests/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability_test.py b/tests/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability_test.py new file mode 100644 index 0000000000..2508cdd161 --- /dev/null +++ b/tests/providers/aws/services/ecr/ecr_repositories_tag_immutability/ecr_repositories_tag_immutability_test.py @@ -0,0 +1,163 @@ +from unittest import mock + +from prowler.providers.aws.services.ecr.ecr_service import Registry, Repository +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + +repository_name = "test_repo" +repository_arn = ( + f"arn:aws:ecr:eu-west-1:{AWS_ACCOUNT_NUMBER}:repository/{repository_name}" +) +repo_policy_public = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ECRRepositoryPolicy", + "Effect": "Allow", + "Principal": {"AWS": f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:user/username"}, + "Action": ["ecr:DescribeImages", "ecr:DescribeRepositories"], + } + ], +} + + +class Test_ecr_repositories_tag_immutability: + # Mocked Audit Info + + def test_no_registries(self): + ecr_client = mock.MagicMock + ecr_client.registries = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability.ecr_client", + ecr_client, + ): + from prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability import ( + ecr_repositories_tag_immutability, + ) + + check = ecr_repositories_tag_immutability() + result = check.execute() + assert len(result) == 0 + + def test_registry_no_repositories(self): + ecr_client = mock.MagicMock + ecr_client.registries = {} + ecr_client.registries[AWS_REGION_EU_WEST_1] = Registry( + id=AWS_ACCOUNT_NUMBER, + region=AWS_REGION_EU_WEST_1, + scan_type="BASIC", + repositories=[], + rules=[], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability.ecr_client", + ecr_client, + ): + from prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability import ( + ecr_repositories_tag_immutability, + ) + + check = ecr_repositories_tag_immutability() + result = check.execute() + assert len(result) == 0 + + def test_immutable(self): + ecr_client = mock.MagicMock + ecr_client.registries = {} + ecr_client.registries[AWS_REGION_EU_WEST_1] = Registry( + id=AWS_ACCOUNT_NUMBER, + region=AWS_REGION_EU_WEST_1, + scan_type="BASIC", + rules=[], + repositories=[ + Repository( + name=repository_name, + arn=repository_arn, + region=AWS_REGION_EU_WEST_1, + scan_on_push=True, + immutability="IMMUTABLE", + policy=repo_policy_public, + images_details=None, + lifecycle_policy="test-policy", + ) + ], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability.ecr_client", + ecr_client, + ): + from prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability import ( + ecr_repositories_tag_immutability, + ) + + check = ecr_repositories_tag_immutability() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Repository {repository_name} has immutability configured." + ) + assert result[0].resource_id == repository_name + assert result[0].resource_arn == repository_arn + assert result[0].resource_tags == [] + + def test_mutable(self): + ecr_client = mock.MagicMock + ecr_client.registries = {} + ecr_client.registries[AWS_REGION_EU_WEST_1] = Registry( + id=AWS_ACCOUNT_NUMBER, + region=AWS_REGION_EU_WEST_1, + scan_type="BASIC", + rules=[], + repositories=[ + Repository( + name=repository_name, + arn=repository_arn, + region=AWS_REGION_EU_WEST_1, + scan_on_push=False, + immutability="MUTABLE", + policy=repo_policy_public, + images_details=None, + lifecycle_policy=None, + ) + ], + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider(), + ), mock.patch( + "prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability.ecr_client", + ecr_client, + ): + from prowler.providers.aws.services.ecr.ecr_repositories_tag_immutability.ecr_repositories_tag_immutability import ( + ecr_repositories_tag_immutability, + ) + + check = ecr_repositories_tag_immutability() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Repository {repository_name} does not have immutability configured." + ) + assert result[0].resource_id == repository_name + assert result[0].resource_arn == repository_arn + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/ecr/ecr_service_test.py b/tests/providers/aws/services/ecr/ecr_service_test.py index 50bc53e564..a8aec1b075 100644 --- a/tests/providers/aws/services/ecr/ecr_service_test.py +++ b/tests/providers/aws/services/ecr/ecr_service_test.py @@ -220,6 +220,7 @@ class Test_ECR_Service: ecr_client.create_repository( repositoryName=repo_name, imageScanningConfiguration={"scanOnPush": True}, + imageTagMutability="IMMUTABLE", ) aws_provider = set_mocked_aws_provider() ecr = ECR(aws_provider) @@ -248,6 +249,7 @@ class Test_ECR_Service: ecr_client.create_repository( repositoryName=repo_name, imageScanningConfiguration={"scanOnPush": True}, + imageTagMutability="IMMUTABLE", ) aws_provider = set_mocked_aws_provider() ecr = ECR(aws_provider) @@ -257,6 +259,7 @@ class Test_ECR_Service: assert ecr.registries[AWS_REGION_EU_WEST_1].repositories[0].arn == repo_arn assert ecr.registries[AWS_REGION_EU_WEST_1].repositories[0].scan_on_push assert ecr.registries[AWS_REGION_EU_WEST_1].repositories[0].lifecycle_policy + assert ecr.registries[AWS_REGION_EU_WEST_1].repositories[0].immutability # Test get image details @mock_aws