From 53a4befb0172bbb806cb39f05179af77a705fc7f Mon Sep 17 00:00:00 2001 From: sansns-aws <107269923+sansns@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:13:16 -0500 Subject: [PATCH] feat(aws): add MemoryDB service (#5546) Co-authored-by: MrCloudSec --- .../aws/services/memorydb/__init__.py | 0 .../aws/services/memorydb/memorydb_client.py | 4 + .../__init__.py | 0 ..._auto_minor_version_upgrades.metadata.json | 30 ++++ ...ydb_cluster_auto_minor_version_upgrades.py | 22 +++ .../aws/services/memorydb/memorydb_service.py | 70 ++++++++++ ...luster_auto_minor_version_upgrades_test.py | 132 ++++++++++++++++++ .../memorydb/memorydb_service_test.py | 110 +++++++++++++++ 8 files changed, 368 insertions(+) create mode 100644 prowler/providers/aws/services/memorydb/__init__.py create mode 100644 prowler/providers/aws/services/memorydb/memorydb_client.py create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/__init__.py create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.metadata.json create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.py create mode 100644 prowler/providers/aws/services/memorydb/memorydb_service.py create mode 100644 tests/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades_test.py create mode 100644 tests/providers/aws/services/memorydb/memorydb_service_test.py diff --git a/prowler/providers/aws/services/memorydb/__init__.py b/prowler/providers/aws/services/memorydb/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/memorydb/memorydb_client.py b/prowler/providers/aws/services/memorydb/memorydb_client.py new file mode 100644 index 00000000000..85a218e7cd0 --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.memorydb.memorydb_service import MemoryDB +from prowler.providers.common.provider import Provider + +memorydb_client = MemoryDB(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/__init__.py b/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.metadata.json b/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.metadata.json new file mode 100644 index 00000000000..35b75bb5f8a --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "memorydb_cluster_auto_minor_version_upgrades", + "CheckTitle": "Ensure Memory DB clusters have minor version upgrade enabled.", + "CheckType": [], + "ServiceName": "memorydb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:memorydb:region:account-id:db-cluster", + "Severity": "medium", + "ResourceType": "AwsMemoryDb", + "Description": "Ensure Memory DB clusters have minor version upgrade enabled.", + "Risk": "Auto Minor Version Upgrade is a feature that you can enable to have your database automatically upgraded when a new minor database engine version is available. Minor version upgrades often patch security vulnerabilities and fix bugs and therefore should be applied.", + "RelatedUrl": "https://docs.aws.amazon.com/memorydb/latest/devguide/engine-versions.html", + "Remediation": { + "Code": { + "CLI": "aws memorydb update-cluster --cluster-name --auto-minor-version-upgrade ", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable auto minor version upgrade for all Memory DB clusters.", + "Url": "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.Upgrading.html#USER_UpgradeDBInstance.Upgrading.AutoMinorVersionUpgrades" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.py b/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.py new file mode 100644 index 00000000000..195185fff71 --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.memorydb.memorydb_client import memorydb_client + + +class memorydb_cluster_auto_minor_version_upgrades(Check): + def execute(self): + findings = [] + for cluster in memorydb_client.clusters.values(): + report = Check_Report_AWS(self.metadata()) + report.region = cluster.region + report.resource_id = cluster.name + report.resource_arn = cluster.arn + if cluster.auto_minor_version_upgrade: + report.status = "PASS" + report.status_extended = f"Memory DB Cluster {cluster.name} has minor version upgrade enabled." + else: + report.status = "FAIL" + report.status_extended = f"Memory DB Cluster {cluster.name} does not have minor version upgrade enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/memorydb/memorydb_service.py b/prowler/providers/aws/services/memorydb/memorydb_service.py new file mode 100644 index 00000000000..2fbe096940f --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_service.py @@ -0,0 +1,70 @@ +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 + + +class MemoryDB(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.clusters = {} + self.__threading_call__(self._describe_clusters) + + def _describe_clusters(self, regional_client): + logger.info("MemoryDB - Describe Clusters...") + try: + describe_clusters_paginator = regional_client.get_paginator( + "describe_clusters" + ) + for page in describe_clusters_paginator.paginate(): + for cluster in page["Clusters"]: + try: + arn = cluster["ARN"] + if not self.audit_resources or ( + is_resource_filtered(arn, self.audit_resources) + ): + self.clusters[arn] = Cluster( + name=cluster["Name"], + arn=arn, + number_of_shards=cluster["NumberOfShards"], + engine=cluster["Engine"], + engine_version=cluster["EngineVersion"], + engine_patch_version=cluster["EnginePatchVersion"], + multi_az=cluster.get("AvailabilityMode", "singleaz"), + region=regional_client.region, + security_groups=[ + sg["SecurityGroupId"] + for sg in cluster["SecurityGroups"] + if sg["Status"] == "active" + ], + tls_enabled=cluster["TLSEnabled"], + auto_minor_version_upgrade=cluster[ + "AutoMinorVersionUpgrade" + ], + snapshot_limit=cluster["SnapshotRetentionLimit"], + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class Cluster(BaseModel): + name: str + arn: str + number_of_shards: int + engine: str + engine_version: str + engine_patch_version: str + multi_az: str + region: str + security_groups: list[str] = [] + tls_enabled: bool + auto_minor_version_upgrade: bool + snapshot_limit: int diff --git a/tests/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades_test.py b/tests/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades_test.py new file mode 100644 index 00000000000..3abba98ac39 --- /dev/null +++ b/tests/providers/aws/services/memorydb/memorydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades_test.py @@ -0,0 +1,132 @@ +from unittest import mock + +from prowler.providers.aws.services.memorydb.memorydb_service import Cluster +from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1 + +memorydb_arn = ( + f"arn:aws:memorydb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" +) + + +class Test_memorydb_cluster_auto_minor_version_upgrades: + def test_no_memorydb(self): + memorydb_client = mock.MagicMock + memorydb_client.clusters = {} + + with mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_service.MemoryDB", + new=memorydb_client, + ), mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades.memorydb_client", + new=memorydb_client, + ): + from prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades import ( + memorydb_cluster_auto_minor_version_upgrades, + ) + + check = memorydb_cluster_auto_minor_version_upgrades() + result = check.execute() + + assert len(result) == 0 + + def test_memorydb_no_minor(self): + memorydb_client = mock.MagicMock + memorydb_client.clusters = {} + memorydb_client.clusters = { + "db-cluster-1": Cluster( + name="db-cluster-1", + arn=memorydb_arn, + status="available", + number_of_shards=2, + engine="valkey", + engine_version="6.2", + region=AWS_REGION_US_EAST_1, + engine_patch_version="6.2.6", + multi_az=True, + SecurityGroups=[ + {"SecurityGroupId": "sg-0a1434xxxxxc9fae", "Status": "active"} + ], + tls_enabled=False, + snapshot_limit=0, + auto_minor_version_upgrade=False, + ) + } + + with mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_service.MemoryDB", + new=memorydb_client, + ), mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades.memorydb_client", + new=memorydb_client, + ): + from prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades import ( + memorydb_cluster_auto_minor_version_upgrades, + ) + + check = memorydb_cluster_auto_minor_version_upgrades() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "Memory DB Cluster db-cluster-1 does not have minor version upgrade enabled." + ) + assert result[0].resource_id == "db-cluster-1" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:memorydb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" + ) + assert result[0].resource_tags == [] + + def test_memorydb_minor_enabled(self): + memorydb_client = mock.MagicMock + memorydb_client.clusters = {} + memorydb_client.clusters = { + "db-cluster-1": Cluster( + name="db-cluster-1", + arn=memorydb_arn, + status="available", + number_of_shards=2, + engine="valkey", + engine_version="6.2", + region=AWS_REGION_US_EAST_1, + engine_patch_version="6.2.6", + multi_az=True, + SecurityGroups=[ + {"SecurityGroupId": "sg-0a1434xxxxxc9fae", "Status": "active"} + ], + tls_enabled=False, + snapshot_limit=0, + auto_minor_version_upgrade=True, + ) + } + + with mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_service.MemoryDB", + new=memorydb_client, + ), mock.patch( + "prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades.memorydb_client", + new=memorydb_client, + ): + from prowler.providers.aws.services.memorydb.memorydb_cluster_auto_minor_version_upgrades.memorydb_cluster_auto_minor_version_upgrades import ( + memorydb_cluster_auto_minor_version_upgrades, + ) + + check = memorydb_cluster_auto_minor_version_upgrades() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "Memory DB Cluster db-cluster-1 has minor version upgrade enabled." + ) + assert result[0].resource_id == "db-cluster-1" + assert result[0].region == AWS_REGION_US_EAST_1 + assert ( + result[0].resource_arn + == f"arn:aws:memorydb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:db-cluster-1" + ) + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/memorydb/memorydb_service_test.py b/tests/providers/aws/services/memorydb/memorydb_service_test.py new file mode 100644 index 00000000000..9b804449e94 --- /dev/null +++ b/tests/providers/aws/services/memorydb/memorydb_service_test.py @@ -0,0 +1,110 @@ +import botocore +from mock import patch + +from prowler.providers.aws.services.memorydb.memorydb_service import Cluster, MemoryDB +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +MEM_DB_CLUSTER_NAME = "test-cluster" +MEM_DB_CLUSTER_ARN = f"arn:aws:memorydb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{MEM_DB_CLUSTER_NAME}" +MEM_DB_ENGINE_VERSION = "5.0.0" + +# Mocking Access Analyzer Calls +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwargs): + """ + As you can see the operation_name has the list_analyzers snake_case form but + we are using the ListAnalyzers form. + Rationale -> https://github.com/boto/botocore/blob/develop/botocore/client.py#L810:L816 + + We have to mock every AWS API call using Boto3 + """ + if operation_name == "DescribeClusters": + return { + "Clusters": [ + { + "Name": MEM_DB_CLUSTER_NAME, + "Description": "Test", + "Status": "test", + "NumberOfShards": 123, + "AvailabilityMode": "singleaz", + "Engine": "valkey", + "EngineVersion": MEM_DB_ENGINE_VERSION, + "EnginePatchVersion": "5.0.6", + "SecurityGroups": [ + {"SecurityGroupId": "sg-0a1434xxxxxc9fae", "Status": "active"}, + ], + "TLSEnabled": True, + "ARN": MEM_DB_CLUSTER_ARN, + "SnapshotRetentionLimit": 5, + "AutoMinorVersionUpgrade": True, + }, + ] + } + 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_MemoryDB_Service: + # Test MemoryDB Service + def test_service(self): + aws_provider = set_mocked_aws_provider() + memorydb = MemoryDB(aws_provider) + assert memorydb.service == "memorydb" + + # Test MemoryDB Client + def test_client(self): + aws_provider = set_mocked_aws_provider() + memorydb = MemoryDB(aws_provider) + assert memorydb.client.__class__.__name__ == "MemoryDB" + + # Test MemoryDB Session + def test__get_session__(self): + aws_provider = set_mocked_aws_provider() + memorydb = MemoryDB(aws_provider) + assert memorydb.session.__class__.__name__ == "Session" + + # Test MemoryDB Session + def test_audited_account(self): + aws_provider = set_mocked_aws_provider() + memorydb = MemoryDB(aws_provider) + assert memorydb.audited_account == AWS_ACCOUNT_NUMBER + + # Test MemoryDB Describe Clusters + def test_describe_clusters(self): + aws_provider = set_mocked_aws_provider() + memorydb = MemoryDB(aws_provider) + assert memorydb.clusters == { + MEM_DB_CLUSTER_ARN: Cluster( + name=MEM_DB_CLUSTER_NAME, + arn=MEM_DB_CLUSTER_ARN, + number_of_shards=123, + engine="valkey", + engine_version=MEM_DB_ENGINE_VERSION, + engine_patch_version="5.0.6", + multi_az="singleaz", + region=AWS_REGION_US_EAST_1, + security_groups=["sg-0a1434xxxxxc9fae"], + tls_enabled=True, + auto_minor_version_upgrade=True, + snapshot_limit=5, + ) + }