From ba726b205d75eb1f3afab47f34f12bbef9ea3732 Mon Sep 17 00:00:00 2001 From: sansns-aws <107269923+sansns@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:07:22 -0400 Subject: [PATCH] feat(Elasticache): Additional Elasticache checks (#4317) Co-authored-by: Sergio --- .../__init__.py | 0 ...cache_cluster_backup_enabled.metadata.json | 30 +++ .../elasticache_cluster_backup_enabled.py | 30 +++ ...che_cluster_multi_az_enabled.metadata.json | 30 +++ .../elasticache_cluster_multi_az_enabled.py | 27 +++ .../__init__.py | 0 ...tion_group_encrypted_at_rest.metadata.json | 32 ++++ ...che_replication_group_encrypted_at_rest.py | 23 +++ .../__init__.py | 0 ...n_group_encrypted_in_transit.metadata.json | 32 ++++ ..._replication_group_encrypted_in_transit.py | 23 +++ .../elasticache/elasticache_service.py | 44 +++++ ...elasticache_cluster_backup_enabled_test.py | 173 ++++++++++++++++++ ...asticache_cluster_multi_az_enabled_test.py | 133 ++++++++++++++ ...eplication_group_encrypted_at_rest_test.py | 133 ++++++++++++++ ...ication_group_encrypted_in_transit_test.py | 133 ++++++++++++++ .../elasticache/elasticache_service_test.py | 46 ++++- 17 files changed, 888 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/__init__.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json create mode 100644 prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json create mode 100644 prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/__init__.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/__init__.py create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json create mode 100644 prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py create mode 100644 tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py create mode 100644 tests/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py create mode 100644 tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py create mode 100644 tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json b/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json new file mode 100644 index 0000000000..5eed4557b8 --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "elasticache_cluster_backup_enabled", + "CheckTitle": "Ensure Elasticache Cluster has automatic backups enabled.", + "CheckType": [], + "ServiceName": "elasticache", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "high", + "ResourceType": "AWSElastiCacheClusters", + "Description": "Ensure Elasticache Cluster has automatic backups enabled.", + "Risk": "Ensure that your Amazon ElastiCache Redis cache clusters have a sufficient backup retention period set in order to fulfill your organization's compliance requirements. The retention period represents the number of days for which Amazon ElastiCache service retains automatic Redis cluster backups before deleting them.", + "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-1", + "Remediation": { + "Code": { + "CLI": "aws elasticache modify-replication-group --region --replication-group-id --snapshot-retention-limit --apply-immediately", + "NativeIaC": "", + "Other": "", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/ensure-that-amazon-elasticache-redis-clusters-have-automatic-backup-turned-on/" + }, + "Recommendation": { + "Text": "Ensure Elasticache Cluster has automatic backups enabled.", + "Url": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-1" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py b/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py new file mode 100644 index 0000000000..fb9580a4ec --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py @@ -0,0 +1,30 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elasticache.elasticache_client import ( + elasticache_client, +) + + +class elasticache_cluster_backup_enabled(Check): + def execute(self): + findings = [] + for repl_group in elasticache_client.replication_groups.values(): + report = Check_Report_AWS(self.metadata()) + report.region = repl_group.region + report.resource_id = repl_group.id + report.resource_arn = repl_group.arn + report.status = "FAIL" + report.status_extended = f"Elasticache Cluster {repl_group.id} does not have automated snapshot backups enabled." + if repl_group.snapshot_retention > elasticache_client.audit_config.get( + "minimum_snapshot_retention_period", 7 + ): + report.status = "PASS" + report.status_extended = f"Elasticache Cluster {repl_group.id} has automated snapshot backups enabled with retention period {repl_group.snapshot_retention} days." + else: + if repl_group.snapshot_retention > 0: + report.status = "FAIL" + report.check_metadata.Severity = "low" + report.status_extended = f"Elasticache Cluster {repl_group.id} has automated snapshot backups enabled with retention period {repl_group.snapshot_retention} days. Recommended to increase the snapshot retention period to a minimum of 7 days." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json b/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json new file mode 100644 index 0000000000..3a2f71151e --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "elasticache_cluster_multi_az_enabled", + "CheckTitle": "Ensure Elasticache Cluster has Multi-AZ enabled.", + "CheckType": [], + "ServiceName": "elasticache", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AWSElastiCacheClusters", + "Description": "Ensure Elasticache Cluster has Multi-AZ enabled.", + "Risk": "Ensure that your Amazon ElastiCache Redis cache clusters has Multi-AZ enabled.", + "RelatedUrl": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/ElastiCache/elasticache-multi-az.html#", + "Remediation": { + "Code": { + "CLI": "aws elasticache modify-replication-group --region --replication-group-id --multi-az-enabled --apply-immediately", + "NativeIaC": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/ElastiCache/elasticache-multi-az.html#", + "Other": "", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/ensure-aws-elasticache-redis-cluster-with-multi-az-automatic-failover-feature-set-to-enabled/" + }, + "Recommendation": { + "Text": "Ensure Elasticache Cluster has Multi-AZ enabled.", + "Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/ElastiCache/elasticache-multi-az.html#" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py b/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py new file mode 100644 index 0000000000..68451af689 --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py @@ -0,0 +1,27 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elasticache.elasticache_client import ( + elasticache_client, +) + + +class elasticache_cluster_multi_az_enabled(Check): + def execute(self): + findings = [] + for repl_group in elasticache_client.replication_groups.values(): + report = Check_Report_AWS(self.metadata()) + report.region = repl_group.region + report.resource_id = repl_group.id + report.resource_arn = repl_group.arn + report.status = "FAIL" + report.status_extended = ( + f"Elasticache Cluster {repl_group.id} does not have Multi-AZ enabled." + ) + if repl_group.multi_az == "enabled": + report.status = "PASS" + report.status_extended = ( + f"Elasticache Cluster {repl_group.id} has Multi-AZ enabled." + ) + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json new file mode 100644 index 0000000000..bf59e37788 --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "elasticache_replication_group_encrypted_at_rest", + "CheckTitle": "Ensure Elasticache Replication Groups have at rest encryption enabled.", + "CheckType": [], + "ServiceName": "elasticache", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AWSElastiCacheReplicationGroups", + "Description": "Ensure Elasticache Replication Groups have at rest encryption enabled.", + "Risk": "There is a risk of exposing sensitive data if Elasticache Replication Group does not have at rest encryption enabled.", + "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-4", + "Remediation": { + "Code": { + "CLI": "https://docs.prowler.com/checks/aws/general-policies/general_9/", + "NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/general_9/", + "Other": "", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/general_9/" + }, + "Recommendation": { + "Text": "Ensure your Elasticache replication groups have at rest encryption enabled.", + "Url": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/at-rest-encryption.html#at-rest-encryption-enable" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py new file mode 100644 index 0000000000..cc4bb433cc --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elasticache.elasticache_client import ( + elasticache_client, +) + + +class elasticache_replication_group_encrypted_at_rest(Check): + def execute(self): + findings = [] + for repl_group in elasticache_client.replication_groups.values(): + report = Check_Report_AWS(self.metadata()) + report.region = repl_group.region + report.resource_id = repl_group.id + report.resource_arn = repl_group.arn + report.status = "FAIL" + report.status_extended = f"Elasticache Replication Group {repl_group.id} does not have at rest encryption enabled." + if repl_group.encrypted: + report.status = "PASS" + report.status_extended = f"Elasticache Replication Group {repl_group.id} has at rest encryption enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json new file mode 100644 index 0000000000..3e70065573 --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "elasticache_replication_group_encrypted_in_transit", + "CheckTitle": "Ensure Elasticache Replication Groups have in transit encryption enabled.", + "CheckType": [], + "ServiceName": "elasticache", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id", + "Severity": "medium", + "ResourceType": "AWSElastiCacheReplicationGroups", + "Description": "Ensure Elasticache Replication Groups have in transit encryption enabled.", + "Risk": "There is a risk of exposing sensitive data if Elasticache Replication Group does not have in transit encryption enabled.", + "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-5", + "Remediation": { + "Code": { + "CLI": "https://docs.prowler.com/checks/aws/general-policies/general_10/", + "NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/general_10/", + "Other": "", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/general_10/" + }, + "Recommendation": { + "Text": "Ensure your Elasticache replication groups have in transit encryption enabled.", + "Url": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/in-transit-encryption.html" + } + }, + "Categories": [ + "encryption" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py new file mode 100644 index 0000000000..f45c4c2106 --- /dev/null +++ b/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py @@ -0,0 +1,23 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.elasticache.elasticache_client import ( + elasticache_client, +) + + +class elasticache_replication_group_encrypted_in_transit(Check): + def execute(self): + findings = [] + for repl_group in elasticache_client.replication_groups.values(): + report = Check_Report_AWS(self.metadata()) + report.region = repl_group.region + report.resource_id = repl_group.id + report.resource_arn = repl_group.arn + report.status = "FAIL" + report.status_extended = f"Elasticache Replication Group {repl_group.id} does not have in transit encryption enabled." + if repl_group.transit_encryption: + report.status = "PASS" + report.status_extended = f"Elasticache Replication Group {repl_group.id} has in transit encryption enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/elasticache/elasticache_service.py b/prowler/providers/aws/services/elasticache/elasticache_service.py index d23a9782d6..10486d9d01 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_service.py +++ b/prowler/providers/aws/services/elasticache/elasticache_service.py @@ -14,9 +14,11 @@ class ElastiCache(AWSService): # Call AWSService's __init__ super().__init__(__class__.__name__, provider) self.clusters = {} + self.replication_groups = {} self.__threading_call__(self.__describe_cache_clusters__) self.__threading_call__(self.__describe_cache_subnet_groups__) self.__list_tags_for_resource__() + self.__threading_call__(self.__describe_replication_groups__) def __describe_cache_clusters__(self, regional_client): logger.info("Elasticache - Describing Cache Clusters...") @@ -32,6 +34,11 @@ class ElastiCache(AWSService): id=cache_cluster["CacheClusterId"], arn=cluster_arn, region=regional_client.region, + security_groups=[ + sg["SecurityGroupId"] + for sg in cache_cluster["SecurityGroups"] + if sg["Status"] == "active" + ], cache_subnet_group_id=cache_cluster.get( "CacheSubnetGroupName", None ), @@ -95,11 +102,48 @@ class ElastiCache(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def __describe_replication_groups__(self, regional_client): + logger.info("Elasticache - Describing Replication Groups...") + try: + for repl_group in regional_client.describe_replication_groups()[ + "ReplicationGroups" + ]: + replication_arn = repl_group["ARN"] + if not self.audit_resources or ( + is_resource_filtered(replication_arn, self.audit_resources) + ): + self.replication_groups[replication_arn] = ReplicationGroup( + id=repl_group["ReplicationGroupId"], + arn=replication_arn, + region=regional_client.region, + status=repl_group["Status"], + snapshot_retention=repl_group.get("SnapshotRetentionLimit", 0), + encrypted=repl_group["AtRestEncryptionEnabled"], + transit_encryption=repl_group["TransitEncryptionEnabled"], + multi_az=repl_group["MultiAZ"], + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + class Cluster(BaseModel): id: str arn: str region: str + security_groups: list[str] = [] cache_subnet_group_id: Optional[str] subnets: list = [] tags: Optional[list] + + +class ReplicationGroup(BaseModel): + id: str + arn: str + region: str + status: str + snapshot_retention: int + encrypted: bool + transit_encryption: bool + multi_az: str diff --git a/tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py b/tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py new file mode 100644 index 0000000000..f4f6bd63ca --- /dev/null +++ b/tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py @@ -0,0 +1,173 @@ +from unittest import mock + +import botocore +from mock import MagicMock +from moto import mock_aws + +from prowler.providers.aws.services.elasticache.elasticache_service import ( + ReplicationGroup, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +REPLICATION_GROUP_ID = "clustered-redis" +REPLICATION_GROUP_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:replicationgroup:{REPLICATION_GROUP_ID}" +REPLICATION_GROUP_STATUS = "available" +REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" +REPLICATION_GROUP_ENCRYPTION = True +REPLICATION_GROUP_TRANSIT_ENCRYPTION = True +REPLICATION_GROUP_MULTI_AZ = "enabled" + +# Patch every AWS call using Boto3 +make_api_call = botocore.client.BaseClient._make_api_call + + +class Test_elasticache_cluster_backup_enabled: + @mock_aws + def test_elasticache_no_replication_groups(self): + + # Mock ElastiCache Service + elasticache_client = MagicMock + elasticache_client.replication_groups = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_backup_enabled.elasticache_cluster_backup_enabled import ( + elasticache_cluster_backup_enabled, + ) + + check = elasticache_cluster_backup_enabled() + result = check.execute() + assert len(result) == 0 + + def test_elasticache_cluster_backup_disabled(self): + # Mock ElastiCache Service + elasticache_client = MagicMock + elasticache_client.replication_groups = {} + + elasticache_client.replication_groups[REPLICATION_GROUP_ARN] = ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=0, + encrypted=False, + transit_encryption=False, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + + elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7} + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_backup_enabled.elasticache_cluster_backup_enabled import ( + elasticache_cluster_backup_enabled, + ) + + check = elasticache_cluster_backup_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Cluster {REPLICATION_GROUP_ID} does not have automated snapshot backups enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN + + def test_elasticache_cluster_backup_enabled(self): + # Mock ElastiCache Service + elasticache_client = MagicMock + elasticache_client.replication_groups = {} + + elasticache_client.replication_groups[REPLICATION_GROUP_ARN] = ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=9, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + + elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_backup_enabled.elasticache_cluster_backup_enabled import ( + elasticache_cluster_backup_enabled, + ) + + check = elasticache_cluster_backup_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Cluster {REPLICATION_GROUP_ID} has automated snapshot backups enabled with retention period 9 days." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN + + def test_elasticache_cluster_backup_enabled_modified_retention(self): + # Mock ElastiCache Service + elasticache_client = MagicMock + elasticache_client.replication_groups = {} + + elasticache_client.replication_groups[REPLICATION_GROUP_ARN] = ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=3, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + + elasticache_client.audit_config = {"minimum_snapshot_retention_period": 1} + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_backup_enabled.elasticache_cluster_backup_enabled import ( + elasticache_cluster_backup_enabled, + ) + + check = elasticache_cluster_backup_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Cluster {REPLICATION_GROUP_ID} has automated snapshot backups enabled with retention period 3 days." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN diff --git a/tests/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py b/tests/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py new file mode 100644 index 0000000000..e8928dbd5a --- /dev/null +++ b/tests/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py @@ -0,0 +1,133 @@ +from unittest import mock + +import botocore +from mock import MagicMock +from moto import mock_aws + +from prowler.providers.aws.services.elasticache.elasticache_service import ( + ReplicationGroup, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +REPLICATION_GROUP_ID = "clustered-redis" +REPLICATION_GROUP_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:replicationgroup:{REPLICATION_GROUP_ID}" +REPLICATION_GROUP_STATUS = "available" +REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" +REPLICATION_GROUP_ENCRYPTION = True +REPLICATION_GROUP_TRANSIT_ENCRYPTION = True +REPLICATION_GROUP_MULTI_AZ = "enabled" + +# Patch every AWS call using Boto3 +make_api_call = botocore.client.BaseClient._make_api_call + + +class Test_elasticache_replication_group_multi_az_enabled: + @mock_aws + def test_elasticache_no_replication_groups(self): + + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_multi_az_enabled.elasticache_cluster_multi_az_enabled import ( + elasticache_cluster_multi_az_enabled, + ) + + check = elasticache_cluster_multi_az_enabled() + result = check.execute() + assert len(result) == 0 + + def test_elasticache_cluster_multi_az_disabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=False, + transit_encryption=False, + multi_az="disabled", + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_multi_az_enabled.elasticache_cluster_multi_az_enabled import ( + elasticache_cluster_multi_az_enabled, + ) + + check = elasticache_cluster_multi_az_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Cluster {REPLICATION_GROUP_ID} does not have Multi-AZ enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN + + def test_elasticache_cluster_multi_az_enabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_multi_az_enabled.elasticache_cluster_multi_az_enabled import ( + elasticache_cluster_multi_az_enabled, + ) + + check = elasticache_cluster_multi_az_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Cluster {REPLICATION_GROUP_ID} has Multi-AZ enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN diff --git a/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py b/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py new file mode 100644 index 0000000000..cdae55254c --- /dev/null +++ b/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py @@ -0,0 +1,133 @@ +from unittest import mock + +import botocore +from mock import MagicMock +from moto import mock_aws + +from prowler.providers.aws.services.elasticache.elasticache_service import ( + ReplicationGroup, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +REPLICATION_GROUP_ID = "clustered-redis" +REPLICATION_GROUP_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:replicationgroup:{REPLICATION_GROUP_ID}" +REPLICATION_GROUP_STATUS = "available" +REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" +REPLICATION_GROUP_ENCRYPTION = True +REPLICATION_GROUP_TRANSIT_ENCRYPTION = True +REPLICATION_GROUP_MULTI_AZ = True + +# Patch every AWS call using Boto3 +make_api_call = botocore.client.BaseClient._make_api_call + + +class Test_elasticache_replication_group_at_rest_encryption_enabled: + @mock_aws + def test_elasticache_no_replication_groups(self): + + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_at_rest.elasticache_replication_group_encrypted_at_rest import ( + elasticache_replication_group_encrypted_at_rest, + ) + + check = elasticache_replication_group_encrypted_at_rest() + result = check.execute() + assert len(result) == 0 + + def test_elasticache_replication_groups_at_rest_encryption_disabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=False, + transit_encryption=False, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_at_rest.elasticache_replication_group_encrypted_at_rest import ( + elasticache_replication_group_encrypted_at_rest, + ) + + check = elasticache_replication_group_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Replication Group {REPLICATION_GROUP_ID} does not have at rest encryption enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN + + def test_elasticache_replication_groups_at_rest_encryption_enabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_at_rest.elasticache_replication_group_encrypted_at_rest import ( + elasticache_replication_group_encrypted_at_rest, + ) + + check = elasticache_replication_group_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Replication Group {REPLICATION_GROUP_ID} has at rest encryption enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN diff --git a/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py b/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py new file mode 100644 index 0000000000..cd0cfe9bf9 --- /dev/null +++ b/tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py @@ -0,0 +1,133 @@ +from unittest import mock + +import botocore +from mock import MagicMock +from moto import mock_aws + +from prowler.providers.aws.services.elasticache.elasticache_service import ( + ReplicationGroup, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +REPLICATION_GROUP_ID = "clustered-redis" +REPLICATION_GROUP_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:replicationgroup:{REPLICATION_GROUP_ID}" +REPLICATION_GROUP_STATUS = "available" +REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" +REPLICATION_GROUP_ENCRYPTION = True +REPLICATION_GROUP_TRANSIT_ENCRYPTION = True +REPLICATION_GROUP_MULTI_AZ = "enabled" + +# Patch every AWS call using Boto3 +make_api_call = botocore.client.BaseClient._make_api_call + + +class Test_elasticache_replication_group_in_transit_encryption_enabled: + @mock_aws + def test_elasticache_no_replication_groups(self): + + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_in_transit.elasticache_replication_group_encrypted_in_transit import ( + elasticache_replication_group_encrypted_in_transit, + ) + + check = elasticache_replication_group_encrypted_in_transit() + result = check.execute() + assert len(result) == 0 + + def test_elasticache_replication_groups_in_transit_encryption_disabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=False, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_in_transit.elasticache_replication_group_encrypted_in_transit import ( + elasticache_replication_group_encrypted_in_transit, + ) + + check = elasticache_replication_group_encrypted_in_transit() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Replication Group {REPLICATION_GROUP_ID} does not have in transit encryption enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN + + def test_elasticache_replication_groups_in_transit_encryption_enabled(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.replication_groups = {} + + elasticache_service.replication_groups[REPLICATION_GROUP_ARN] = ( + ReplicationGroup( + arn=REPLICATION_GROUP_ARN, + id=REPLICATION_GROUP_ID, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + ) + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=set_mocked_aws_provider([AWS_REGION_US_EAST_1]), + ), mock.patch( + "prowler.providers.aws.services.elasticache.elasticache_service.ElastiCache", + new=elasticache_service, + ): + from prowler.providers.aws.services.elasticache.elasticache_replication_group_encrypted_in_transit.elasticache_replication_group_encrypted_in_transit import ( + elasticache_replication_group_encrypted_in_transit, + ) + + check = elasticache_replication_group_encrypted_in_transit() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Replication Group {REPLICATION_GROUP_ID} has in transit encryption enabled." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == REPLICATION_GROUP_ID + assert result[0].resource_arn == REPLICATION_GROUP_ARN diff --git a/tests/providers/aws/services/elasticache/elasticache_service_test.py b/tests/providers/aws/services/elasticache/elasticache_service_test.py index 804a519abd..3fada1164b 100644 --- a/tests/providers/aws/services/elasticache/elasticache_service_test.py +++ b/tests/providers/aws/services/elasticache/elasticache_service_test.py @@ -4,6 +4,7 @@ from mock import patch from prowler.providers.aws.services.elasticache.elasticache_service import ( Cluster, ElastiCache, + ReplicationGroup, ) from tests.providers.aws.utils import ( AWS_ACCOUNT_NUMBER, @@ -25,6 +26,14 @@ ELASTICACHE_CLUSTER_TAGS = [ {"Key": "environment", "Value": "test"}, ] +REPLICATION_GROUP_ID = "clustered-redis" +REPLICATION_GROUP_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:replicationgroup:{REPLICATION_GROUP_ID}" +REPLICATION_GROUP_STATUS = "available" +REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" +REPLICATION_GROUP_ENCRYPTION = True +REPLICATION_GROUP_TRANSIT_ENCRYPTION = True +REPLICATION_GROUP_MULTI_AZ = "enabled" + # Mocking Access Analyzer Calls make_api_call = botocore.client.BaseClient._make_api_call @@ -44,6 +53,7 @@ def mock_make_api_call(self, operation_name, kwargs): "CacheClusterId": ELASTICACHE_CLUSTER_NAME, "CacheSubnetGroupName": SUBNET_GROUP_NAME, "ARN": ELASTICACHE_CLUSTER_ARN, + "SecurityGroups": [], }, ] } @@ -77,7 +87,20 @@ def mock_make_api_call(self, operation_name, kwargs): } if operation_name == "ListTagsForResource": return {"TagList": ELASTICACHE_CLUSTER_TAGS} - + if operation_name == "DescribeReplicationGroups": + return { + "ReplicationGroups": [ + { + "ReplicationGroupId": REPLICATION_GROUP_ID, + "Status": REPLICATION_GROUP_STATUS, + "SnapshotRetentionLimit": REPLICATION_GROUP_SNAPSHOT_RETENTION, + "MultiAZ": REPLICATION_GROUP_MULTI_AZ, + "TransitEncryptionEnabled": REPLICATION_GROUP_TRANSIT_ENCRYPTION, + "AtRestEncryptionEnabled": REPLICATION_GROUP_ENCRYPTION, + "ARN": REPLICATION_GROUP_ARN, + }, + ] + } return make_api_call(self, operation_name, kwargs) @@ -132,7 +155,28 @@ class Test_ElastiCache_Service: name=ELASTICACHE_CLUSTER_NAME, id=ELASTICACHE_CLUSTER_NAME, region=AWS_REGION_US_EAST_1, + security_groups=[], cache_subnet_group_id=SUBNET_GROUP_NAME, subnets=[SUBNET_1, SUBNET_2], tags=ELASTICACHE_CLUSTER_TAGS, ) + + # Test ElastiCache Replication Groups + def test_describe_replication_groups(self): + aws_provider = set_mocked_aws_provider() + elasticache = ElastiCache(aws_provider) + + assert len(elasticache.replication_groups) == 1 + assert elasticache.replication_groups[REPLICATION_GROUP_ARN] + assert elasticache.replication_groups[ + REPLICATION_GROUP_ARN + ] == ReplicationGroup( + id=REPLICATION_GROUP_ID, + arn=REPLICATION_GROUP_ARN, + region=AWS_REGION_US_EAST_1, + status=REPLICATION_GROUP_STATUS, + snapshot_retention=REPLICATION_GROUP_SNAPSHOT_RETENTION, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + )