feat(aws): Add new RDS check to ensure db clusters are configured for multiple availability zones (#4781)

This commit is contained in:
Daniel Barranquero
2024-08-22 13:49:59 +02:00
committed by GitHub
parent a2144ad353
commit 0005f86a5f
5 changed files with 240 additions and 71 deletions
@@ -0,0 +1,30 @@
{
"Provider": "aws",
"CheckID": "rds_cluster_multi_az",
"CheckTitle": "Check if RDS clusters have multi-AZ enabled.",
"CheckType": [],
"ServiceName": "rds",
"SubServiceName": "",
"ResourceIdTemplate": "arn:aws:rds:region:account-id:db-cluster",
"Severity": "medium",
"ResourceType": "AwsRdsDbCluster",
"Description": "Check if RDS clusters have multi-AZ enabled.",
"Risk": "In case of failure, with a single-AZ deployment configuration, should an availability zone specific database failure occur, Amazon RDS can not automatically fail over to the standby availability zone.",
"RelatedUrl": "https://aws.amazon.com/rds/features/multi-az/",
"Remediation": {
"Code": {
"CLI": "aws rds create-db-cluster --db-cluster-identifier <db_cluster_id> --multi-az true",
"NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/general_73#cloudformation",
"Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/rds-controls.html#rds-15",
"Terraform": "https://docs.prowler.com/checks/aws/general-policies/general_73#terraform"
},
"Recommendation": {
"Text": "Enable multi-AZ deployment for production databases.",
"Url": "https://aws.amazon.com/rds/features/multi-az/"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}
@@ -0,0 +1,26 @@
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.rds.rds_client import rds_client
class rds_cluster_multi_az(Check):
def execute(self):
findings = []
for db_cluster_arn, db_cluster in rds_client.db_clusters.items():
report = Check_Report_AWS(self.metadata())
report.region = db_cluster.region
report.resource_id = db_cluster.id
report.resource_arn = db_cluster_arn
report.resource_tags = db_cluster.tags
report.status = "FAIL"
report.status_extended = (
f"RDS Cluster {db_cluster.id} does not have multi-AZ enabled."
)
if db_cluster.multi_az:
report.status = "PASS"
report.status_extended = (
f"RDS Cluster {db_cluster.id} has multi-AZ enabled."
)
findings.append(report)
return findings
@@ -0,0 +1,138 @@
from unittest import mock
from prowler.providers.aws.services.rds.rds_service import DBCluster
from tests.providers.aws.utils import AWS_ACCOUNT_NUMBER, AWS_REGION_US_EAST_1
class Test_rds_cluster_multi_az:
def test_rds_no_clusters(self):
rds_client = mock.MagicMock
rds_client.db_clusters = {}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
new=rds_client,
), mock.patch(
"prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az.rds_client",
new=rds_client,
):
from prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az import (
rds_cluster_multi_az,
)
check = rds_cluster_multi_az()
result = check.execute()
assert len(result) == 0
def test_rds_cluster_no_multi_az(self):
rds_client = mock.MagicMock
cluster_arn = (
f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-cluster-1"
)
rds_client.db_clusters = {
cluster_arn: DBCluster(
id="db-cluster-1",
arn=cluster_arn,
endpoint="",
engine="aurora",
status="available",
public=False,
encrypted=False,
auto_minor_version_upgrade=False,
backup_retention_period=0,
cloudwatch_logs=[],
deletion_protection=False,
parameter_group="",
multi_az=False,
username="test",
iam_auth=False,
backtrack=0,
region=AWS_REGION_US_EAST_1,
tags=[],
)
}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
new=rds_client,
), mock.patch(
"prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az.rds_client",
new=rds_client,
):
from prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az import (
rds_cluster_multi_az,
)
check = rds_cluster_multi_az()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS Cluster db-cluster-1 does not have multi-AZ enabled."
)
assert result[0].resource_id == "db-cluster-1"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-cluster-1"
)
assert result[0].resource_tags == []
def test_rds_cluster_multi_az(self):
rds_client = mock.MagicMock
cluster_arn = (
f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-cluster-1"
)
rds_client.db_clusters = {
cluster_arn: DBCluster(
id="db-cluster-1",
arn=cluster_arn,
endpoint="",
engine="aurora",
status="available",
public=False,
encrypted=False,
auto_minor_version_upgrade=False,
backup_retention_period=0,
cloudwatch_logs=[],
deletion_protection=False,
parameter_group="",
multi_az=True,
username="test",
iam_auth=False,
backtrack=0,
region=AWS_REGION_US_EAST_1,
tags=[],
)
}
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
new=rds_client,
), mock.patch(
"prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az.rds_client",
new=rds_client,
):
from prowler.providers.aws.services.rds.rds_cluster_multi_az.rds_cluster_multi_az import (
rds_cluster_multi_az,
)
check = rds_cluster_multi_az()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS Cluster db-cluster-1 has multi-AZ enabled."
)
assert result[0].resource_id == "db-cluster-1"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:db-cluster-1"
)
assert result[0].resource_tags == []
@@ -1,6 +1,5 @@
from unittest import mock
import botocore
from boto3 import client
from moto import mock_aws
@@ -11,25 +10,7 @@ from tests.providers.aws.utils import (
set_mocked_aws_provider,
)
make_api_call = botocore.client.BaseClient._make_api_call
def mock_make_api_call(self, operation_name, kwarg):
if operation_name == "DescribeDBEngineVersions":
return {
"DBEngineVersions": [
{
"Engine": "mysql",
"EngineVersion": "8.0.32",
"DBEngineDescription": "description",
"DBEngineVersionDescription": "description",
},
]
}
return make_api_call(self, operation_name, kwarg)
@mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call)
class Test_rds_instance_multi_az:
@mock_aws
def test_rds_no_instances(self):
@@ -195,37 +176,34 @@ class Test_rds_instance_multi_az:
)
]
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
new=rds_client,
), mock.patch(
"prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
new=rds_client,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
new=rds_client,
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
rds_instance_multi_az,
)
# Test Check
from prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
rds_instance_multi_az,
)
check = rds_instance_multi_az()
result = check.execute()
check = rds_instance_multi_az()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS Instance test-instance has multi-AZ enabled at cluster test-cluster level."
)
assert result[0].resource_id == "test-instance"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:test-instance"
)
assert result[0].resource_tags == []
assert len(result) == 1
assert result[0].status == "PASS"
assert (
result[0].status_extended
== "RDS Instance test-instance has multi-AZ enabled at cluster test-cluster level."
)
assert result[0].resource_id == "test-instance"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:test-instance"
)
assert result[0].resource_tags == []
def test_rds_instance_in_cluster_without_multi_az(self):
rds_client = mock.MagicMock
@@ -277,34 +255,31 @@ class Test_rds_instance_multi_az:
)
]
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=aws_provider,
new=rds_client,
), mock.patch(
"prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
new=rds_client,
):
with mock.patch(
"prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az.rds_client",
new=rds_client,
):
# Test Check
from prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
rds_instance_multi_az,
)
# Test Check
from prowler.providers.aws.services.rds.rds_instance_multi_az.rds_instance_multi_az import (
rds_instance_multi_az,
)
check = rds_instance_multi_az()
result = check.execute()
check = rds_instance_multi_az()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS Instance test-instance does not have multi-AZ enabled at cluster test-cluster level."
)
assert result[0].resource_id == "test-instance"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:test-instance"
)
assert result[0].resource_tags == []
assert len(result) == 1
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== "RDS Instance test-instance does not have multi-AZ enabled at cluster test-cluster level."
)
assert result[0].resource_id == "test-instance"
assert result[0].region == AWS_REGION_US_EAST_1
assert (
result[0].resource_arn
== f"arn:aws:rds:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:db:test-instance"
)
assert result[0].resource_tags == []