From 472aea6a913dcada04aacda020866074f9f59def Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:09:52 +0200 Subject: [PATCH] feat(aws): Add new check to ensure RDS db clusters copy tags to snapshots (#4846) --- .../__init__.py | 0 ...uster_copy_tags_to_snapshots.metadata.json | 30 +++++ .../rds_cluster_copy_tags_to_snapshots.py | 25 ++++ ...rds_cluster_copy_tags_to_snapshots_test.py | 127 ++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/__init__.py create mode 100644 prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.metadata.json create mode 100644 prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.py create mode 100644 tests/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots_test.py diff --git a/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/__init__.py b/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.metadata.json b/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.metadata.json new file mode 100644 index 0000000000..677ecbd1db --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "rds_cluster_copy_tags_to_snapshots", + "CheckTitle": "Check if RDS DB clusters have copy tags to snapshots enabled", + "CheckType": [], + "ServiceName": "rds", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:rds:region:account-id:db-cluster", + "Severity": "low", + "ResourceType": "AwsRdsDbCluster", + "Description": "Check if RDS DB clusters have copy tags to snapshots enabled, Aurora instances do not support this feature at instance level so those who are clustered will be scan by this check.", + "Risk": "If RDS clusters are not configured to copy tags to snapshots, it could lead to compliance issues as the snapshots will not inherit necessary metadata such as environment, owner, or purpose tags. This could result in inefficient tracking and management of RDS resources and their snapshots.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Tagging.html#USER_Tagging.CopyTags", + "Remediation": { + "Code": { + "CLI": "aws rds modify-db-cluster --db-cluster-identifier --copy-tags-to-snapshot", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-16", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure that the `CopyTagsToSnapshot` setting is enabled for all RDS clusters to propagate cluster tags to their snapshots for improved tracking and compliance.", + "Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Tagging.html#USER_Tagging.CopyTags" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.py b/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.py new file mode 100644 index 0000000000..3bdce163f1 --- /dev/null +++ b/prowler/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots.py @@ -0,0 +1,25 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.rds.rds_client import rds_client + + +class rds_cluster_copy_tags_to_snapshots(Check): + def execute(self): + findings = [] + for db_cluster_arn, db_cluster in rds_client.db_clusters.items(): + report = Check_Report_AWS(self.metadata()) + report.region = db_cluster.region + report.resource_id = db_cluster.id + report.resource_arn = db_cluster_arn + report.resource_tags = db_cluster.tags + if db_cluster.copy_tags_to_snapshot: + report.status = "PASS" + report.status_extended = ( + f"RDS Cluster {db_cluster.id} has copy tags to snapshots enabled." + ) + else: + report.status = "FAIL" + report.status_extended = f"RDS Cluster {db_cluster.id} does not have copy tags to snapshots enabled." + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots_test.py b/tests/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots_test.py new file mode 100644 index 0000000000..aa8e215df1 --- /dev/null +++ b/tests/providers/aws/services/rds/rds_cluster_copy_tags_to_snapshots/rds_cluster_copy_tags_to_snapshots_test.py @@ -0,0 +1,127 @@ +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_cluster_copy_tags_to_snapshots: + @mock_aws + def test_rds_no_clusters(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_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots import ( + rds_cluster_copy_tags_to_snapshots, + ) + + check = rds_cluster_copy_tags_to_snapshots() + result = check.execute() + assert len(result) == 0 + + @mock_aws + def test_rds_cluster_without_copy_tags(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="test-cluster", + AllocatedStorage=10, + Engine="mysql", + DatabaseName="staging-mysql", + DeletionProtection=True, + DBClusterParameterGroupName="test", + MasterUsername="test", + MasterUserPassword="password", + Tags=[], + CopyTagsToSnapshot=False, + ) + 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_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots import ( + rds_cluster_copy_tags_to_snapshots, + ) + + check = rds_cluster_copy_tags_to_snapshots() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "RDS Cluster test-cluster does not have copy tags to snapshots enabled." + ) + assert result[0].resource_id == "test-cluster" + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:test-cluster" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_rds_cluster_with_copy_tags(self): + conn = client("rds", region_name=AWS_REGION_US_EAST_1) + conn.create_db_cluster( + DBClusterIdentifier="test-cluster", + AllocatedStorage=10, + Engine="mysql", + DatabaseName="staging-mysql", + DeletionProtection=True, + DBClusterParameterGroupName="test", + MasterUsername="test", + MasterUserPassword="password", + Tags=[], + CopyTagsToSnapshot=True, + ) + 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_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots.rds_client", + new=RDS(aws_provider), + ): + from prowler.providers.aws.services.rds.rds_cluster_copy_tags_to_snapshots.rds_cluster_copy_tags_to_snapshots import ( + rds_cluster_copy_tags_to_snapshots, + ) + + check = rds_cluster_copy_tags_to_snapshots() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "RDS Cluster test-cluster has copy tags to snapshots enabled." + ) + assert result[0].resource_id == "test-cluster" + assert ( + result[0].resource_arn + == f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:test-cluster" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == []