Skip to content

Commit

Permalink
feat(elasticache): Ensure Redis replication groups have automatic fai…
Browse files Browse the repository at this point in the history
…lover enabled (#4853)

Co-authored-by: Sergio <[email protected]>
  • Loading branch information
HugoPBrito and MrCloudSec authored Sep 12, 2024
1 parent edbe463 commit cc8bc78
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -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": ""
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def _describe_replication_groups(self, regional_client):
auto_minor_version_upgrade=repl_group.get(
"AutoMinorVersionUpgrade", False
),
automatic_failover=repl_group.get(
"AutomaticFailoverStatus", "disabled"
),
)
except Exception as error:
logger.error(
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
)
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,
REPLICATION_GROUP_TRANSIT_ENCRYPTION,
)
from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider

Expand Down Expand Up @@ -52,49 +52,6 @@ def test_elasticache_no_clusters(self):
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
Expand All @@ -112,6 +69,7 @@ def test_elasticache_clusters_auto_minor_version_upgrades_disabled(self):
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=not AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)

Expand Down Expand Up @@ -156,6 +114,7 @@ def test_elasticache_clusters_auto_minor_version_upgrades_enabled(self):
multi_az=REPLICATION_GROUP_MULTI_AZ,
tags=REPLICATION_GROUP_TAGS,
auto_minor_version_upgrade=AUTO_MINOR_VERSION_UPGRADE,
automatic_failover=AUTOMATIC_FAILOVER,
)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
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,
Expand All @@ -23,7 +27,6 @@
REPLICATION_GROUP_TAGS = [
{"Key": "environment", "Value": "test"},
]

# Patch every AWS call using Boto3
make_api_call = botocore.client.BaseClient._make_api_call

Expand Down Expand Up @@ -66,6 +69,8 @@ def test_elasticache_cluster_backup_disabled(self):
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}
Expand Down Expand Up @@ -109,6 +114,8 @@ def test_elasticache_redis_cluster_backup_enabled(self):
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}
Expand Down Expand Up @@ -153,6 +160,8 @@ def test_elasticache_redis_cluster_backup_enabled_modified_retention(self):
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}
Expand Down Expand Up @@ -196,6 +205,8 @@ def test_elasticache_redis_cluster_backup_enabled_low_retention(self):
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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
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,
Expand Down Expand Up @@ -68,6 +72,8 @@ def test_elasticache_replication_groups_in_transit_encryption_disabled(self):
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,
)
)

Expand Down Expand Up @@ -112,6 +118,8 @@ def test_elasticache_replication_groups_in_transit_encryption_enabled(self):
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,
)
)

Expand Down
Loading

0 comments on commit cc8bc78

Please sign in to comment.