feat(aws): add MemoryDB service (#5546)

Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
This commit is contained in:
sansns-aws
2024-11-22 14:13:16 -05:00
committed by GitHub
parent fee0bf3ea1
commit 53a4befb01
8 changed files with 368 additions and 0 deletions

View File

@@ -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())

View File

@@ -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": ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 == []

View File

@@ -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,
)
}