diff --git a/prowler/providers/aws/services/rds/rds_instance_non_default_port/__init__.py b/prowler/providers/aws/services/rds/rds_instance_non_default_port/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.metadata.json b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.metadata.json new file mode 100644 index 0000000000..84ff5db321 --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "rds_instance_non_default_port", + "CheckTitle": "Check if RDS instances are using non-default ports.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance", + "Severity": "low", + "ResourceType": "AwsRdsDbInstance", + "Description": "Checks if an instance uses a port other than the default port of the database engine. The control fails if the RDS instance uses the default port or a non-default port that is not configured in the security group.", + "Risk": "Using a default database port exposes the instance to potential security vulnerabilities, as attackers are more likely to target known, commonly-used ports. This may result in unauthorized access to the database or increased susceptibility to automated attacks.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html", + "Remediation": { + "Code": { + "CLI": "aws rds modify-db-instance --db-instance-identifier --port ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-23", + "Terraform": "" + }, + "Recommendation": { + "Text": "Modify the RDS instance to use a non-default port, and ensure that the security group permits access to the new port.", + "Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.DBInstance.Modifying.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py new file mode 100644 index 0000000000..9e2dda34ed --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port.py @@ -0,0 +1,38 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.rds.rds_client import rds_client + + +class rds_instance_non_default_port(Check): + def execute(self): + findings = [] + default_ports = { + 3306: ["mysql", "mariadb"], + 5432: ["postgres"], + 1521: ["oracle"], + 1433: ["sqlserver"], + 50000: ["db2"], + } + for db_instance_arn, db_instance in rds_client.db_instances.items(): + report = Check_Report_AWS(self.metadata()) + report.region = db_instance.region + report.resource_id = db_instance.id + report.resource_arn = db_instance_arn + report.resource_tags = db_instance.tags + report.status = "PASS" + report.status_extended = ( + f"RDS Instance {db_instance.id} is not using the default port " + f"{db_instance.port} for {db_instance.engine}." + ) + if db_instance.port in default_ports: + default_engines = default_ports[db_instance.port] + for default_engine in default_engines: + if default_engine in db_instance.engine.lower(): + report.status = "FAIL" + report.status_extended = ( + f"RDS Instance {db_instance.id} is using the default port " + f"{db_instance.port} for {db_instance.engine}." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/rds/rds_service.py b/prowler/providers/aws/services/rds/rds_service.py index b14dfc1bfe..c3c385a566 100644 --- a/prowler/providers/aws/services/rds/rds_service.py +++ b/prowler/providers/aws/services/rds/rds_service.py @@ -100,6 +100,7 @@ class RDS(AWSService): copy_tags_to_snapshot=instance.get( "CopyTagsToSnapshot" ), + port=instance.get("Endpoint", {}).get("Port"), vpc_id=instance.get("DBSubnetGroup", {}).get("VpcId"), ) except Exception as error: @@ -262,6 +263,7 @@ class RDS(AWSService): copy_tags_to_snapshot=cluster.get( "CopyTagsToSnapshot" ), + port=cluster.get("Port"), ) # We must use a unique value as the dict key to have unique keys self.db_clusters[db_cluster_arn] = db_cluster @@ -508,6 +510,7 @@ class DBInstance(BaseModel): ca_cert: Optional[str] cert: list[Certificate] = [] copy_tags_to_snapshot: Optional[bool] + port: Optional[int] vpc_id: Optional[str] @@ -533,6 +536,7 @@ class DBCluster(BaseModel): region: str tags: Optional[list] = [] copy_tags_to_snapshot: Optional[bool] + port: Optional[int] class DBSnapshot(BaseModel): diff --git a/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py b/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py new file mode 100644 index 0000000000..9959c85a42 --- /dev/null +++ b/tests/providers/aws/services/rds/rds_instance_non_default_port/rds_instance_non_default_port_test.py @@ -0,0 +1,248 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_rds_instance_non_default_port: + @mock_aws + def test_rds_no_instances(self): + from prowler.providers.aws.services.rds.rds_service import RDS + + 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.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + # Test del check + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_rds_instance_using_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-1", + AllocatedStorage=10, + Engine="postgres", + DBName="staging-postgres", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=5432, # Puerto por defecto para postgres + Tags=[{"Key": "test", "Value": "test"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + 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.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + # Test del check + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 is using the default port 5432 for postgres." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-1" + ) + assert result[0].resource_tags == [{"Key": "test", "Value": "test"}] + + @mock_aws + def test_rds_instance_using_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-2", + AllocatedStorage=10, + Engine="postgres", + DBName="production-postgres", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=5433, # Puerto no por defecto para postgres + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + 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.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + # Test del check + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Instance db-master-2 is not using the default port 5433 for postgres." + ) + assert result[0].resource_id == "db-master-2" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-2" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] + + @mock_aws + def test_rds_instance_mariadb_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-3", + AllocatedStorage=10, + Engine="mariadb", + DBName="staging-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3306, # Puerto por defecto para mariadb + Tags=[{"Key": "env", "Value": "staging"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + 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.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + # Test del check + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Instance db-master-3 is using the default port 3306 for mariadb." + ) + assert result[0].resource_id == "db-master-3" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-3" + ) + assert result[0].resource_tags == [{"Key": "env", "Value": "staging"}] + + @mock_aws + def test_rds_instance_mariadb_non_default_port(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_instance( + DBInstanceIdentifier="db-master-4", + AllocatedStorage=10, + Engine="mariadb", + DBName="production-mariadb", + DBInstanceClass="db.m1.small", + StorageEncrypted=True, + DeletionProtection=True, + PubliclyAccessible=True, + AutoMinorVersionUpgrade=True, + BackupRetentionPeriod=10, + Port=3307, # Puerto no por defecto para mariadb + Tags=[{"Key": "env", "Value": "production"}], + ) + + from prowler.providers.aws.services.rds.rds_service import RDS + + 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.rds.rds_instance_non_default_port.rds_instance_non_default_port.rds_client", + new=RDS(aws_provider), + ): + # Test del check + from prowler.providers.aws.services.rds.rds_instance_non_default_port.rds_instance_non_default_port import ( + rds_instance_non_default_port, + ) + + check = rds_instance_non_default_port() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Instance db-master-4 is not using the default port 3307 for mariadb." + ) + assert result[0].resource_id == "db-master-4" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-master-4" + ) + assert result[0].resource_tags == [ + {"Key": "env", "Value": "production"} + ] diff --git a/tests/providers/aws/services/rds/rds_service_test.py b/tests/providers/aws/services/rds/rds_service_test.py index 5008a1fc1c..11043a61e6 100644 --- a/tests/providers/aws/services/rds/rds_service_test.py +++ b/tests/providers/aws/services/rds/rds_service_test.py @@ -95,6 +95,7 @@ class Test_RDS_Service: {"Key": "test", "Value": "test"}, ], CopyTagsToSnapshot=True, + Port=5432, ) # RDS client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -121,6 +122,7 @@ class Test_RDS_Service: ] assert "test" in db_instance.parameter_groups assert db_instance.copy_tags_to_snapshot + assert db_instance.port == 5432 @mock_aws def test_describe_db_parameters(self): @@ -276,6 +278,7 @@ class Test_RDS_Service: {"Key": "test", "Value": "test"}, ], CopyTagsToSnapshot=True, + Port=5432, ) # RDS client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) @@ -306,6 +309,7 @@ class Test_RDS_Service: assert rds.db_clusters[db_cluster_arn].force_ssl == "0" assert rds.db_clusters[db_cluster_arn].require_secure_transport == "OFF" assert rds.db_clusters[db_cluster_arn].copy_tags_to_snapshot + assert rds.db_clusters[db_cluster_arn].port == 5432 # Test RDS Describe DB Cluster Snapshots @mock_aws