From e21946874f253684600223401a80d30315711015 Mon Sep 17 00:00:00 2001 From: Utkarsh Batham <146472914+UTKARSH698@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:33:10 +0530 Subject: [PATCH] feat(memorydb): add memorydb_cluster_in_transit_encryption_enabled check (#12246) --- ...ter-in-transit-encryption-enabled.added.md | 1 + .../__init__.py | 0 ...n_transit_encryption_enabled.metadata.json | 41 ++++++ ...b_cluster_in_transit_encryption_enabled.py | 30 ++++ ...ster_in_transit_encryption_enabled_test.py | 133 ++++++++++++++++++ 5 files changed, 205 insertions(+) create mode 100644 prowler/changelog.d/memorydb-cluster-in-transit-encryption-enabled.added.md create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/__init__.py create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.metadata.json create mode 100644 prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.py create mode 100644 tests/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled_test.py diff --git a/prowler/changelog.d/memorydb-cluster-in-transit-encryption-enabled.added.md b/prowler/changelog.d/memorydb-cluster-in-transit-encryption-enabled.added.md new file mode 100644 index 0000000000..90ce26d446 --- /dev/null +++ b/prowler/changelog.d/memorydb-cluster-in-transit-encryption-enabled.added.md @@ -0,0 +1 @@ +`memorydb_cluster_in_transit_encryption_enabled` check for AWS provider, verifying MemoryDB clusters have in-transit encryption (TLS) enabled diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/__init__.py b/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.metadata.json b/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.metadata.json new file mode 100644 index 0000000000..04523c7c63 --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.metadata.json @@ -0,0 +1,41 @@ +{ + "Provider": "aws", + "CheckID": "memorydb_cluster_in_transit_encryption_enabled", + "CheckTitle": "MemoryDB cluster has in-transit encryption enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices/Network Security", + "Effects/Data Exposure" + ], + "ServiceName": "memorydb", + "SubServiceName": "", + "ResourceIdTemplate": "", + "Severity": "medium", + "ResourceType": "Other", + "ResourceGroup": "database", + "Description": "**MemoryDB clusters** are evaluated for **in-transit encryption (TLS)** on client and inter-node traffic (`TLSEnabled=true`).", + "Risk": "Without **in-transit encryption**, client and inter-node traffic travels in cleartext inside the VPC.\n\nAnyone able to observe it can **read cached data** and the **ACL credentials** clients send to authenticate, or **tamper with commands**, enabling **MITM** and replication corruption. This harms **confidentiality** and **integrity**.", + "RelatedUrl": "", + "AdditionalURLs": [ + "https://docs.aws.amazon.com/memorydb/latest/devguide/in-transit-encryption.html", + "https://docs.aws.amazon.com/memorydb/latest/devguide/API_CreateCluster.html", + "https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/memorydb_cluster#tls_enabled" + ], + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "```yaml\n# Enable in-transit encryption for a MemoryDB cluster\nResources:\n :\n Type: AWS::MemoryDB::Cluster\n Properties:\n ClusterName: \n ACLName: \n NodeType: \n NumShards: 1\n TLSEnabled: true # CRITICAL: enables TLS in-transit; replaces the cluster if changed\n```", + "Other": "1. In the AWS Console, go to MemoryDB > Clusters\n2. Select the cluster and choose Actions > Take snapshot\n3. Once the snapshot is available, go to Snapshots, select it and choose Actions > Restore\n4. In the restore form, enable \"Encryption in transit\" and create the cluster\n5. Repoint clients at the new cluster endpoint using a TLS-enabled client, then delete the old cluster", + "Terraform": "```hcl\nresource \"aws_memorydb_cluster\" \"\" {\n name = \"\"\n acl_name = \"\"\n node_type = \"\"\n num_shards = 1\n\n tls_enabled = true # CRITICAL: enables TLS in-transit; forces replacement if changed\n}\n```" + }, + "Recommendation": { + "Text": "Create MemoryDB clusters with `TLSEnabled=true`. Because this flag is **immutable after creation**, an unencrypted cluster must be replaced: snapshot it, restore into a new TLS-enabled cluster, then cut clients over and delete the original.\n\nConfigure clients to validate the server certificate, and pair TLS with **ACL users**, least-privilege security groups, and private subnets for defense in depth.", + "Url": "https://hub.prowler.com/check/memorydb_cluster_in_transit_encryption_enabled" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "MemoryDB enables in-transit encryption by default, and `TLSEnabled` cannot be changed after the cluster is created, so a failing cluster must be recreated from a snapshot rather than updated in place." +} diff --git a/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.py b/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.py new file mode 100644 index 0000000000..0ade116e6f --- /dev/null +++ b/prowler/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.memorydb.memorydb_client import memorydb_client + + +class memorydb_cluster_in_transit_encryption_enabled(Check): + """Verify that every MemoryDB cluster encrypts data in transit. + + A cluster PASSES when ``TLSEnabled`` is set. The flag can only be chosen at + creation time, so a failing cluster has to be rebuilt from a snapshot. + """ + + def execute(self) -> list[Check_Report_AWS]: + """Execute the MemoryDB in-transit encryption check. + + Returns: + A list of reports with each cluster in-transit encryption status. + """ + findings = [] + for cluster in memorydb_client.clusters.values(): + report = Check_Report_AWS(metadata=self.metadata(), resource=cluster) + if cluster.tls_enabled: + report.status = "PASS" + report.status_extended = f"Memory DB Cluster {cluster.name} has in-transit encryption enabled." + else: + report.status = "FAIL" + report.status_extended = f"Memory DB Cluster {cluster.name} does not have in-transit encryption enabled." + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled_test.py b/tests/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled_test.py new file mode 100644 index 0000000000..f30c10b123 --- /dev/null +++ b/tests/providers/aws/services/memorydb/memorydb_cluster_in_transit_encryption_enabled/memorydb_cluster_in_transit_encryption_enabled_test.py @@ -0,0 +1,133 @@ +from unittest import mock + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +CLUSTER_NAME = "db-cluster-1" +CLUSTER_ARN = f"arn:aws:memorydb:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:cluster:{CLUSTER_NAME}" + +CHECK_PATH = "prowler.providers.aws.services.memorydb.memorydb_cluster_in_transit_encryption_enabled.memorydb_cluster_in_transit_encryption_enabled.memorydb_client" +PROVIDER_PATH = "prowler.providers.common.provider.Provider.get_global_provider" + +make_api_call = botocore.client.BaseClient._make_api_call + + +def cluster_payload(tls_enabled: bool) -> dict: + return { + "Name": CLUSTER_NAME, + "Status": "available", + "NumberOfShards": 2, + "AvailabilityMode": "multiaz", + "Engine": "valkey", + "EngineVersion": "7.2", + "EnginePatchVersion": "7.2.4", + "SecurityGroups": [ + {"SecurityGroupId": "sg-0a1434xxxxxc9fae", "Status": "active"} + ], + "TLSEnabled": tls_enabled, + "ARN": CLUSTER_ARN, + "SnapshotRetentionLimit": 0, + "AutoMinorVersionUpgrade": True, + } + + +def mock_describe_clusters(clusters: list): + """Stub DescribeClusters, which moto covers only partially. + + Moto accepts ``Engine`` on CreateCluster but never returns it from + DescribeClusters, and the service layer requires that field, so every + cluster would be dropped before reaching the check. + """ + + def mock_make_api_call(self, operation_name, kwargs): + if operation_name == "DescribeClusters": + return {"Clusters": clusters} + return make_api_call(self, operation_name, kwargs) + + return mock_make_api_call + + +def build_memorydb_client(clusters: list): + from prowler.providers.aws.services.memorydb.memorydb_service import MemoryDB + + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_describe_clusters(clusters), + ): + return aws_provider, MemoryDB(aws_provider) + + +class Test_memorydb_cluster_in_transit_encryption_enabled: + @mock_aws + def test_no_clusters(self): + aws_provider, memorydb_client = build_memorydb_client([]) + + with mock.patch(PROVIDER_PATH, return_value=aws_provider): + with mock.patch(CHECK_PATH, new=memorydb_client): + from prowler.providers.aws.services.memorydb.memorydb_cluster_in_transit_encryption_enabled.memorydb_cluster_in_transit_encryption_enabled import ( + memorydb_cluster_in_transit_encryption_enabled, + ) + + check = memorydb_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_cluster_in_transit_encryption_disabled(self): + aws_provider, memorydb_client = build_memorydb_client( + [cluster_payload(tls_enabled=False)] + ) + + with mock.patch(PROVIDER_PATH, return_value=aws_provider): + with mock.patch(CHECK_PATH, new=memorydb_client): + from prowler.providers.aws.services.memorydb.memorydb_cluster_in_transit_encryption_enabled.memorydb_cluster_in_transit_encryption_enabled import ( + memorydb_cluster_in_transit_encryption_enabled, + ) + + check = memorydb_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Memory DB Cluster {CLUSTER_NAME} does not have in-transit encryption enabled." + ) + assert result[0].resource_id == CLUSTER_NAME + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_cluster_in_transit_encryption_enabled(self): + aws_provider, memorydb_client = build_memorydb_client( + [cluster_payload(tls_enabled=True)] + ) + + with mock.patch(PROVIDER_PATH, return_value=aws_provider): + with mock.patch(CHECK_PATH, new=memorydb_client): + from prowler.providers.aws.services.memorydb.memorydb_cluster_in_transit_encryption_enabled.memorydb_cluster_in_transit_encryption_enabled import ( + memorydb_cluster_in_transit_encryption_enabled, + ) + + check = memorydb_cluster_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Memory DB Cluster {CLUSTER_NAME} has in-transit encryption enabled." + ) + assert result[0].resource_id == CLUSTER_NAME + assert result[0].resource_arn == CLUSTER_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == []