From 05f7170addfccd83069b616ff1aa59a5542f536e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20De=20la=20Torre=20Vico?= Date: Thu, 26 Sep 2024 19:33:28 +0200 Subject: [PATCH] feat(dms): add tags to DMS checks (#5209) --- .../dms_endpoint_ssl_enabled.py | 9 ++- ..._instance_minor_version_upgrade_enabled.py | 1 + .../dms_instance_multi_az_enabled.py | 1 + .../dms_instance_no_public_access.py | 1 + .../providers/aws/services/dms/dms_service.py | 21 ++++++- .../dms_endpoint_ssl_enabled_test.py | 60 +++++++++++++++---- ...ance_minor_version_upgrade_enabled_test.py | 8 +++ .../dms_instance_multi_az_test.py | 8 +++ .../dms_no_public_access_test.py | 30 +++++++++- .../aws/services/dms/dms_service_test.py | 50 ++++++++++++++++ 10 files changed, 169 insertions(+), 20 deletions(-) diff --git a/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.py b/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.py index 27f335aa4b..465923b60c 100644 --- a/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.py +++ b/prowler/providers/aws/services/dms/dms_endpoint_ssl_enabled/dms_endpoint_ssl_enabled.py @@ -9,16 +9,15 @@ class dms_endpoint_ssl_enabled(Check): report = Check_Report_AWS(self.metadata()) report.resource_id = endpoint.id report.resource_arn = endpoint_arn - report.region = dms_client.audited_region + report.region = endpoint.region + report.resource_tags = endpoint.tags if endpoint.ssl_mode == "none": report.status = "FAIL" - report.status_extended = ( - f"DMS Endpoint {endpoint_arn} is not using SSL." - ) + report.status_extended = f"DMS Endpoint {endpoint.id} is not using SSL." else: report.status = "PASS" - report.status_extended = f"DMS Endpoint {endpoint_arn} is using SSL with mode: {endpoint.ssl_mode}." + report.status_extended = f"DMS Endpoint {endpoint.id} is using SSL with mode: {endpoint.ssl_mode}." findings.append(report) diff --git a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.py b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.py index a63fbe7118..b89e72ec89 100644 --- a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.py +++ b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.py @@ -10,6 +10,7 @@ class dms_instance_minor_version_upgrade_enabled(Check): report.region = instance.region report.resource_id = instance.id report.resource_arn = instance.arn + report.resource_tags = instance.tags report.status = "FAIL" report.status_extended = f"DMS Replication Instance {instance.id} does not have auto minor version upgrade enabled." if instance.auto_minor_version_upgrade: diff --git a/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.py b/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.py index 56be3cd468..d632ff225b 100644 --- a/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.py +++ b/prowler/providers/aws/services/dms/dms_instance_multi_az_enabled/dms_instance_multi_az_enabled.py @@ -10,6 +10,7 @@ class dms_instance_multi_az_enabled(Check): report.region = instance.region report.resource_id = instance.id report.resource_arn = instance.arn + report.resource_tags = instance.tags report.status = "FAIL" report.status_extended = f"DMS Replication Instance {instance.id} does not have multi az enabled." if instance.multi_az: diff --git a/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.py b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.py index ee7a9f22e3..de383196a7 100644 --- a/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.py +++ b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.py @@ -12,6 +12,7 @@ class dms_instance_no_public_access(Check): report.region = instance.region report.resource_id = instance.id report.resource_arn = instance.arn + report.resource_tags = instance.tags report.status = "PASS" report.status_extended = ( f"DMS Replication Instance {instance.id} is not publicly accessible." diff --git a/prowler/providers/aws/services/dms/dms_service.py b/prowler/providers/aws/services/dms/dms_service.py index 1e759a135e..a424c23037 100644 --- a/prowler/providers/aws/services/dms/dms_service.py +++ b/prowler/providers/aws/services/dms/dms_service.py @@ -1,3 +1,5 @@ +from typing import Optional + from pydantic import BaseModel from prowler.lib.logger import logger @@ -5,7 +7,6 @@ from prowler.lib.scan_filters.scan_filters import is_resource_filtered from prowler.providers.aws.lib.service.service import AWSService -################## Database Migration Service class DMS(AWSService): def __init__(self, provider): # Call AWSService's __init__ @@ -13,7 +14,9 @@ class DMS(AWSService): self.instances = [] self.endpoints = {} self.__threading_call__(self._describe_replication_instances) + self.__threading_call__(self._list_tags, self.instances) self.__threading_call__(self._describe_endpoints) + self.__threading_call__(self._list_tags, self.endpoints.values()) def _describe_replication_instances(self, regional_client): logger.info("DMS - Describing DMS Replication Instances...") @@ -64,7 +67,9 @@ class DMS(AWSService): is_resource_filtered(arn, self.audit_resources) ): self.endpoints[arn] = Endpoint( + arn=arn, id=endpoint["EndpointIdentifier"], + region=regional_client.region, ssl_mode=endpoint.get("SslMode", False), ) except Exception as error: @@ -72,10 +77,23 @@ class DMS(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _list_tags(self, resource: any): + try: + resource.tags = self.regional_clients[ + resource.region + ].list_tags_for_resource(ResourceArn=resource.arn)["TagList"] + except Exception as error: + logger.error( + f"{resource.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Endpoint(BaseModel): + arn: str id: str + region: str ssl_mode: str + tags: Optional[list] class RepInstance(BaseModel): @@ -88,3 +106,4 @@ class RepInstance(BaseModel): security_groups: list[str] = [] multi_az: bool region: str + tags: Optional[list] diff --git a/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py b/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py index 34b015456d..4d4f087fce 100644 --- a/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py +++ b/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py @@ -5,7 +5,6 @@ from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 class Test_dms_endpoint_ssl_enabled: - def test_dms_no_endpoints(self): dms_client = mock.MagicMock dms_client.endpoints = {} @@ -24,8 +23,15 @@ class Test_dms_endpoint_ssl_enabled: def test_dms_endpoint_ssl_none(self): dms_client = mock.MagicMock + endpoint_arn = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:test-endpoint-no-ssl" dms_client.endpoints = { - "test-endpoint-no-ssl": Endpoint(id="test-endpoint-no-ssl", ssl_mode="none") + endpoint_arn: Endpoint( + arn=endpoint_arn, + id="test-endpoint-no-ssl", + region=AWS_REGION_US_EAST_1, + ssl_mode="none", + tags=[{"Key": "Name", "Value": "test-endpoint-no-ssl"}], + ) } dms_client.audited_account = AWS_ACCOUNT_NUMBER dms_client.audited_partition = "aws" @@ -45,18 +51,29 @@ class Test_dms_endpoint_ssl_enabled: assert len(result) == 1 assert result[0].status == "FAIL" assert result[0].resource_id == "test-endpoint-no-ssl" - assert result[0].resource_arn == "test-endpoint-no-ssl" + assert ( + result[0].resource_arn + == f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:test-endpoint-no-ssl" + ) assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].status_extended == "DMS Endpoint test-endpoint-no-ssl is not using SSL." ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-endpoint-no-ssl"} + ] def test_dms_endpoint_ssl_require(self): dms_client = mock.MagicMock + endpoint_arn = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:test-endpoint-ssl-require" dms_client.endpoints = { - "test-endpoint-ssl-require": Endpoint( - id="test-endpoint-ssl-require", ssl_mode="require" + endpoint_arn: Endpoint( + arn=endpoint_arn, + id="test-endpoint-ssl-require", + region=AWS_REGION_US_EAST_1, + ssl_mode="require", + tags=[{"Key": "Name", "Value": "test-endpoint-ssl-require"}], ) } dms_client.audited_account = AWS_ACCOUNT_NUMBER @@ -77,18 +94,26 @@ class Test_dms_endpoint_ssl_enabled: assert len(result) == 1 assert result[0].status == "PASS" assert result[0].resource_id == "test-endpoint-ssl-require" - assert result[0].resource_arn == "test-endpoint-ssl-require" + assert result[0].resource_arn == endpoint_arn assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].status_extended == "DMS Endpoint test-endpoint-ssl-require is using SSL with mode: require." ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-endpoint-ssl-require"} + ] def test_dms_endpoint_ssl_verify_ca(self): dms_client = mock.MagicMock + endpoint_arn = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:test-endpoint-ssl-verify-ca" dms_client.endpoints = { - "test-endpoint-ssl-verify-ca": Endpoint( - id="test-endpoint-ssl-verify-ca", ssl_mode="verify-ca" + endpoint_arn: Endpoint( + arn=endpoint_arn, + id="test-endpoint-ssl-verify-ca", + region=AWS_REGION_US_EAST_1, + ssl_mode="verify-ca", + tags=[{"Key": "Name", "Value": "test-endpoint-ssl-verify-ca"}], ) } dms_client.audited_account = AWS_ACCOUNT_NUMBER @@ -109,18 +134,26 @@ class Test_dms_endpoint_ssl_enabled: assert len(result) == 1 assert result[0].status == "PASS" assert result[0].resource_id == "test-endpoint-ssl-verify-ca" - assert result[0].resource_arn == "test-endpoint-ssl-verify-ca" + assert result[0].resource_arn == endpoint_arn assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].status_extended == "DMS Endpoint test-endpoint-ssl-verify-ca is using SSL with mode: verify-ca." ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-endpoint-ssl-verify-ca"} + ] def test_dms_endpoint_ssl_verify_full(self): dms_client = mock.MagicMock + endpoint_arn = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:test-endpoint-ssl-verify-full" dms_client.endpoints = { - "test-endpoint-ssl-verify-full": Endpoint( - id="test-endpoint-ssl-verify-full", ssl_mode="verify-full" + endpoint_arn: Endpoint( + arn=endpoint_arn, + id="test-endpoint-ssl-verify-full", + region=AWS_REGION_US_EAST_1, + ssl_mode="verify-full", + tags=[{"Key": "Name", "Value": "test-endpoint-ssl-verify-full"}], ) } dms_client.audited_account = AWS_ACCOUNT_NUMBER @@ -141,9 +174,12 @@ class Test_dms_endpoint_ssl_enabled: assert len(result) == 1 assert result[0].status == "PASS" assert result[0].resource_id == "test-endpoint-ssl-verify-full" - assert result[0].resource_arn == "test-endpoint-ssl-verify-full" + assert result[0].resource_arn == endpoint_arn assert result[0].region == AWS_REGION_US_EAST_1 assert ( result[0].status_extended == "DMS Endpoint test-endpoint-ssl-verify-full is using SSL with mode: verify-full." ) + assert result[0].resource_tags == [ + {"Key": "Name", "Value": "test-endpoint-ssl-verify-full"} + ] diff --git a/tests/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled_test.py b/tests/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled_test.py index 1bc9710078..1022a54bbd 100644 --- a/tests/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled_test.py +++ b/tests/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled_test.py @@ -40,6 +40,7 @@ class Test_dms_instance_minor_version_upgrade_enabled: auto_minor_version_upgrade=False, multi_az=True, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -62,6 +63,9 @@ class Test_dms_instance_minor_version_upgrade_enabled: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + {"Key": "Name", "Value": DMS_INSTANCE_NAME} + ] def test_dms_instance_minor_version_upgrade_enabled(self): dms_client = mock.MagicMock @@ -76,6 +80,7 @@ class Test_dms_instance_minor_version_upgrade_enabled: auto_minor_version_upgrade=True, multi_az=True, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -98,3 +103,6 @@ class Test_dms_instance_minor_version_upgrade_enabled: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + {"Key": "Name", "Value": DMS_INSTANCE_NAME} + ] diff --git a/tests/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az_test.py b/tests/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az_test.py index 3fd5eb6022..3ba0c3befc 100644 --- a/tests/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az_test.py +++ b/tests/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az_test.py @@ -40,6 +40,7 @@ class Test_dms_instance_multi_az: auto_minor_version_upgrade=False, multi_az=False, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -62,6 +63,9 @@ class Test_dms_instance_multi_az: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + {"Key": "Name", "Value": DMS_INSTANCE_NAME} + ] def test_dms_instance_multi_az_enabled(self): dms_client = mock.MagicMock @@ -76,6 +80,7 @@ class Test_dms_instance_multi_az: auto_minor_version_upgrade=True, multi_az=True, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -98,3 +103,6 @@ class Test_dms_instance_multi_az: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + {"Key": "Name", "Value": DMS_INSTANCE_NAME} + ] diff --git a/tests/providers/aws/services/dms/dms_instance_no_public_access/dms_no_public_access_test.py b/tests/providers/aws/services/dms/dms_instance_no_public_access/dms_no_public_access_test.py index 0b5a447fdc..82e54bbda3 100644 --- a/tests/providers/aws/services/dms/dms_instance_no_public_access/dms_no_public_access_test.py +++ b/tests/providers/aws/services/dms/dms_instance_no_public_access/dms_no_public_access_test.py @@ -45,7 +45,6 @@ class Test_dms_instance_no_public_access: result = check.execute() assert len(result) == 0 - @mock_aws def test_dms_private(self): dms_client = mock.MagicMock dms_client.instances = [] @@ -60,6 +59,7 @@ class Test_dms_instance_no_public_access: auto_minor_version_upgrade=False, multi_az=False, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -88,8 +88,13 @@ class Test_dms_instance_no_public_access: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": DMS_INSTANCE_NAME, + } + ] - @mock_aws def test_dms_public(self): dms_client = mock.MagicMock dms_client.instances = [] @@ -104,6 +109,7 @@ class Test_dms_instance_no_public_access: auto_minor_version_upgrade=False, multi_az=False, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -132,6 +138,12 @@ class Test_dms_instance_no_public_access: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": DMS_INSTANCE_NAME, + } + ] @mock_aws def test_dms_public_with_public_sg(self): @@ -163,6 +175,7 @@ class Test_dms_instance_no_public_access: auto_minor_version_upgrade=False, multi_az=False, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -201,6 +214,12 @@ class Test_dms_instance_no_public_access: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": DMS_INSTANCE_NAME, + } + ] @mock_aws def test_dms_public_with_filtered_sg(self): @@ -232,6 +251,7 @@ class Test_dms_instance_no_public_access: auto_minor_version_upgrade=False, multi_az=False, region=AWS_REGION_US_EAST_1, + tags=[{"Key": "Name", "Value": DMS_INSTANCE_NAME}], ) ) @@ -270,3 +290,9 @@ class Test_dms_instance_no_public_access: assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == DMS_INSTANCE_NAME assert result[0].resource_arn == DMS_INSTANCE_ARN + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": DMS_INSTANCE_NAME, + } + ] diff --git a/tests/providers/aws/services/dms/dms_service_test.py b/tests/providers/aws/services/dms/dms_service_test.py index 35d0f09f31..7a13e8a872 100644 --- a/tests/providers/aws/services/dms/dms_service_test.py +++ b/tests/providers/aws/services/dms/dms_service_test.py @@ -14,6 +14,9 @@ DMS_INSTANCE_ARN = ( ) KMS_KEY_ID = f"arn:aws:kms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:key/abcdabcd-1234-abcd-1234-abcdabcdabcd" +DMS_ENDPOINT_NAME = "dms-endpoint" +DMS_ENDPOINT_ARN = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:{DMS_ENDPOINT_NAME}" + # Mocking Access Analyzer Calls make_api_call = botocore.client.BaseClient._make_api_call @@ -34,6 +37,31 @@ def mock_make_api_call(self, operation_name, kwargs): }, ] } + elif operation_name == "DescribeEndpoints": + return { + "Endpoints": [ + { + "EndpointIdentifier": DMS_ENDPOINT_NAME, + "EndpointArn": DMS_ENDPOINT_ARN, + "SslMode": "require", + } + ] + } + elif operation_name == "ListTagsForResource": + if kwargs["ResourceArn"] == DMS_INSTANCE_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "rep-instance"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + elif kwargs["ResourceArn"] == DMS_ENDPOINT_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "dms-endpoint"}, + {"Key": "Owner", "Value": "admin"}, + ] + } return make_api_call(self, operation_name, kwargs) @@ -84,3 +112,25 @@ class Test_DMS_Service: assert dms.instances[0].auto_minor_version_upgrade assert dms.instances[0].multi_az assert dms.instances[0].security_groups == [] + + # Test DMS Endpoints + def test_describe_endpoints(self): + aws_provider = set_mocked_aws_provider() + dms = DMS(aws_provider) + + assert len(dms.endpoints) == 1 + assert dms.endpoints[DMS_ENDPOINT_ARN].id == DMS_ENDPOINT_NAME + assert dms.endpoints[DMS_ENDPOINT_ARN].ssl_mode == "require" + + def test_list_tags(self): + aws_provider = set_mocked_aws_provider() + dms = DMS(aws_provider) + + assert dms.instances[0].tags == [ + {"Key": "Name", "Value": "rep-instance"}, + {"Key": "Owner", "Value": "admin"}, + ] + assert dms.endpoints[DMS_ENDPOINT_ARN].tags == [ + {"Key": "Name", "Value": "dms-endpoint"}, + {"Key": "Owner", "Value": "admin"}, + ]