diff --git a/prowler/providers/aws/services/dms/__init__.py b/prowler/providers/aws/services/dms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dms/dms_client.py b/prowler/providers/aws/services/dms/dms_client.py new file mode 100644 index 0000000000..72e39d74b9 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.dms.dms_service import DMS +from prowler.providers.common.provider import Provider + +dms_client = DMS(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/__init__.py b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json new file mode 100644 index 0000000000..4b32256724 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "dms_instance_minor_version_upgrade_enabled", + "CheckTitle": "Ensure DMS instances have auto minor version upgrade enabled.", + "CheckType": [], + "ServiceName": "dms", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "Severity": "medium", + "ResourceType": "AwsDmsReplicationInstance", + "Description": "Ensure DMS instances have auto minor version upgrade enabled.", + "Risk": "Ensure that your Amazon Database Migration Service (DMS) replication instances have the Auto Minor Version Upgrade feature enabled in order to receive automatically minor engine upgrades.", + "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-6", + "Remediation": { + "Code": { + "CLI": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#", + "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#", + "Other": "", + "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/auto-minor-version-upgrade.html#" + }, + "Recommendation": { + "Text": "Enable auto minor version upgrade for all DMS replication instances.", + "Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-6" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} 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 new file mode 100644 index 0000000000..a63fbe7118 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled.py @@ -0,0 +1,21 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.dms.dms_client import dms_client + + +class dms_instance_minor_version_upgrade_enabled(Check): + def execute(self): + findings = [] + for instance in dms_client.instances: + report = Check_Report_AWS(self.metadata()) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = instance.arn + 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: + report.status = "PASS" + report.status_extended = f"DMS Replication Instance {instance.id} has auto minor version upgrade enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/dms/dms_instance_multi_az/__init__.py b/prowler/providers/aws/services/dms/dms_instance_multi_az/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.metadata.json b/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.metadata.json new file mode 100644 index 0000000000..392068737b --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "dms_instance_multi_az_enabled", + "CheckTitle": "Ensure DMS instances have multi az enabled.", + "CheckType": [], + "ServiceName": "dms", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "Severity": "medium", + "ResourceType": "AwsDmsReplicationInstance", + "Description": "Ensure DMS instances have multi az enabled.", + "Risk": "Ensure that your Amazon Database Migration Service (DMS) replication instances are using Multi-AZ deployment configurations to provide High Availability (HA) through automatic failover to standby replicas in the event of a failure such as an Availability Zone (AZ) outage, an internal hardware or network outage, a software failure or in case of a planned maintenance session.", + "RelatedUrl": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", + "Remediation": { + "Code": { + "CLI": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", + "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#", + "Other": "", + "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#" + }, + "Recommendation": { + "Text": "Enable multi az for all DMS replication instances.", + "Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/multi-az.html#" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.py b/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.py new file mode 100644 index 0000000000..43ddf53f31 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.dms.dms_client import dms_client + + +class dms_instance_multi_az(Check): + def execute(self): + findings = [] + for instance in dms_client.instances: + report = Check_Report_AWS(self.metadata()) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = instance.arn + report.status = "FAIL" + report.status_extended = f"DMS Replication Instance {instance.id} does not have multi az enabled." + if instance.multi_az: + report.status = "PASS" + report.status_extended = ( + f"DMS Replication Instance {instance.id} has multi az enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/dms/dms_instance_no_public_access/__init__.py b/prowler/providers/aws/services/dms/dms_instance_no_public_access/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json new file mode 100644 index 0000000000..8387c0c2d2 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "dms_instance_not_public", + "CheckTitle": "Ensure DMS instances are not publicly accessible.", + "CheckType": [], + "ServiceName": "dms", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rdmsds:region:account-id:rep", + "Severity": "critical", + "ResourceType": "AwsDmsReplicationInstance", + "Description": "Ensure DMS instances are not publicly accessible.", + "Risk": "Ensure that your Amazon Database Migration Service (DMS) are not publicly accessible from the Internet in order to avoid exposing private data and minimize security risks. A DMS replication instance should have a private IP address and the Publicly Accessible feature disabled when both the source and the target databases are in the same network that is connected to the instance's VPC through a VPN, VPC peering connection, or using an AWS Direct Connect dedicated connection.", + "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-1", + "Remediation": { + "Code": { + "CLI": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#", + "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#", + "Other": "", + "Terraform": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/DMS/publicly-accessible.html#" + }, + "Recommendation": { + "Text": "Restrict DMS Replication instances security groups to only required IPs, or re-create these instances that is only accessible privately.", + "Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-1" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} 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 new file mode 100644 index 0000000000..f4f4cd79df --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_instance_no_public_access/dms_instance_no_public_access.py @@ -0,0 +1,39 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.dms.dms_client import dms_client +from prowler.providers.aws.services.ec2.ec2_client import ec2_client +from prowler.providers.aws.services.ec2.lib.security_groups import check_security_group + + +class dms_instance_no_public_access(Check): + def execute(self): + findings = [] + for instance in dms_client.instances: + report = Check_Report_AWS(self.metadata()) + report.region = instance.region + report.resource_id = instance.id + report.resource_arn = instance.arn + report.status = "PASS" + report.status_extended = ( + f"DMS Replication Instance {instance.id} is not publicly accessible." + ) + if instance.public: + report.status_extended = f"DMS Replication Instance {instance.id} is set as publicly accessible, but is not publicly exposed." + # Check if any DB Instance Security Group is publicly open + if instance.security_groups: + report.status = "PASS" + report.status_extended = f"DMS Replication Instance {instance.id} is set as publicly accessible but filtered with security groups." + for security_group in ec2_client.security_groups: + if security_group.id in instance.security_groups: + for ingress_rule in security_group.ingress_rules: + if check_security_group( + ingress_rule, + "-1", + ports=None, + any_address=True, + ): + report.status = "FAIL" + report.status_extended = f"DMS Replication Instance {instance.id} is set as publicly accessible and security group {security_group.name} ({security_group.id}) is open to the Internet." + break + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/dms/dms_service.py b/prowler/providers/aws/services/dms/dms_service.py new file mode 100644 index 0000000000..90fcd71b11 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_service.py @@ -0,0 +1,62 @@ +from pydantic import BaseModel + +from prowler.lib.logger import logger +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__ + super().__init__(__class__.__name__, provider) + self.instances = [] + self.__threading_call__(self.__describe_replication_instances__) + + def __describe_replication_instances__(self, regional_client): + logger.info("DMS - Describing DMS Replication Instances...") + try: + describe_replication_instances_paginator = regional_client.get_paginator( + "describe_replication_instances" + ) + for page in describe_replication_instances_paginator.paginate(): + for instance in page["ReplicationInstances"]: + arn = instance["ReplicationInstanceArn"] + if not self.audit_resources or ( + is_resource_filtered(arn, self.audit_resources) + ): + self.instances.append( + RepInstance( + id=instance["ReplicationInstanceIdentifier"], + arn=arn, + status=instance["ReplicationInstanceStatus"], + public=instance["PubliclyAccessible"], + kms_key=instance["KmsKeyId"], + auto_minor_version_upgrade=instance[ + "AutoMinorVersionUpgrade" + ], + security_groups=[ + sg["VpcSecurityGroupId"] + for sg in instance["VpcSecurityGroups"] + if sg["Status"] == "active" + ], + multi_az=instance["MultiAZ"], + region=regional_client.region, + ) + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class RepInstance(BaseModel): + id: str + arn: str + status: str + public: bool + kms_key: str + auto_minor_version_upgrade: bool + security_groups: list[str] = [] + multi_az: bool + region: str 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 new file mode 100644 index 0000000000..1bc9710078 --- /dev/null +++ b/tests/providers/aws/services/dms/dms_instance_minor_version_upgrade_enabled/dms_instance_minor_version_upgrade_enabled_test.py @@ -0,0 +1,100 @@ +from unittest import mock + +from prowler.providers.aws.services.dms.dms_service import RepInstance +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 + +DMS_INSTANCE_NAME = "rep-instance" +DMS_INSTANCE_ARN = ( + f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rep:{DMS_INSTANCE_NAME}" +) +KMS_KEY_ID = f"arn:aws:kms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:key/abcdabcd-1234-abcd-1234-abcdabcdabcd" + + +class Test_dms_instance_minor_version_upgrade_enabled: + def test_dms_no_instances(self): + dms_client = mock.MagicMock + dms_client.instances = [] + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_minor_version_upgrade_enabled.dms_instance_minor_version_upgrade_enabled import ( + dms_instance_minor_version_upgrade_enabled, + ) + + check = dms_instance_minor_version_upgrade_enabled() + result = check.execute() + assert len(result) == 0 + + def test_dms_minor_version_upgrade_not_enabled(self): + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION_US_EAST_1, + ) + ) + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_minor_version_upgrade_enabled.dms_instance_minor_version_upgrade_enabled import ( + dms_instance_minor_version_upgrade_enabled, + ) + + check = dms_instance_minor_version_upgrade_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} does not have auto 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 + + def test_dms_instance_minor_version_upgrade_enabled(self): + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=True, + multi_az=True, + region=AWS_REGION_US_EAST_1, + ) + ) + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_minor_version_upgrade_enabled.dms_instance_minor_version_upgrade_enabled import ( + dms_instance_minor_version_upgrade_enabled, + ) + + check = dms_instance_minor_version_upgrade_enabled() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} has auto 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 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 new file mode 100644 index 0000000000..d0e92a8e69 --- /dev/null +++ b/tests/providers/aws/services/dms/dms_instance_multi_az/dms_instance_multi_az_test.py @@ -0,0 +1,100 @@ +from unittest import mock + +from prowler.providers.aws.services.dms.dms_service import RepInstance +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 + +DMS_INSTANCE_NAME = "rep-instance" +DMS_INSTANCE_ARN = ( + f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rep:{DMS_INSTANCE_NAME}" +) +KMS_KEY_ID = f"arn:aws:kms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:key/abcdabcd-1234-abcd-1234-abcdabcdabcd" + + +class Test_dms_instance_multi_az: + def test_dms_no_instances(self): + dms_client = mock.MagicMock + dms_client.instances = [] + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_multi_az.dms_instance_multi_az import ( + dms_instance_multi_az, + ) + + check = dms_instance_multi_az() + result = check.execute() + assert len(result) == 0 + + def test_dms_instance_multi_az_not_enabled(self): + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=False, + region=AWS_REGION_US_EAST_1, + ) + ) + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_multi_az.dms_instance_multi_az import ( + dms_instance_multi_az, + ) + + check = dms_instance_multi_az() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} does not have multi az 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 + + def test_dms_instance_multi_az_enabled(self): + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=True, + multi_az=True, + region=AWS_REGION_US_EAST_1, + ) + ) + + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_multi_az.dms_instance_multi_az import ( + dms_instance_multi_az, + ) + + check = dms_instance_multi_az() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} has multi az 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 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 new file mode 100644 index 0000000000..0b5a447fdc --- /dev/null +++ b/tests/providers/aws/services/dms/dms_instance_no_public_access/dms_no_public_access_test.py @@ -0,0 +1,272 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from prowler.providers.aws.services.dms.dms_service import RepInstance +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +DMS_INSTANCE_NAME = "rep-instance" +DMS_INSTANCE_ARN = ( + f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rep:{DMS_INSTANCE_NAME}" +) +KMS_KEY_ID = f"arn:aws:kms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:key/abcdabcd-1234-abcd-1234-abcdabcdabcd" + + +class Test_dms_instance_no_public_access: + def test_dms_no_instances(self): + dms_client = mock.MagicMock + dms_client.instances = [] + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_instance_no_public_access.rds_instance_no_public_access.ec2_client", + new=EC2(aws_provider), + ): + from prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access import ( + dms_instance_no_public_access, + ) + + check = 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 = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=False, + security_groups=[], + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=False, + region=AWS_REGION_US_EAST_1, + ) + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access import ( + dms_instance_no_public_access, + ) + + check = dms_instance_no_public_access() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} is not publicly accessible." + ) + 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 + + @mock_aws + def test_dms_public(self): + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + security_groups=[], + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=False, + region=AWS_REGION_US_EAST_1, + ) + ) + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ): + from prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access import ( + dms_instance_no_public_access, + ) + + check = dms_instance_no_public_access() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} is set as publicly accessible, but is not publicly exposed." + ) + 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 + + @mock_aws + def test_dms_public_with_public_sg(self): + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + ec2_client.create_vpc(CidrBlock="10.0.0.0/16") + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "-1", + "IpRanges": [{"CidrIp": "0.0.0.0/0"}], + } + ], + ) + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + security_groups=[default_sg_id], + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=False, + region=AWS_REGION_US_EAST_1, + ) + ) + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + aws_provider.audit_metadata.expected_checks = [ + "ec2_securitygroup_allow_ingress_from_internet_to_any_port" + ] + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ), mock.patch( + "prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access.ec2_client", + new=EC2(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access import ( + dms_instance_no_public_access, + ) + + check = dms_instance_no_public_access() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} is set as publicly accessible and security group default ({default_sg_id}) is open to the Internet." + ) + 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 + + @mock_aws + def test_dms_public_with_filtered_sg(self): + ec2_client = client("ec2", region_name=AWS_REGION_US_EAST_1) + ec2_client.create_vpc(CidrBlock="10.0.0.0/16") + default_sg = ec2_client.describe_security_groups(GroupNames=["default"])[ + "SecurityGroups" + ][0] + default_sg_id = default_sg["GroupId"] + ec2_client.authorize_security_group_ingress( + GroupId=default_sg_id, + IpPermissions=[ + { + "IpProtocol": "-1", + "IpRanges": [{"CidrIp": "123.123.123.123/32"}], + } + ], + ) + dms_client = mock.MagicMock + dms_client.instances = [] + dms_client.instances.append( + RepInstance( + id=DMS_INSTANCE_NAME, + arn=DMS_INSTANCE_ARN, + status="available", + public=True, + security_groups=[default_sg_id], + kms_key=KMS_KEY_ID, + auto_minor_version_upgrade=False, + multi_az=False, + region=AWS_REGION_US_EAST_1, + ) + ) + + from prowler.providers.aws.services.ec2.ec2_service import EC2 + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + aws_provider.audit_metadata.expected_checks = [ + "ec2_securitygroup_allow_ingress_from_internet_to_any_port" + ] + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.dms.dms_service.DMS", + new=dms_client, + ), mock.patch( + "prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access.ec2_client", + new=EC2(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_instance_no_public_access.dms_instance_no_public_access import ( + dms_instance_no_public_access, + ) + + check = dms_instance_no_public_access() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"DMS Replication Instance {DMS_INSTANCE_NAME} is set as publicly accessible but filtered with security groups." + ) + 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 diff --git a/tests/providers/aws/services/dms/dms_service_test.py b/tests/providers/aws/services/dms/dms_service_test.py new file mode 100644 index 0000000000..35d0f09f31 --- /dev/null +++ b/tests/providers/aws/services/dms/dms_service_test.py @@ -0,0 +1,86 @@ +import botocore +from mock import patch + +from prowler.providers.aws.services.dms.dms_service import DMS +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +DMS_INSTANCE_NAME = "rep-instance" +DMS_INSTANCE_ARN = ( + f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rep:{DMS_INSTANCE_NAME}" +) +KMS_KEY_ID = f"arn:aws:kms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:key/abcdabcd-1234-abcd-1234-abcdabcdabcd" + +# Mocking Access Analyzer Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwargs): + if operation_name == "DescribeReplicationInstances": + return { + "ReplicationInstances": [ + { + "ReplicationInstanceIdentifier": DMS_INSTANCE_NAME, + "ReplicationInstanceStatus": "available", + "AutoMinorVersionUpgrade": True, + "PubliclyAccessible": True, + "ReplicationInstanceArn": DMS_INSTANCE_ARN, + "MultiAZ": True, + "VpcSecurityGroups": [], + "KmsKeyId": KMS_KEY_ID, + }, + ] + } + + return make_api_call(self, operation_name, kwargs) + + +def mock_generate_regional_clients(provider, service): + regional_client = provider._session.current_session.client( + service, region_name=AWS_REGION_US_EAST_1 + ) + regional_client.region = AWS_REGION_US_EAST_1 + return {AWS_REGION_US_EAST_1: regional_client} + + +@patch( + "prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients", + new=mock_generate_regional_clients, +) +# Patch every AWS call using Boto3 +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +class Test_DMS_Service: + # Test DMS Service + def test_service(self): + aws_provider = set_mocked_aws_provider() + DMS(aws_provider) + + # Test DMS Client + def test_client(self): + aws_provider = set_mocked_aws_provider() + dms = DMS(aws_provider) + assert dms.client.__class__.__name__ == "DatabaseMigrationService" + + # Test DMS Account + def test_audited_account(self): + aws_provider = set_mocked_aws_provider() + dms = DMS(aws_provider) + assert dms.audited_account == AWS_ACCOUNT_NUMBER + + # Test DMS Replication Instances + def test_describe_rep_instances(self): + aws_provider = set_mocked_aws_provider() + dms = DMS(aws_provider) + + assert len(dms.instances) == 1 + assert dms.instances[0].id == DMS_INSTANCE_NAME + assert dms.instances[0].region == AWS_REGION_US_EAST_1 + assert dms.instances[0].status == "available" + assert dms.instances[0].public + assert dms.instances[0].kms_key == KMS_KEY_ID + assert dms.instances[0].auto_minor_version_upgrade + assert dms.instances[0].multi_az + assert dms.instances[0].security_groups == []