feat(elasticache): Ensure Redis replication groups have automatic failover enabled (#4853)

Co-authored-by: Sergio <sergio@prowler.com>
This commit is contained in:
Hugo Pereira Brito
2024-09-12 18:23:15 +02:00
committed by GitHub
parent edbe463d73
commit cc8bc781c1
11 changed files with 244 additions and 46 deletions
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "elasticache_redis_cluster_automatic_failover_enabled",
"CheckTitle": "Ensure Elasticache Redis clusters have automatic failover enabled.",
"CheckType": [],
"ServiceName": "elasticache",
"SubServiceName": "",
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
"Severity": "medium",
"ResourceType": "AWSElastiCacheReplicationGroup",
"Description": "Ensure Elasticache Redis OSS cache clusters use automatic failover.",
"Risk": "If automatic failover is not enabled, a failure in the primary node could result in significant downtime, impacting the availability and resilience of your application.",
"RelatedUrl": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/AutoFailover.html",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/elasticache-controls.html#elasticache-3",
"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": "Enable automatic failover for ElastiCache (Redis OSS) clusters to ensure high availability and minimize downtime during failures.",
"Url": "https://redis.io/blog/highly-available-in-memory-cloud-datastores/"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.elasticache.elasticache_client import (
elasticache_client,
)
class elasticache_redis_cluster_automatic_failover_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.resource_tags = repl_group.tags
report.status = "FAIL"
report.status_extended = f"Elasticache Redis cache cluster {repl_group.id} does not have automatic failover enabled."
if repl_group.automatic_failover == "enabled":
report.status = "PASS"
report.status_extended = f"Elasticache Redis cache cluster {repl_group.id} does have automatic failover enabled."
findings.append(report)
return findings
@@ -106,6 +106,9 @@ class ElastiCache(AWSService):
auto_minor_version_upgrade=repl_group.get(
"AutoMinorVersionUpgrade", False
),
automatic_failover=repl_group.get(
"AutomaticFailoverStatus", "disabled"
),
)
except Exception as error:
logger.error(
@@ -176,4 +179,5 @@ class ReplicationGroup(BaseModel):
transit_encryption: bool
multi_az: str
tags: Optional[list]
auto_minor_version_upgrade: bool = False
auto_minor_version_upgrade: bool
automatic_failover: str
@@ -7,6 +7,7 @@ from prowler.providers.aws.services.elasticache.elasticache_service import (
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
REPLICATION_GROUP_ARN,
REPLICATION_GROUP_ENCRYPTION,
REPLICATION_GROUP_ID,
@@ -14,7 +15,6 @@ from tests.providers.aws.services.elasticache.elasticache_service_test import (
REPLICATION_GROUP_SNAPSHOT_RETENTION,
REPLICATION_GROUP_STATUS,
REPLICATION_GROUP_TAGS,
REPLICATION_GROUP_TRANSIT_ENCRYPTION,
)
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
@@ -52,49 +52,6 @@ class Test_elasticache_redis_cluster_auto_minor_version_upgrades:
result = check.execute()
assert len(result) == 0
def test_elasticache_clusters_auto_minor_version_upgrades_undefined(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,
tags=REPLICATION_GROUP_TAGS,
)
)
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_redis_cluster_auto_minor_version_upgrades.elasticache_redis_cluster_auto_minor_version_upgrades import (
elasticache_redis_cluster_auto_minor_version_upgrades,
)
check = elasticache_redis_cluster_auto_minor_version_upgrades()
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} does not have automated minor version upgrades 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_clusters_auto_minor_version_upgrades_disabled(self):
# Mock ElastiCache Service
elasticache_service = MagicMock
@@ -112,6 +69,7 @@ class Test_elasticache_redis_cluster_auto_minor_version_upgrades:
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -156,6 +114,7 @@ class Test_elasticache_redis_cluster_auto_minor_version_upgrades:
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -0,0 +1,143 @@
from unittest import mock
from mock import MagicMock
from prowler.providers.aws.services.elasticache.elasticache_service import (
ReplicationGroup,
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
REPLICATION_GROUP_ARN,
REPLICATION_GROUP_ENCRYPTION,
REPLICATION_GROUP_ID,
REPLICATION_GROUP_MULTI_AZ,
REPLICATION_GROUP_SNAPSHOT_RETENTION,
REPLICATION_GROUP_STATUS,
REPLICATION_GROUP_TAGS,
)
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider
VPC_ID = "vpc-12345678901234567"
class Test_elasticache_redis_cluster_automatic_failover_enabled:
def test_elasticache_no_clusters(self):
# Mock VPC Service
vpc_client = MagicMock
vpc_client.vpc_subnets = {}
# 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,
), 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_redis_cluster_automatic_failover_enabled.elasticache_redis_cluster_automatic_failover_enabled import (
elasticache_redis_cluster_automatic_failover_enabled,
)
check = elasticache_redis_cluster_automatic_failover_enabled()
result = check.execute()
assert len(result) == 0
def test_elasticache_clusters_automatic_failover_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,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover="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_redis_cluster_automatic_failover_enabled.elasticache_redis_cluster_automatic_failover_enabled import (
elasticache_redis_cluster_automatic_failover_enabled,
)
check = elasticache_redis_cluster_automatic_failover_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} does not have automatic failover 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_clusters_automatic_failover_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=False,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
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_redis_cluster_automatic_failover_enabled.elasticache_redis_cluster_automatic_failover_enabled import (
elasticache_redis_cluster_automatic_failover_enabled,
)
check = elasticache_redis_cluster_automatic_failover_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"Elasticache Redis cache cluster {REPLICATION_GROUP_ID} does have automatic failover 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
@@ -7,6 +7,10 @@ from moto import mock_aws
from prowler.providers.aws.services.elasticache.elasticache_service import (
ReplicationGroup,
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
@@ -23,7 +27,6 @@ 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
@@ -66,6 +69,8 @@ class Test_elasticache_redis_cluster_backup_enabled:
transit_encryption=False,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7}
@@ -109,6 +114,8 @@ class Test_elasticache_redis_cluster_backup_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
elasticache_client.audit_config = {"minimum_snapshot_retention_period": 7}
@@ -153,6 +160,8 @@ class Test_elasticache_redis_cluster_backup_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
elasticache_client.audit_config = {"minimum_snapshot_retention_period": 1}
@@ -196,6 +205,8 @@ class Test_elasticache_redis_cluster_backup_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
elasticache_client.audit_config = {"minimum_snapshot_retention_period": 3}
@@ -7,6 +7,10 @@ from moto import mock_aws
from prowler.providers.aws.services.elasticache.elasticache_service import (
ReplicationGroup,
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
@@ -68,6 +72,8 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled:
transit_encryption=False,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -112,6 +118,8 @@ class Test_elasticache_replication_group_in_transit_encryption_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -7,6 +7,10 @@ from moto import mock_aws
from prowler.providers.aws.services.elasticache.elasticache_service import (
ReplicationGroup,
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
@@ -68,6 +72,8 @@ class Test_elasticache_replication_group_multi_az_enabled:
transit_encryption=False,
multi_az="disabled",
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -112,6 +118,8 @@ class Test_elasticache_replication_group_multi_az_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -7,6 +7,10 @@ from moto import mock_aws
from prowler.providers.aws.services.elasticache.elasticache_service import (
ReplicationGroup,
)
from tests.providers.aws.services.elasticache.elasticache_service_test import (
AUTO_MINOR_VERSION_UPGRADE,
AUTOMATIC_FAILOVER,
)
from tests.providers.aws.utils import (
AWS_ACCOUNT_NUMBER,
AWS_REGION_US_EAST_1,
@@ -67,6 +71,8 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled:
transit_encryption=False,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -111,6 +117,8 @@ class Test_elasticache_replication_group_at_rest_encryption_enabled:
transit_encryption=REPLICATION_GROUP_TRANSIT_ENCRYPTION,
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)
@@ -38,6 +38,7 @@ REPLICATION_GROUP_TAGS = [
{"Key": "environment", "Value": "test"},
]
AUTO_MINOR_VERSION_UPGRADE = True
AUTOMATIC_FAILOVER = "enabled"
# Mocking Access Analyzer Calls
@@ -192,4 +193,5 @@ class Test_ElastiCache_Service:
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover="disabled",
)