diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/__init__.py b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.metadata.json b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.metadata.json new file mode 100644 index 0000000000..03a23473ac --- /dev/null +++ b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.metadata.json @@ -0,0 +1,30 @@ +{ + "Provider": "aws", + "CheckID": "dynamodb_accelerator_cluster_multi_az", + "CheckTitle": "Check if DynamoDB Accelerator (DAX) clusters have nodes in multiple availability zones.", + "CheckType": [], + "ServiceName": "dynamodb", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:dax:region:account-id:cache/table-name", + "Severity": "medium", + "ResourceType": "Other", + "Description": "This control checks whether an Amazon DynamoDB Accelerator (DAX) cluster has nodes in multiple availability zones.", + "Risk": "Without DAX nodes in multiple availability zones (AZ) the nodes are at risk of interruption if an AZ disruption occurs.", + "RelatedUrl": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.concepts.cluster.html#DAX.concepts.regions-and-azs", + "Remediation": { + "Code": { + "CLI": "", + "NativeIaC": "", + "Other": "", + "Terraform": "" + }, + "Recommendation": { + "Text": "Create a DAX cluster with nodes in multiple availability zones.", + "Url": "https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.concepts.cluster.html#DAX.concepts.regions-and-azs" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.py b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.py new file mode 100644 index 0000000000..02db43eff7 --- /dev/null +++ b/prowler/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az.py @@ -0,0 +1,20 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.dynamodb.dax_client import dax_client + + +class dynamodb_accelerator_cluster_multi_az(Check): + def execute(self): + findings = [] + for cluster in dax_client.clusters: + report = Check_Report_AWS(self.metadata()) + report.resource_id = cluster.name + report.resource_arn = cluster.arn + report.region = cluster.region + report.resource_tags = cluster.tags + report.status = "FAIL" + report.status_extended = f"DAX cluster {cluster.name} does not have nodes in multiple availability zones." + if len(cluster.node_azs) > 1: + report.status = "PASS" + report.status_extended = f"DAX cluster {cluster.name} has nodes in multiple availability zones." + findings.append(report) + return findings diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_service.py b/prowler/providers/aws/services/dynamodb/dynamodb_service.py index edf5bf0c37..ef31276196 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_service.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_service.py @@ -185,6 +185,10 @@ class DAX(AWSService): arn=cluster["ClusterArn"], name=cluster["ClusterName"], encryption=encryption, + node_azs=[ + node["AvailabilityZone"] + for node in cluster.get("Nodes", {}) + ], region=regional_client.region, tls_encryption=tls_encryption, ) @@ -232,6 +236,7 @@ class Cluster(BaseModel): arn: str name: str encryption: bool + node_azs: Optional[list] = [] region: str tags: Optional[list] = [] tls_encryption: bool diff --git a/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py b/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py index 921e410171..a805204d64 100644 --- a/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py +++ b/prowler/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled.py @@ -13,10 +13,10 @@ class dynamodb_tables_kms_cmk_encryption_enabled(Check): report.region = table.region report.status = "FAIL" report.status_extended = ( - f"DynamoDB table {table.name} does have DEFAULT encryption enabled." + f"DynamoDB table {table.name} is using DEFAULT encryption." ) if table.encryption_type == "KMS": report.status = "PASS" - report.status_extended = f"DynamoDB table {table.name} does have KMS encryption enabled with key {table.kms_arn.split('/')[1]}." + report.status_extended = f"DynamoDB table {table.name} has KMS encryption enabled with key {table.kms_arn.split('/')[1]}." findings.append(report) return findings diff --git a/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled_test.py b/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled_test.py index 228883b280..c8c096c669 100644 --- a/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled_test.py +++ b/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_encryption_enabled/dynamodb_accelerator_cluster_encryption_enabled_test.py @@ -1,4 +1,3 @@ -from re import search from unittest import mock from boto3 import client @@ -47,6 +46,7 @@ class Test_dynamodb_accelerator_cluster_encryption_enabled: NodeType="dax.t3.small", ReplicationFactor=3, IamRoleArn=iam_role_arn, + ClusterEndpointEncryptionType="TLS", )["Cluster"] from prowler.providers.aws.services.dynamodb.dynamodb_service import DAX @@ -71,9 +71,9 @@ class Test_dynamodb_accelerator_cluster_encryption_enabled: assert len(result) == 1 assert result[0].status == "FAIL" - assert search( - "does not have encryption at rest enabled", - result[0].status_extended, + assert ( + result[0].status_extended + == "DAX cluster daxcluster does not have encryption at rest enabled." ) assert result[0].resource_id == cluster["ClusterName"] assert result[0].resource_arn == cluster["ClusterArn"] @@ -89,6 +89,7 @@ class Test_dynamodb_accelerator_cluster_encryption_enabled: NodeType="dax.t3.small", ReplicationFactor=3, IamRoleArn=iam_role_arn, + ClusterEndpointEncryptionType="TLS", SSESpecification={"Enabled": True}, )["Cluster"] from prowler.providers.aws.services.dynamodb.dynamodb_service import DAX @@ -114,7 +115,10 @@ class Test_dynamodb_accelerator_cluster_encryption_enabled: assert len(result) == 1 assert result[0].status == "PASS" - assert search("has encryption at rest enabled", result[0].status_extended) + assert ( + result[0].status_extended + == "DAX cluster daxcluster has encryption at rest enabled." + ) assert result[0].resource_id == cluster["ClusterName"] assert result[0].resource_arn == cluster["ClusterArn"] assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az_test.py b/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az_test.py new file mode 100644 index 0000000000..edc581afee --- /dev/null +++ b/tests/providers/aws/services/dynamodb/dynamodb_accelerator_cluster_multi_az/dynamodb_accelerator_cluster_multi_az_test.py @@ -0,0 +1,125 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_dynamodb_accelerator_cluster_multi_az: + @mock_aws + def test_dax_no_clusters(self): + from prowler.providers.aws.services.dynamodb.dynamodb_service import DAX + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az.dax_client", + new=DAX(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az import ( + dynamodb_accelerator_cluster_multi_az, + ) + + check = dynamodb_accelerator_cluster_multi_az() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_dax_cluster_no_multi_az(self): + dax_client = client("dax", region_name=AWS_REGION_US_EAST_1) + iam_role_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/aws-service-role/dax.amazonaws.com/AWSServiceRoleForDAX" + cluster = dax_client.create_cluster( + ClusterName="daxcluster", + NodeType="dax.t3.small", + ReplicationFactor=3, + IamRoleArn=iam_role_arn, + )["Cluster"] + from prowler.providers.aws.services.dynamodb.dynamodb_service import DAX + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az.dax_client", + new=DAX(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az import ( + dynamodb_accelerator_cluster_multi_az, + ) + + check = dynamodb_accelerator_cluster_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "DAX cluster daxcluster does not have nodes in multiple availability zones." + ) + assert result[0].resource_id == cluster["ClusterName"] + assert result[0].resource_arn == cluster["ClusterArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] + + @mock_aws + def test_dax_cluster_with_multi_az(self): + dax_client = client("dax", region_name=AWS_REGION_US_EAST_1) + iam_role_arn = f"arn:aws:iam::{AWS_ACCOUNT_NUMBER}:role/aws-service-role/dax.amazonaws.com/AWSServiceRoleForDAX" + cluster = dax_client.create_cluster( + ClusterName="daxcluster", + NodeType="dax.t3.small", + ReplicationFactor=3, + IamRoleArn=iam_role_arn, + ClusterEndpointEncryptionType="TLS", + )["Cluster"] + from prowler.providers.aws.services.dynamodb.dynamodb_service import DAX + + aws_provider = set_mocked_aws_provider( + [AWS_REGION_EU_WEST_1, AWS_REGION_US_EAST_1] + ) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az.dax_client", + new=DAX(aws_provider), + ) as service_client: + # Test Check + from prowler.providers.aws.services.dynamodb.dynamodb_accelerator_cluster_multi_az.dynamodb_accelerator_cluster_multi_az import ( + dynamodb_accelerator_cluster_multi_az, + ) + + # Setting node_azs manually as Moto does not support that yet. + service_client.clusters[0].node_azs = ["us-east-1a", "us-east-1b"] + check = dynamodb_accelerator_cluster_multi_az() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "DAX cluster daxcluster has nodes in multiple availability zones." + ) + assert result[0].resource_id == cluster["ClusterName"] + assert result[0].resource_arn == cluster["ClusterArn"] + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_tags == [] diff --git a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py index 9920338049..bef0aee35f 100644 --- a/tests/providers/aws/services/dynamodb/dynamodb_service_test.py +++ b/tests/providers/aws/services/dynamodb/dynamodb_service_test.py @@ -195,7 +195,6 @@ class Test_DynamoDB_Service: {"Key": "test", "Value": "test"}, ] assert dax.clusters[0].tls_encryption - assert dax.clusters[1].name == "daxcluster2" assert dax.clusters[1].region == AWS_REGION_US_EAST_1 assert dax.clusters[1].encryption diff --git a/tests/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled_test.py b/tests/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled_test.py index 4bb6c8b982..45817c081a 100644 --- a/tests/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled_test.py +++ b/tests/providers/aws/services/dynamodb/dynamodb_tables_kms_cmk_encryption_enabled/dynamodb_tables_kms_cmk_encryption_enabled_test.py @@ -1,4 +1,3 @@ -from re import search from unittest import mock from boto3 import client @@ -76,7 +75,10 @@ class Test_dynamodb_tables_kms_cmk_encryption_enabled: assert len(result) == 1 assert result[0].status == "PASS" - assert search("KMS encryption enabled", result[0].status_extended) + assert ( + result[0].status_extended + == "DynamoDB table test1 has KMS encryption enabled with key custom-kms-key." + ) assert result[0].resource_id == table["TableName"] assert result[0].resource_arn == table["TableArn"] assert result[0].region == AWS_REGION_US_EAST_1 @@ -120,7 +122,10 @@ class Test_dynamodb_tables_kms_cmk_encryption_enabled: assert len(result) == 1 assert result[0].status == "FAIL" - assert search("DEFAULT encryption enabled", result[0].status_extended) + assert ( + result[0].status_extended + == "DynamoDB table test1 is using DEFAULT encryption." + ) assert result[0].resource_id == table["TableName"] assert result[0].resource_arn == table["TableArn"] assert result[0].region == AWS_REGION_US_EAST_1