From 82ccdc45d29d1391aea02ccb1daeb875eaf3747f Mon Sep 17 00:00:00 2001 From: Sergio Garcia <38561120+sergargar@users.noreply.github.com> Date: Mon, 1 Jul 2024 10:06:24 -0400 Subject: [PATCH] chore(elasticache): enhance service and checks (#4329) --- .../elasticache_cluster_uses_public_subnet.py | 14 +- .../__init__.py | 0 ...edis_cluster_backup_enabled.metadata.json} | 6 +- ...asticache_redis_cluster_backup_enabled.py} | 9 +- .../__init__.py | 0 ..._transit_encryption_enabled.metadata.json} | 10 +- ..._cluster_in_transit_encryption_enabled.py} | 7 +- .../__init__.py | 0 ...is_cluster_multi_az_enabled.metadata.json} | 8 +- ...ticache_redis_cluster_multi_az_enabled.py} | 11 +- .../__init__.py | 0 ...ter_rest_encryption_enabled.metadata.json} | 10 +- ..._redis_cluster_rest_encryption_enabled.py} | 7 +- .../elasticache/elasticache_service.py | 127 +++++++------ ...ticache_cluster_uses_public_subnet_test.py | 167 +++++++++++++++++- ...ache_redis_cluster_backup_enabled_test.py} | 89 ++++++++-- ...ter_in_transit_encryption_enabled_test.py} | 30 ++-- ...he_redis_cluster_multi_az_enabled_test.py} | 32 ++-- ...s_cluster_rest_encryption_enabled_test.py} | 29 +-- .../elasticache/elasticache_service_test.py | 12 +- 20 files changed, 421 insertions(+), 147 deletions(-) rename prowler/providers/aws/services/elasticache/{elasticache_cluster_backup_enabled => elasticache_redis_cluster_backup_enabled}/__init__.py (100%) rename prowler/providers/aws/services/elasticache/{elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json => elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.metadata.json} (85%) rename prowler/providers/aws/services/elasticache/{elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py => elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.py} (57%) rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_at_rest => elasticache_redis_cluster_in_transit_encryption_enabled}/__init__.py (100%) rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json => elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.metadata.json} (71%) rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py => elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.py} (63%) rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_in_transit => elasticache_redis_cluster_multi_az_enabled}/__init__.py (100%) rename prowler/providers/aws/services/elasticache/{elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json => elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.metadata.json} (78%) rename prowler/providers/aws/services/elasticache/{elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py => elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.py} (66%) create mode 100644 prowler/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/__init__.py rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json => elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.metadata.json} (72%) rename prowler/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py => elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.py} (63%) rename tests/providers/aws/services/elasticache/{elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py => elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled_test.py} (59%) rename tests/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py => elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled_test.py} (77%) rename tests/providers/aws/services/elasticache/{elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py => elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled_test.py} (78%) rename tests/providers/aws/services/elasticache/{elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py => elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled_test.py} (78%) diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.py b/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.py index 16acc76c82..3a8c554fa4 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.py +++ b/prowler/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet.py @@ -16,9 +16,12 @@ class elasticache_cluster_uses_public_subnet(Check): report.region = cluster.region report.status = "PASS" - report.status_extended = ( - f"Cluster {cluster.id} is not using public subnets." - ) + if cluster.engine == "redis": + report.status_extended = ( + f"Elasticache Redis Node {cluster.id} is not using public subnets." + ) + else: + report.status_extended = f"Elasticache Memcached Cluster {cluster.id} is not using public subnets." public_subnets = [] for subnet in cluster.subnets: @@ -30,7 +33,10 @@ class elasticache_cluster_uses_public_subnet(Check): if len(public_subnets) > 0: report.status = "FAIL" - report.status_extended = f"Cluster {cluster.id} is using {', '.join(public_subnets)} public subnets." + if cluster.engine == "redis": + report.status_extended = f"Elasticache Redis Node {cluster.id} is using {', '.join(public_subnets)} public subnets." + else: + report.status_extended = f"Elasticache Memcached Cluster {cluster.id} is using {', '.join(public_subnets)} public subnets." findings.append(report) diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/__init__.py similarity index 100% rename from prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/__init__.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/__init__.py 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_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.metadata.json similarity index 85% rename from prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.metadata.json index 5eed4557b8..45606f0718 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.metadata.json +++ b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.metadata.json @@ -1,14 +1,14 @@ { "Provider": "aws", - "CheckID": "elasticache_cluster_backup_enabled", - "CheckTitle": "Ensure Elasticache Cluster has automatic backups enabled.", + "CheckID": "elasticache_redis_cluster_backup_enabled", + "CheckTitle": "Ensure Elasticache Redis cache 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.", + "Description": "Ensure Elasticache Redis cache 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": { diff --git a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.py similarity index 57% rename from prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.py index fb9580a4ec..1c73d77427 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled.py +++ b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled.py @@ -4,7 +4,7 @@ from prowler.providers.aws.services.elasticache.elasticache_client import ( ) -class elasticache_cluster_backup_enabled(Check): +class elasticache_redis_cluster_backup_enabled(Check): def execute(self): findings = [] for repl_group in elasticache_client.replication_groups.values(): @@ -12,18 +12,19 @@ class elasticache_cluster_backup_enabled(Check): report.region = repl_group.region report.resource_id = repl_group.id report.resource_arn = repl_group.arn + report.resource_tags = repl_group.tags report.status = "FAIL" - report.status_extended = f"Elasticache Cluster {repl_group.id} does not have automated snapshot backups enabled." + report.status_extended = f"Elasticache Redis cache 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." + report.status_extended = f"Elasticache Redis cache 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." + report.status_extended = f"Elasticache Redis cache 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) diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_in_transit_encryption_enabled/__init__.py similarity index 100% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/__init__.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_in_transit_encryption_enabled/__init__.py 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.metadata.json similarity index 71% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.metadata.json rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.metadata.json index 3e70065573..02c405ff94 100644 --- 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.metadata.json @@ -1,15 +1,15 @@ { "Provider": "aws", - "CheckID": "elasticache_replication_group_encrypted_in_transit", - "CheckTitle": "Ensure Elasticache Replication Groups have in transit encryption enabled.", + "CheckID": "elasticache_redis_cluster_in_transit_encryption_enabled", + "CheckTitle": "Ensure Elasticache Redis cache clusters 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.", + "Description": "Ensure Elasticache Redis cache clusters have in transit encryption enabled.", + "Risk": "There is a risk of exposing sensitive data if Elasticache Redis cache cluster does not have in transit encryption enabled.", "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-5", "Remediation": { "Code": { @@ -19,7 +19,7 @@ "Terraform": "https://docs.prowler.com/checks/aws/general-policies/general_10/" }, "Recommendation": { - "Text": "Ensure your Elasticache replication groups have in transit encryption enabled.", + "Text": "Ensure your Elasticache Redis cache clusters have in transit encryption enabled.", "Url": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/in-transit-encryption.html" } }, 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.py similarity index 63% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.py index f45c4c2106..bed12a7214 100644 --- 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled.py @@ -4,7 +4,7 @@ from prowler.providers.aws.services.elasticache.elasticache_client import ( ) -class elasticache_replication_group_encrypted_in_transit(Check): +class elasticache_redis_cluster_in_transit_encryption_enabled(Check): def execute(self): findings = [] for repl_group in elasticache_client.replication_groups.values(): @@ -12,11 +12,12 @@ class elasticache_replication_group_encrypted_in_transit(Check): report.region = repl_group.region report.resource_id = repl_group.id report.resource_arn = repl_group.arn + report.resource_tags = repl_group.tags report.status = "FAIL" - report.status_extended = f"Elasticache Replication Group {repl_group.id} does not have in transit encryption enabled." + report.status_extended = f"Elasticache Redis cache cluster {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." + report.status_extended = f"Elasticache Redis cache cluster {repl_group.id} has in transit encryption enabled." findings.append(report) diff --git a/prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/__init__.py similarity index 100% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/__init__.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/__init__.py 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_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.metadata.json similarity index 78% rename from prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.metadata.json rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.metadata.json index 3a2f71151e..9246952f40 100644 --- 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_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.metadata.json @@ -1,14 +1,14 @@ { "Provider": "aws", - "CheckID": "elasticache_cluster_multi_az_enabled", - "CheckTitle": "Ensure Elasticache Cluster has Multi-AZ enabled.", + "CheckID": "elasticache_redis_cluster_multi_az_enabled", + "CheckTitle": "Ensure Elasticache Elasticache Redis cache 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.", + "Description": "Ensure Elasticache Elasticache Redis cache 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": { @@ -19,7 +19,7 @@ "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.", + "Text": "Ensure Elasticache Elasticache Redis cache cluster has Multi-AZ enabled.", "Url": "https://www.trendmicro.com/cloudoneconformity-staging/knowledge-base/aws/ElastiCache/elasticache-multi-az.html#" } }, 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_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.py similarity index 66% rename from prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.py index 68451af689..18b3aaa068 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled.py +++ b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled.py @@ -4,7 +4,7 @@ from prowler.providers.aws.services.elasticache.elasticache_client import ( ) -class elasticache_cluster_multi_az_enabled(Check): +class elasticache_redis_cluster_multi_az_enabled(Check): def execute(self): findings = [] for repl_group in elasticache_client.replication_groups.values(): @@ -12,15 +12,12 @@ class elasticache_cluster_multi_az_enabled(Check): report.region = repl_group.region report.resource_id = repl_group.id report.resource_arn = repl_group.arn + report.resource_tags = repl_group.tags report.status = "FAIL" - report.status_extended = ( - f"Elasticache Cluster {repl_group.id} does not have Multi-AZ enabled." - ) + report.status_extended = f"Elasticache Redis cache 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." - ) + report.status_extended = f"Elasticache Redis cache cluster {repl_group.id} has Multi-AZ enabled." findings.append(report) diff --git a/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/__init__.py b/prowler/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/__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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.metadata.json similarity index 72% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.metadata.json rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.metadata.json index bf59e37788..8540350e28 100644 --- 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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.metadata.json @@ -1,15 +1,15 @@ { "Provider": "aws", - "CheckID": "elasticache_replication_group_encrypted_at_rest", - "CheckTitle": "Ensure Elasticache Replication Groups have at rest encryption enabled.", + "CheckID": "elasticache_redis_cluster_rest_encryption_enabled", + "CheckTitle": "Ensure Elasticache Redis cache clusters 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.", + "Description": "Ensure Elasticache Redis cache clusters have at rest encryption enabled.", + "Risk": "There is a risk of exposing sensitive data if Elasticache Redis cache clusters does not have at rest encryption enabled.", "RelatedUrl": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-4", "Remediation": { "Code": { @@ -19,7 +19,7 @@ "Terraform": "https://docs.prowler.com/checks/aws/general-policies/general_9/" }, "Recommendation": { - "Text": "Ensure your Elasticache replication groups have at rest encryption enabled.", + "Text": "Ensure your Elasticache Redis cache clusters have at rest encryption enabled.", "Url": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/at-rest-encryption.html#at-rest-encryption-enable" } }, 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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.py similarity index 63% rename from prowler/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest.py rename to prowler/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.py index cc4bb433cc..5a2a990db5 100644 --- 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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled.py @@ -4,7 +4,7 @@ from prowler.providers.aws.services.elasticache.elasticache_client import ( ) -class elasticache_replication_group_encrypted_at_rest(Check): +class elasticache_redis_cluster_rest_encryption_enabled(Check): def execute(self): findings = [] for repl_group in elasticache_client.replication_groups.values(): @@ -12,11 +12,12 @@ class elasticache_replication_group_encrypted_at_rest(Check): report.region = repl_group.region report.resource_id = repl_group.id report.resource_arn = repl_group.arn + report.resource_tags = repl_group.tags report.status = "FAIL" - report.status_extended = f"Elasticache Replication Group {repl_group.id} does not have at rest encryption enabled." + report.status_extended = f"Elasticache Redis cache cluster {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." + report.status_extended = f"Elasticache Redis cache cluster {repl_group.id} has at rest encryption enabled." findings.append(report) diff --git a/prowler/providers/aws/services/elasticache/elasticache_service.py b/prowler/providers/aws/services/elasticache/elasticache_service.py index 10486d9d01..980e3540a1 100644 --- a/prowler/providers/aws/services/elasticache/elasticache_service.py +++ b/prowler/providers/aws/services/elasticache/elasticache_service.py @@ -1,6 +1,5 @@ from typing import Optional -from botocore.exceptions import ClientError from pydantic import BaseModel from prowler.lib.logger import logger @@ -17,31 +16,33 @@ class ElastiCache(AWSService): 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__) + self.__list_tags_for_resource__() def __describe_cache_clusters__(self, regional_client): + # Memcached Clusters and Redis Nodes logger.info("Elasticache - Describing Cache Clusters...") try: for cache_cluster in regional_client.describe_cache_clusters()[ "CacheClusters" ]: - cluster_arn = cache_cluster["ARN"] - if not self.audit_resources or ( - is_resource_filtered(cluster_arn, self.audit_resources) - ): - self.clusters[cluster_arn] = Cluster( - 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 - ), + try: + cluster_arn = cache_cluster["ARN"] + if not self.audit_resources or ( + is_resource_filtered(cluster_arn, self.audit_resources) + ): + self.clusters[cluster_arn] = Cluster( + id=cache_cluster["CacheClusterId"], + arn=cluster_arn, + region=regional_client.region, + engine=cache_cluster["Engine"], + cache_subnet_group_id=cache_cluster.get( + "CacheSubnetGroupName", None + ), + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) except Exception as error: logger.error( @@ -75,23 +76,31 @@ class ElastiCache(AWSService): f"{error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) - def __list_tags_for_resource__(self): - logger.info("Elasticache - Listing Tags...") + def __describe_replication_groups__(self, regional_client): + # Redis Clusters + logger.info("Elasticache - Describing Replication Groups...") try: - for cluster in self.clusters.values(): + for repl_group in regional_client.describe_replication_groups()[ + "ReplicationGroups" + ]: try: - regional_client = self.regional_clients[cluster.region] - cluster.tags = regional_client.list_tags_for_resource( - ResourceName=cluster.arn - )["TagList"] - except ClientError as error: - if error.response["Error"]["Code"] == "CacheClusterNotFound": - logger.warning( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" - ) - else: - logger.error( - f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + 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.get("AtRestEncryptionEnabled", False), + transit_encryption=repl_group.get( + "TransitEncryptionEnabled", False + ), + multi_az=repl_group.get("MultiAZ", "disabled"), ) except Exception as error: logger.error( @@ -102,25 +111,38 @@ 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...") + def __list_tags_for_resource__(self): + logger.info("Elasticache - Listing Tags...") 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"], + for cluster in self.clusters.values(): + try: + regional_client = self.regional_clients[cluster.region] + cluster.tags = regional_client.list_tags_for_resource( + ResourceName=cluster.arn + )["TagList"] + except regional_client.exceptions.CacheClusterNotFoundFault as error: + logger.warning( + 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}" + ) + for repl_group in self.replication_groups.values(): + try: + regional_client = self.regional_clients[repl_group.region] + repl_group.tags = regional_client.list_tags_for_resource( + ResourceName=repl_group.arn + )["TagList"] + except ( + regional_client.exceptions.ReplicationGroupNotFoundFault + ) as error: + logger.warning( + 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}" ) except Exception as error: logger.error( @@ -132,7 +154,7 @@ class Cluster(BaseModel): id: str arn: str region: str - security_groups: list[str] = [] + engine: str cache_subnet_group_id: Optional[str] subnets: list = [] tags: Optional[list] @@ -147,3 +169,4 @@ class ReplicationGroup(BaseModel): encrypted: bool transit_encryption: bool multi_az: str + tags: Optional[list] diff --git a/tests/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet_test.py b/tests/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet_test.py index b5875182d9..8529f0ad00 100644 --- a/tests/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet_test.py +++ b/tests/providers/aws/services/elasticache/elasticache_cluster_uses_public_subnet/elasticache_cluster_uses_public_subnet_test.py @@ -9,6 +9,8 @@ from tests.providers.aws.services.elasticache.elasticache_service_test import ( ELASTICACHE_CLUSTER_ARN, ELASTICACHE_CLUSTER_NAME, ELASTICACHE_CLUSTER_TAGS, + ELASTICACHE_ENGINE, + ELASTICACHE_ENGINE_MEMCACHED, SUBNET_1, SUBNET_2, SUBNET_GROUP_NAME, @@ -67,6 +69,7 @@ class Test_elasticache_cluster_uses_public_subnet: arn=ELASTICACHE_CLUSTER_ARN, name=ELASTICACHE_CLUSTER_NAME, id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE, region=AWS_REGION_US_EAST_1, cache_subnet_group_id=SUBNET_GROUP_NAME, tags=ELASTICACHE_CLUSTER_TAGS, @@ -99,7 +102,7 @@ class Test_elasticache_cluster_uses_public_subnet: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"Cluster {ELASTICACHE_CLUSTER_NAME} is not using public subnets." + == f"Elasticache Redis Node {ELASTICACHE_CLUSTER_NAME} is not using public subnets." ) assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == ELASTICACHE_CLUSTER_NAME @@ -115,6 +118,7 @@ class Test_elasticache_cluster_uses_public_subnet: arn=ELASTICACHE_CLUSTER_ARN, name=ELASTICACHE_CLUSTER_NAME, id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE, region=AWS_REGION_US_EAST_1, cache_subnet_group_id=SUBNET_GROUP_NAME, subnets=[SUBNET_1, SUBNET_2], @@ -176,7 +180,7 @@ class Test_elasticache_cluster_uses_public_subnet: assert result[0].status == "PASS" assert ( result[0].status_extended - == f"Cluster {ELASTICACHE_CLUSTER_NAME} is not using public subnets." + == f"Elasticache Redis Node {ELASTICACHE_CLUSTER_NAME} is not using public subnets." ) assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == ELASTICACHE_CLUSTER_NAME @@ -192,6 +196,7 @@ class Test_elasticache_cluster_uses_public_subnet: arn=ELASTICACHE_CLUSTER_ARN, name=ELASTICACHE_CLUSTER_NAME, id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE, region=AWS_REGION_US_EAST_1, cache_subnet_group_id=SUBNET_GROUP_NAME, subnets=[SUBNET_1, SUBNET_2], @@ -253,7 +258,163 @@ class Test_elasticache_cluster_uses_public_subnet: assert result[0].status == "FAIL" assert ( result[0].status_extended - == f"Cluster {ELASTICACHE_CLUSTER_NAME} is using subnet-1, subnet-2 public subnets." + == f"Elasticache Redis Node {ELASTICACHE_CLUSTER_NAME} is using subnet-1, subnet-2 public subnets." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == ELASTICACHE_CLUSTER_NAME + assert result[0].resource_arn == ELASTICACHE_CLUSTER_ARN + assert result[0].resource_tags == ELASTICACHE_CLUSTER_TAGS + + def test_elasticache_clusters_using_public_subnets_memcached(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.clusters = {} + + elasticache_service.clusters[ELASTICACHE_CLUSTER_ARN] = Cluster( + arn=ELASTICACHE_CLUSTER_ARN, + name=ELASTICACHE_CLUSTER_NAME, + id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE_MEMCACHED, + region=AWS_REGION_US_EAST_1, + cache_subnet_group_id=SUBNET_GROUP_NAME, + subnets=[SUBNET_1, SUBNET_2], + tags=ELASTICACHE_CLUSTER_TAGS, + ) + + # Mock VPC Service + vpc_client = MagicMock + vpc_client.vpc_subnets = {} + vpc_client.vpc_subnets[SUBNET_1] = VpcSubnet( + id=SUBNET_1, + name=SUBNET_1, + arn="arn_test", + default=False, + vpc_id=VPC_ID, + cidr_block="192.168.0.0/24", + availability_zone=AWS_REGION_US_EAST_1_AZA, + public=True, + nat_gateway=False, + region=AWS_REGION_US_EAST_1, + tags=[], + mapPublicIpOnLaunch=False, + ) + vpc_client.vpc_subnets[SUBNET_2] = VpcSubnet( + id=SUBNET_2, + name=SUBNET_2, + arn="arn_test", + default=False, + vpc_id=VPC_ID, + cidr_block="192.168.0.1/24", + availability_zone=AWS_REGION_US_EAST_1_AZB, + public=True, + nat_gateway=False, + region=AWS_REGION_US_EAST_1, + tags=[], + mapPublicIpOnLaunch=False, + ) + + 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, + ), mock.patch( + "prowler.providers.aws.services.vpc.vpc_service.VPC", + new=vpc_client, + ), mock.patch( + "prowler.providers.aws.services.vpc.vpc_client.vpc_client", + new=vpc_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_uses_public_subnet.elasticache_cluster_uses_public_subnet import ( + elasticache_cluster_uses_public_subnet, + ) + + check = elasticache_cluster_uses_public_subnet() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Memcached Cluster {ELASTICACHE_CLUSTER_NAME} is using subnet-1, subnet-2 public subnets." + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == ELASTICACHE_CLUSTER_NAME + assert result[0].resource_arn == ELASTICACHE_CLUSTER_ARN + assert result[0].resource_tags == ELASTICACHE_CLUSTER_TAGS + + def test_elasticache_clusters_using_private_subnets_memchached(self): + # Mock ElastiCache Service + elasticache_service = MagicMock + elasticache_service.clusters = {} + + elasticache_service.clusters[ELASTICACHE_CLUSTER_ARN] = Cluster( + arn=ELASTICACHE_CLUSTER_ARN, + name=ELASTICACHE_CLUSTER_NAME, + id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE_MEMCACHED, + region=AWS_REGION_US_EAST_1, + cache_subnet_group_id=SUBNET_GROUP_NAME, + subnets=[SUBNET_1, SUBNET_2], + tags=ELASTICACHE_CLUSTER_TAGS, + ) + + # Mock VPC Service + vpc_client = MagicMock + vpc_client.vpc_subnets = {} + vpc_client.vpc_subnets[SUBNET_1] = VpcSubnet( + id=SUBNET_1, + name=SUBNET_1, + arn="arn_test", + default=False, + vpc_id=VPC_ID, + cidr_block="192.168.0.0/24", + availability_zone=AWS_REGION_US_EAST_1_AZA, + public=False, + nat_gateway=False, + region=AWS_REGION_US_EAST_1, + tags=[], + mapPublicIpOnLaunch=False, + ) + vpc_client.vpc_subnets[SUBNET_2] = VpcSubnet( + id=SUBNET_2, + name=SUBNET_2, + arn="arn_test", + default=False, + vpc_id=VPC_ID, + cidr_block="192.168.0.1/24", + availability_zone=AWS_REGION_US_EAST_1_AZB, + public=False, + nat_gateway=False, + region=AWS_REGION_US_EAST_1, + tags=[], + mapPublicIpOnLaunch=False, + ) + + 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, + ), mock.patch( + "prowler.providers.aws.services.vpc.vpc_service.VPC", + new=vpc_client, + ), mock.patch( + "prowler.providers.aws.services.vpc.vpc_client.vpc_client", + new=vpc_client, + ): + from prowler.providers.aws.services.elasticache.elasticache_cluster_uses_public_subnet.elasticache_cluster_uses_public_subnet import ( + elasticache_cluster_uses_public_subnet, + ) + + check = elasticache_cluster_uses_public_subnet() + result = check.execute() + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Elasticache Memcached Cluster {ELASTICACHE_CLUSTER_NAME} is not using public subnets." ) assert result[0].region == AWS_REGION_US_EAST_1 assert result[0].resource_id == ELASTICACHE_CLUSTER_NAME 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_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled_test.py similarity index 59% rename from tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py rename to tests/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled_test.py index f4f6bd63ca..ab69a9c844 100644 --- a/tests/providers/aws/services/elasticache/elasticache_cluster_backup_enabled/elasticache_cluster_backup_enabled_test.py +++ b/tests/providers/aws/services/elasticache/elasticache_redis_cluster_backup_enabled/elasticache_redis_cluster_backup_enabled_test.py @@ -20,12 +20,15 @@ REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" REPLICATION_GROUP_ENCRYPTION = True REPLICATION_GROUP_TRANSIT_ENCRYPTION = True REPLICATION_GROUP_MULTI_AZ = "enabled" +REPLICATION_GROUP_TAGS = [ + {"Key": "environment", "Value": "test"}, +] # Patch every AWS call using Boto3 make_api_call = botocore.client.BaseClient._make_api_call -class Test_elasticache_cluster_backup_enabled: +class Test_elasticache_redis_cluster_backup_enabled: @mock_aws def test_elasticache_no_replication_groups(self): @@ -40,11 +43,11 @@ class Test_elasticache_cluster_backup_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_backup_enabled.elasticache_redis_cluster_backup_enabled import ( + elasticache_redis_cluster_backup_enabled, ) - check = elasticache_cluster_backup_enabled() + check = elasticache_redis_cluster_backup_enabled() result = check.execute() assert len(result) == 0 @@ -62,6 +65,7 @@ class Test_elasticache_cluster_backup_enabled: encrypted=False, transit_encryption=False, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7} @@ -72,24 +76,25 @@ class Test_elasticache_cluster_backup_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_backup_enabled.elasticache_redis_cluster_backup_enabled import ( + elasticache_redis_cluster_backup_enabled, ) - check = elasticache_cluster_backup_enabled() + check = elasticache_redis_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." + == f"Elasticache Redis cache 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS - def test_elasticache_cluster_backup_enabled(self): + def test_elasticache_redis_cluster_backup_enabled(self): # Mock ElastiCache Service elasticache_client = MagicMock elasticache_client.replication_groups = {} @@ -103,6 +108,7 @@ class Test_elasticache_cluster_backup_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7} @@ -114,24 +120,25 @@ class Test_elasticache_cluster_backup_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_backup_enabled.elasticache_redis_cluster_backup_enabled import ( + elasticache_redis_cluster_backup_enabled, ) - check = elasticache_cluster_backup_enabled() + check = elasticache_redis_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." + == f"Elasticache Redis cache 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS - def test_elasticache_cluster_backup_enabled_modified_retention(self): + def test_elasticache_redis_cluster_backup_enabled_modified_retention(self): # Mock ElastiCache Service elasticache_client = MagicMock elasticache_client.replication_groups = {} @@ -145,6 +152,7 @@ class Test_elasticache_cluster_backup_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) elasticache_client.audit_config = {"minimum_snapshot_retention_period": 1} @@ -155,19 +163,64 @@ class Test_elasticache_cluster_backup_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_backup_enabled.elasticache_redis_cluster_backup_enabled import ( + elasticache_redis_cluster_backup_enabled, ) - check = elasticache_cluster_backup_enabled() + check = elasticache_redis_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." + == f"Elasticache Redis cache 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS + + def test_elasticache_redis_cluster_backup_enabled_low_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=2, + encrypted=REPLICATION_GROUP_ENCRYPTION, + transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, + multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, + ) + + elasticache_client.audit_config = {"minimum_snapshot_retention_period": 3} + 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_redis_cluster_backup_enabled.elasticache_redis_cluster_backup_enabled import ( + elasticache_redis_cluster_backup_enabled, + ) + + check = elasticache_redis_cluster_backup_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Elasticache Redis cache cluster {REPLICATION_GROUP_ID} has automated snapshot backups enabled with retention period 2 days. Recommended to increase the snapshot retention period to a minimum of 7 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS + assert result[0].check_metadata.Severity == "low" 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled_test.py similarity index 77% rename from tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_in_transit/elasticache_replication_group_encrypted_in_transit_test.py rename to tests/providers/aws/services/elasticache/elasticache_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled_test.py index cd0cfe9bf9..25c025902a 100644 --- 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_redis_cluster_in_transit_encryption_enabled/elasticache_redis_cluster_in_transit_encryption_enabled_test.py @@ -20,6 +20,10 @@ REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" REPLICATION_GROUP_ENCRYPTION = True REPLICATION_GROUP_TRANSIT_ENCRYPTION = True REPLICATION_GROUP_MULTI_AZ = "enabled" +REPLICATION_GROUP_TAGS = [ + {"Key": "environment", "Value": "test"}, +] + # Patch every AWS call using Boto3 make_api_call = botocore.client.BaseClient._make_api_call @@ -40,11 +44,11 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_in_transit_encryption_enabled.elasticache_redis_cluster_in_transit_encryption_enabled import ( + elasticache_redis_cluster_in_transit_encryption_enabled, ) - check = elasticache_replication_group_encrypted_in_transit() + check = elasticache_redis_cluster_in_transit_encryption_enabled() result = check.execute() assert len(result) == 0 @@ -63,6 +67,7 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=False, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) ) @@ -73,22 +78,23 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_in_transit_encryption_enabled.elasticache_redis_cluster_in_transit_encryption_enabled import ( + elasticache_redis_cluster_in_transit_encryption_enabled, ) - check = elasticache_replication_group_encrypted_in_transit() + check = elasticache_redis_cluster_in_transit_encryption_enabled() 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." + == f"Elasticache Redis cache cluster {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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS def test_elasticache_replication_groups_in_transit_encryption_enabled(self): # Mock ElastiCache Service @@ -105,6 +111,7 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) ) @@ -115,19 +122,20 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_in_transit_encryption_enabled.elasticache_redis_cluster_in_transit_encryption_enabled import ( + elasticache_redis_cluster_in_transit_encryption_enabled, ) - check = elasticache_replication_group_encrypted_in_transit() + check = elasticache_redis_cluster_in_transit_encryption_enabled() 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." + == f"Elasticache Redis cache cluster {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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS 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_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled_test.py similarity index 78% rename from tests/providers/aws/services/elasticache/elasticache_cluster_multi_az_enabled/elasticache_cluster_multi_az_enabled_test.py rename to tests/providers/aws/services/elasticache/elasticache_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled_test.py index e8928dbd5a..b0f4bd4b42 100644 --- 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_redis_cluster_multi_az_enabled/elasticache_redis_cluster_multi_az_enabled_test.py @@ -20,6 +20,10 @@ REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" REPLICATION_GROUP_ENCRYPTION = True REPLICATION_GROUP_TRANSIT_ENCRYPTION = True REPLICATION_GROUP_MULTI_AZ = "enabled" +REPLICATION_GROUP_TAGS = [ + {"Key": "environment", "Value": "test"}, +] + # Patch every AWS call using Boto3 make_api_call = botocore.client.BaseClient._make_api_call @@ -40,11 +44,11 @@ class Test_elasticache_replication_group_multi_az_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_multi_az_enabled.elasticache_redis_cluster_multi_az_enabled import ( + elasticache_redis_cluster_multi_az_enabled, ) - check = elasticache_cluster_multi_az_enabled() + check = elasticache_redis_cluster_multi_az_enabled() result = check.execute() assert len(result) == 0 @@ -63,6 +67,7 @@ class Test_elasticache_replication_group_multi_az_enabled: encrypted=False, transit_encryption=False, multi_az="disabled", + tags=REPLICATION_GROUP_TAGS, ) ) @@ -73,24 +78,25 @@ class Test_elasticache_replication_group_multi_az_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_multi_az_enabled.elasticache_redis_cluster_multi_az_enabled import ( + elasticache_redis_cluster_multi_az_enabled, ) - check = elasticache_cluster_multi_az_enabled() + check = elasticache_redis_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." + == f"Elasticache Redis cache 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS - def test_elasticache_cluster_multi_az_enabled(self): + def test_elasticache_redis_cluster_multi_az_enabled(self): # Mock ElastiCache Service elasticache_service = MagicMock elasticache_service.replication_groups = {} @@ -105,6 +111,7 @@ class Test_elasticache_replication_group_multi_az_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) ) @@ -115,19 +122,20 @@ class Test_elasticache_replication_group_multi_az_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_multi_az_enabled.elasticache_redis_cluster_multi_az_enabled import ( + elasticache_redis_cluster_multi_az_enabled, ) - check = elasticache_cluster_multi_az_enabled() + check = elasticache_redis_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." + == f"Elasticache Redis cache 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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS 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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled_test.py similarity index 78% rename from tests/providers/aws/services/elasticache/elasticache_replication_group_encrypted_at_rest/elasticache_replication_group_encrypted_at_rest_test.py rename to tests/providers/aws/services/elasticache/elasticache_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled_test.py index cdae55254c..9c0a371701 100644 --- 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_redis_cluster_rest_encryption_enabled/elasticache_redis_cluster_rest_encryption_enabled_test.py @@ -20,6 +20,9 @@ REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" REPLICATION_GROUP_ENCRYPTION = True REPLICATION_GROUP_TRANSIT_ENCRYPTION = True REPLICATION_GROUP_MULTI_AZ = True +REPLICATION_GROUP_TAGS = [ + {"Key": "environment", "Value": "test"}, +] # Patch every AWS call using Boto3 make_api_call = botocore.client.BaseClient._make_api_call @@ -40,11 +43,11 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_rest_encryption_enabled.elasticache_redis_cluster_rest_encryption_enabled import ( + elasticache_redis_cluster_rest_encryption_enabled, ) - check = elasticache_replication_group_encrypted_at_rest() + check = elasticache_redis_cluster_rest_encryption_enabled() result = check.execute() assert len(result) == 0 @@ -63,6 +66,7 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled: encrypted=False, transit_encryption=False, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) ) @@ -73,22 +77,23 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_rest_encryption_enabled.elasticache_redis_cluster_rest_encryption_enabled import ( + elasticache_redis_cluster_rest_encryption_enabled, ) - check = elasticache_replication_group_encrypted_at_rest() + check = elasticache_redis_cluster_rest_encryption_enabled() 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." + == f"Elasticache Redis cache cluster {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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS def test_elasticache_replication_groups_at_rest_encryption_enabled(self): # Mock ElastiCache Service @@ -105,6 +110,7 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, ) ) @@ -115,19 +121,20 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled: "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, + from prowler.providers.aws.services.elasticache.elasticache_redis_cluster_rest_encryption_enabled.elasticache_redis_cluster_rest_encryption_enabled import ( + elasticache_redis_cluster_rest_encryption_enabled, ) - check = elasticache_replication_group_encrypted_at_rest() + check = elasticache_redis_cluster_rest_encryption_enabled() 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." + == f"Elasticache Redis cache cluster {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 + assert result[0].resource_tags == REPLICATION_GROUP_TAGS diff --git a/tests/providers/aws/services/elasticache/elasticache_service_test.py b/tests/providers/aws/services/elasticache/elasticache_service_test.py index 3fada1164b..c4bb7dcca1 100644 --- a/tests/providers/aws/services/elasticache/elasticache_service_test.py +++ b/tests/providers/aws/services/elasticache/elasticache_service_test.py @@ -21,6 +21,7 @@ SUBNET_2 = "subnet-2" ELASTICACHE_CLUSTER_NAME = "test-cluster" ELASTICACHE_CLUSTER_ARN = f"arn:aws:elasticache:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:{ELASTICACHE_CLUSTER_NAME}" ELASTICACHE_ENGINE = "redis" +ELASTICACHE_ENGINE_MEMCACHED = "memcached" ELASTICACHE_CLUSTER_TAGS = [ {"Key": "environment", "Value": "test"}, @@ -33,6 +34,10 @@ REPLICATION_GROUP_SNAPSHOT_RETENTION = "0" REPLICATION_GROUP_ENCRYPTION = True REPLICATION_GROUP_TRANSIT_ENCRYPTION = True REPLICATION_GROUP_MULTI_AZ = "enabled" +REPLICATION_GROUP_TAGS = [ + {"Key": "environment", "Value": "test"}, +] + # Mocking Access Analyzer Calls make_api_call = botocore.client.BaseClient._make_api_call @@ -53,6 +58,7 @@ def mock_make_api_call(self, operation_name, kwargs): "CacheClusterId": ELASTICACHE_CLUSTER_NAME, "CacheSubnetGroupName": SUBNET_GROUP_NAME, "ARN": ELASTICACHE_CLUSTER_ARN, + "Engine": ELASTICACHE_ENGINE, "SecurityGroups": [], }, ] @@ -143,7 +149,7 @@ class Test_ElastiCache_Service: elasticache = ElastiCache(aws_provider) assert elasticache.audited_account == AWS_ACCOUNT_NUMBER - # Test ElastiCache Clusters + # Test Elasticache Redis cache clusters def test_describe_cache_clusters(self): aws_provider = set_mocked_aws_provider() elasticache = ElastiCache(aws_provider) @@ -154,6 +160,7 @@ class Test_ElastiCache_Service: arn=ELASTICACHE_CLUSTER_ARN, name=ELASTICACHE_CLUSTER_NAME, id=ELASTICACHE_CLUSTER_NAME, + engine=ELASTICACHE_ENGINE, region=AWS_REGION_US_EAST_1, security_groups=[], cache_subnet_group_id=SUBNET_GROUP_NAME, @@ -161,7 +168,7 @@ class Test_ElastiCache_Service: tags=ELASTICACHE_CLUSTER_TAGS, ) - # Test ElastiCache Replication Groups + # Test Elasticache Redis cache clusters def test_describe_replication_groups(self): aws_provider = set_mocked_aws_provider() elasticache = ElastiCache(aws_provider) @@ -179,4 +186,5 @@ class Test_ElastiCache_Service: encrypted=REPLICATION_GROUP_ENCRYPTION, transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION, multi_az=REPLICATION_GROUP_MULTI_AZ, + tags=REPLICATION_GROUP_TAGS, )