Skip to content

Commit

Permalink
feat(aws): add MemoryDB service (#5546)
Browse files Browse the repository at this point in the history
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
  • Loading branch information
sansns and MrCloudSec authored Nov 22, 2024
1 parent fee0bf3 commit 53a4bef
Show file tree
Hide file tree
Showing 8 changed files with 368 additions and 0 deletions.
Empty file.
4 changes: 4 additions & 0 deletions prowler/providers/aws/services/memorydb/memorydb_client.py
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -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 <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": ""
}
Original file line number Diff line number Diff line change
@@ -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
70 changes: 70 additions & 0 deletions prowler/providers/aws/services/memorydb/memorydb_service.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 == []
110 changes: 110 additions & 0 deletions tests/providers/aws/services/memorydb/memorydb_service_test.py
Original file line number Diff line number Diff line change
@@ -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,
)
}

0 comments on commit 53a4bef

Please # to comment.