-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(aws): add MemoryDB service (#5546)
Co-authored-by: MrCloudSec <hello@mistercloudsec.com>
- Loading branch information
1 parent
fee0bf3
commit 53a4bef
Showing
8 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) |
Empty file.
30 changes: 30 additions & 0 deletions
30
...er_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.metadata.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": "" | ||
} |
22 changes: 22 additions & 0 deletions
22
...orydb_cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
70
prowler/providers/aws/services/memorydb/memorydb_service.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
132 changes: 132 additions & 0 deletions
132
..._cluster_auto_minor_version_upgrades/memorydb_cluster_auto_minor_version_upgrades_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
110
tests/providers/aws/services/memorydb/memorydb_service_test.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) | ||
} |