diff --git a/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/__init__.py b/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.metadata.json b/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.metadata.json new file mode 100644 index 0000000000..533df66f11 --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "rds_instance_certificate_expiration", + "CheckTitle": "Ensure that the SSL/TLS certificates configured for your Amazon RDS are not expired.", + "CheckType": [], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rds:region:account-id:db-instance", + "Severity": "high", + "ResourceType": "AwsRdsDbInstance", + "Description": "To maintain Amazon RDS database security and avoid interruption of the applications that are using RDS and/or Aurora databases, rotate the required SSL/TLS certificates and update the deprecated Certificate Authority (CA) certificates at the Amazon RDS instance level.", + "Risk": "Interruption of application if the certificate expires.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL-certificate-rotation.html", + "Remediation": { + "Code": { + "CLI": "aws rds modify-db-instance --region us-east-1 --db-instance-identifier cc-project5-mysql-database --ca-certificate-identifier \"rds-ca-2019\" --apply-immediately", + "NativeIaC": "", + "Other": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/RDS/rotate-rds-certificates.html", + "Terraform": "" + }, + "Recommendation": { + "Text": "To maintain Amazon RDS database security and avoid interruption of the applications that are using RDS and/or Aurora databases, rotate the required SSL/TLS certificates and update the deprecated Certificate Authority (CA) certificates at the Amazon RDS instance level.", + "Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL-certificate-rotation.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.py b/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.py new file mode 100644 index 0000000000..5eb5fc810b --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration.py @@ -0,0 +1,117 @@ +from datetime import datetime + +from dateutil import parser, relativedelta +from pytz import utc + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.rds.rds_client import rds_client + + +class rds_instance_certificate_expiration(Check): + # RDS Certificates with an expiration greater than 3 months the check will PASS with a severity of informational if greater than 6 months and a severity of low if between 3 and 6 months. + # RDS Certificates that expires in less than 3 months the check will FAIL with a severity of medium. + # RDS Certificates that expires in less than a month the check will FAIL with a severity of high. + # RDS Certificates that are expired the check will FAIL with a severity of critical. + def execute(self): + findings = [] + for db_instance in rds_client.db_instances: + 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 = "FAIL" + report.check_metadata.Severity = "critical" + report.status_extended = ( + f"RDS Instance {db_instance.id} certificate has expired." + ) + + # Check only RDS DB instances that support parameter group encryption + for cert in db_instance.cert: + if cert["CustomerOverride"] == 0: + valid_till = parser.parse(cert["ValidTill"]) + if valid_till > datetime.now(utc) + relativedelta.relativedelta( + months=6 + ): + report.status = "PASS" + report.check_metadata.Severity = "informational" + report.status_extended = f"RDS Instance {db_instance.id} certificate has over 6 months of validity left." + elif valid_till < datetime.now(utc) + relativedelta.relativedelta( + months=6 + ) and valid_till > datetime.now(utc) + relativedelta.relativedelta( + months=3 + ): + report.status = "PASS" + report.check_metadata.Severity = "low" + report.status_extended = f"RDS Instance {db_instance.id} certificate has between 3 and 6 months of validity." + elif valid_till < datetime.now(utc) + relativedelta.relativedelta( + months=3 + ) and valid_till > datetime.now(utc) + relativedelta.relativedelta( + months=1 + ): + report.status = "FAIL" + report.check_metadata.Severity = "medium" + report.status_extended = f"RDS Instance {db_instance.id} certificate less than 3 months of validity." + elif valid_till < datetime.now(utc) + relativedelta.relativedelta( + months=1 + ) and valid_till > datetime.now(utc): + report.status = "FAIL" + report.check_metadata.Severity = "high" + report.status_extended = f"RDS Instance {db_instance.id} certificate less than 1 month of validity." + else: + report.status = "FAIL" + report.check_metadata.Severity = "critical" + report.status_extended = ( + f"RDS Instance {db_instance.id} certificate has expired." + ) + else: + customer_override_valid_till = parser.parse( + cert["CustomerOverrideValidTill"] + ) + if customer_override_valid_till > datetime.now( + utc + ) + relativedelta.relativedelta(months=6): + report.status = "PASS" + report.check_metadata.Severity = "informational" + report.status_extended = f"RDS Instance {db_instance.id} custom certificate has over 6 months of validity left." + elif customer_override_valid_till < datetime.now( + utc + ) + relativedelta.relativedelta( + months=6 + ) and customer_override_valid_till > datetime.now( + utc + ) + relativedelta.relativedelta( + months=3 + ): + report.status = "PASS" + report.check_metadata.Severity = "low" + report.status_extended = f"RDS Instance {db_instance.id} custom certificate has between 3 and 6 months of validity." + elif customer_override_valid_till < datetime.now( + utc + ) + relativedelta.relativedelta( + months=3 + ) and customer_override_valid_till > datetime.now( + utc + ) + relativedelta.relativedelta( + months=1 + ): + report.status = "FAIL" + report.check_metadata.Severity = "medium" + report.status_extended = f"RDS Instance {db_instance.id} custom certificate less than 3 months of validity." + elif customer_override_valid_till < datetime.now( + utc + ) + relativedelta.relativedelta( + months=1 + ) and customer_override_valid_till > datetime.now( + utc + ): + report.status = "FAIL" + report.check_metadata.Severity = "high" + report.status_extended = f"RDS Instance {db_instance.id} custom certificate less than 1 month of validity." + else: + report.status = "FAIL" + report.check_metadata.Severity = "critical" + report.status_extended = f"RDS Instance {db_instance.id} custom certificate has expired." + 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 05632fa200..2d208aa026 100644 --- a/prowler/providers/aws/services/rds/rds_service.py +++ b/prowler/providers/aws/services/rds/rds_service.py @@ -19,6 +19,7 @@ class RDS(AWSService): self.db_engines = {} self.db_cluster_snapshots = [] self.__threading_call__(self.__describe_db_instances__) + self.__threading_call__(self.__describe_db_certificate__) self.__threading_call__(self.__describe_db_parameters__) self.__threading_call__(self.__describe_db_snapshots__) self.__threading_call__(self.__describe_db_snapshot_attributes__) @@ -80,6 +81,7 @@ class RDS(AWSService): replica_source=instance.get( "ReadReplicaSourceDBInstanceIdentifier" ), + ca_cert=instance.get("CACertificateIdentifier"), ) ) except Exception as error: @@ -107,6 +109,25 @@ class RDS(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __describe_db_certificate__(self, regional_client): + logger.info("RDS - Describe DB Certificate...") + try: + for instance in self.db_instances: + if instance.region == regional_client.region: + describe_db_certificates_paginator = regional_client.get_paginator( + "describe_certificates" + ) + for page in describe_db_certificates_paginator.paginate( + CertificateIdentifier=instance.ca_cert + ): + for certificate in page["Certificates"]: + instance.cert.append(certificate) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + def __describe_db_snapshots__(self, regional_client): logger.info("RDS - Describe Snapshots...") try: @@ -309,6 +330,8 @@ class DBInstance(BaseModel): region: str tags: Optional[list] = [] replica_source: Optional[str] + ca_cert: Optional[str] + cert: list[dict] = [] class DBCluster(BaseModel): diff --git a/tests/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration_test.py b/tests/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration_test.py new file mode 100644 index 0000000000..798f09b178 --- /dev/null +++ b/tests/providers/aws/services/rds/rds_instance_certificate_expiration/rds_instance_certificate_expiration_test.py @@ -0,0 +1,659 @@ +from datetime import datetime +from unittest import mock + +import botocore +from dateutil import relativedelta +from moto import mock_aws +from pytz import utc + +from prowler.providers.aws.services.rds.rds_service import DBInstance + +AWS_ACCOUNT_NUMBER_CON = "123456789012" +AWS_REGION = "us-east-1" + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "DescribeDBEngineVersions": + return { + "DBEngineVersions": [ + { + "Engine": "mysql", + "EngineVersion": "8.0.32", + "DBEngineDescription": "description", + "DBEngineVersionDescription": "description", + }, + ] + } + return make_api_call(self, operation_name, kwarg) + + +@mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +class Test_rds_instance_certificate_expiration: + @mock_aws + def test_rds_no_instances(self): + rds_client = mock.MagicMock + rds_client.db_instances = [] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_rds_certificate_expired(self): + + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) - relativedelta.relativedelta(months=7) + customer_override_valid = datetime.now(utc) - relativedelta.relativedelta( + months=7 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": False, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].check_metadata.Severity == "critical" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 certificate has expired." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_rds_certificate_not_expired(self): + + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=7) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=7 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": False, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].check_metadata.Severity == "informational" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 certificate has over 6 months of validity left." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_rds_certificate_between_three_and_six_months(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=4) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=4 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": False, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].check_metadata.Severity == "low" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 certificate has between 3 and 6 months of validity." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_rds_certificate_less_than_three_months(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=3) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=3 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": False, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].check_metadata.Severity == "medium" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 certificate less than 3 months of validity." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_custom_rds_certificate_over_six_months(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=7) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=7 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": True, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].check_metadata.Severity == "informational" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 custom certificate has over 6 months of validity left." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_custom_rds_certificate_between_three_and_six_months(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=4) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=4 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": True, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].check_metadata.Severity == "low" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 custom certificate has between 3 and 6 months of validity." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_custom_rds_certificate_less_than_three_months(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(months=3) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + months=3 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": True, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].check_metadata.Severity == "medium" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 custom certificate less than 3 months of validity." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_custom_rds_certificate_less_than_one_month(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) + relativedelta.relativedelta(weeks=2) + customer_override_valid = datetime.now(utc) + relativedelta.relativedelta( + weeks=2 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": True, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].check_metadata.Severity == "high" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 custom certificate less than 1 month of validity." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] + + @mock_aws + def test_custom_rds_certificate_expired(self): + valid_from = datetime.now(utc) - relativedelta.relativedelta(months=7) + valid_till = datetime.now(utc) - relativedelta.relativedelta(months=7) + customer_override_valid = datetime.now(utc) - relativedelta.relativedelta( + months=7 + ) + + rds_client = mock.MagicMock + rds_client.db_instances = [] + rds_client.db_instances = [ + DBInstance( + id="db-master-1", + arn=f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1", + engine="aurora-postgresql", + engine_version="aurora14", + status="available", + public=False, + encrypted=True, + deletion_protection=True, + auto_minor_version_upgrade=False, + multi_az=True, + region=AWS_REGION, + ca_cert="rds-ca-rsa2048-g1", + cert=[ + { + "CertificateIdentifier": "rds-ca-rsa2048-g1", + "CertificateType": "CA", + "ValidFrom": f"{valid_from}", + "ValidTill": f"{valid_till}", + "CustomerOverride": True, + "CustomerOverrideValidTill": f"{customer_override_valid}", + } + ], + ) + ] + + with mock.patch( + "prowler.providers.aws.services.rds.rds_service.RDS", + new=rds_client, + ), mock.patch( + "prowler.providers.aws.services.rds.rds_client.rds_client", + new=rds_client, + ): + # Test Check + from prowler.providers.aws.services.rds.rds_instance_certificate_expiration.rds_instance_certificate_expiration import ( + rds_instance_certificate_expiration, + ) + + check = rds_instance_certificate_expiration() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].check_metadata.Severity == "critical" + assert ( + result[0].status_extended + == "RDS Instance db-master-1 custom certificate has expired." + ) + assert result[0].resource_id == "db-master-1" + assert result[0].region == AWS_REGION + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION}:{AWS_ACCOUNT_NUMBER_CON}:db:db-master-1" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/rds/rds_service_test.py b/tests/providers/aws/services/rds/rds_service_test.py index bee0102e74..0695249ac7 100644 --- a/tests/providers/aws/services/rds/rds_service_test.py +++ b/tests/providers/aws/services/rds/rds_service_test.py @@ -1,3 +1,4 @@ +from datetime import datetime from unittest.mock import patch import botocore @@ -154,6 +155,33 @@ class Test_RDS_Service: if parameter["ParameterName"] == "rds.force_ssl": assert parameter["ParameterValue"] == "1" + @mock_aws + def test__describe_db_certificate__(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_parameter_group( + DBParameterGroupName="test", + DBParameterGroupFamily="default.postgres9.3", + Description="test parameter group", + ) + conn.create_db_instance( + DBInstanceIdentifier="db-master-1", + AllocatedStorage=10, + Engine="postgres", + DBName="staging-postgres", + DBInstanceClass="db.m1.small", + DBParameterGroupName="test", + CACertificateIdentifier="rds-cert-2015", + ) + + # RDS client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + rds = RDS(aws_provider) + assert len(rds.db_instances) == 1 + assert rds.db_instances[0].id == "db-master-1" + assert rds.db_instances[0].region == AWS_REGION_US_EAST_1 + for cert in rds.db_instances[0].cert: + assert cert["ValidTill"] < datetime.now() + # Test RDS Describe DB Snapshots @mock_aws def test__describe_db_snapshots__(self):