From df373279e932d24ae5f2d553ab70f8f7d4d8a6e0 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:00:13 +0200 Subject: [PATCH] feat(mq): add new check `mq_broker_cluster_deployment_mode` (#5481) Co-authored-by: Sergio --- .../__init__.py | 0 ...oker_cluster_deployment_mode.metadata.json | 32 +++ .../mq_broker_cluster_deployment_mode.py | 36 ++++ .../providers/aws/services/mq/mq_service.py | 31 ++- .../mq_broker_cluster_deployment_mode_test.py | 183 ++++++++++++++++++ .../aws/services/mq/mq_service_test.py | 15 +- 6 files changed, 285 insertions(+), 12 deletions(-) create mode 100644 prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/__init__.py create mode 100644 prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.metadata.json create mode 100644 prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.py create mode 100644 tests/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode_test.py diff --git a/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/__init__.py b/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.metadata.json b/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.metadata.json new file mode 100644 index 0000000000..dd33fa7ef5 --- /dev/null +++ b/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "mq_broker_cluster_deployment_mode", + "CheckTitle": "MQ RabbitMQ Brokers should use cluster deployment mode.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "mq", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:mq:region:account-id:broker:broker-id", + "Severity": "low", + "ResourceType": "AwsAmazonMQBroker", + "Description": "Ensure that RabbitMQ Brokers use cluster deployment mode.", + "Risk": "Using a single-instance RabbitMQ broker limits fault tolerance and high availability. Without cluster deployment, broker failures could lead to significant downtime and potential data loss.", + "RelatedUrl": "https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/rabbitmq-basic-elements.html", + "Remediation": { + "Code": { + "CLI": "aws mq create-broker --broker-name --engine-type RabbitMQ --deployment-mode CLUSTER_MULTI_AZ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/mq-controls.html#mq-6", + "Terraform": "" + }, + "Recommendation": { + "Text": "Ensure RabbitMQ brokers are deployed in cluster mode to enhance resilience and prevent data loss during failures.", + "Url": "https://docs.aws.amazon.com/amazon-mq/latest/developer-guide/rabbitmq-broker-architecture.html#rabbitmq-broker-architecture-cluster" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.py b/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.py new file mode 100644 index 0000000000..920fc98db0 --- /dev/null +++ b/prowler/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode.py @@ -0,0 +1,36 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.mq.mq_client import mq_client +from prowler.providers.aws.services.mq.mq_service import DeploymentMode, EngineType + + +class mq_broker_cluster_deployment_mode(Check): + """Ensure MQ RabbitMQ Broker has cluster deployment mode. + + This check will fail if the RabbitMQ Broker does not have cluster deployment mode. + """ + + def execute(self) -> List[Check_Report_AWS]: + """Execute the check. + + Returns: + List[Check_Report_AWS]: A list of reports for each RabbitMQ Broker that does not have cluster deployment mode. + """ + findings = [] + for broker in mq_client.brokers.values(): + if broker.engine_type == EngineType.RABBITMQ: + report = Check_Report_AWS(self.metadata()) + report.region = broker.region + report.resource_id = broker.id + report.resource_arn = broker.arn + report.resource_tags = broker.tags + report.status = "FAIL" + report.status_extended = f"MQ RabbitMQ Broker {broker.name} does not have a cluster deployment mode." + if broker.deployment_mode == DeploymentMode.CLUSTER_MULTI_AZ: + report.status = "PASS" + report.status_extended = f"MQ RabbitMQ Broker {broker.name} does have a cluster deployment mode." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/mq/mq_service.py b/prowler/providers/aws/services/mq/mq_service.py index 7de9265347..63a6e8d39a 100644 --- a/prowler/providers/aws/services/mq/mq_service.py +++ b/prowler/providers/aws/services/mq/mq_service.py @@ -1,6 +1,7 @@ -from typing import Dict, List, Optional +from enum import Enum +from typing import Dict, List -from pydantic import BaseModel +from pydantic import BaseModel, Field from prowler.lib.logger import logger from prowler.lib.scan_filters.scan_filters import is_resource_filtered @@ -42,13 +43,33 @@ class MQ(AWSService): broker.auto_minor_version_upgrade = describe_broker.get( "AutoMinorVersionUpgrade", False ) - broker.tags = [describe_broker.get("Tags", {})] + broker.engine_type = EngineType( + describe_broker.get("EngineType", "ACTIVEMQ") + ) + broker.deployment_mode = DeploymentMode( + describe_broker.get("DeploymentMode", "SINGLE_INSTANCE") + ) except Exception as error: logger.error( f"{broker.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) +class DeploymentMode(Enum): + """Possible Deployment Modes for MQ""" + + SINGLE_INSTANCE = "SINGLE_INSTANCE" + ACTIVE_STANDBY_MULTI_AZ = "ACTIVE_STANDBY_MULTI_AZ" + CLUSTER_MULTI_AZ = "CLUSTER_MULTI_AZ" + + +class EngineType(Enum): + """Possible Engine Types for MQ""" + + ACTIVEMQ = "ACTIVEMQ" + RABBITMQ = "RABBITMQ" + + class Broker(BaseModel): """Broker model for MQ""" @@ -57,4 +78,6 @@ class Broker(BaseModel): id: str region: str auto_minor_version_upgrade: bool = False - tags: Optional[List[Dict[str, str]]] + engine_type: EngineType = EngineType.ACTIVEMQ + deployment_mode: DeploymentMode = DeploymentMode.SINGLE_INSTANCE + tags: List[Dict[str, str]] = Field(default_factory=list) diff --git a/tests/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode_test.py b/tests/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode_test.py new file mode 100644 index 0000000000..5d951ffb1e --- /dev/null +++ b/tests/providers/aws/services/mq/mq_broker_cluster_deployment_mode/mq_broker_cluster_deployment_mode_test.py @@ -0,0 +1,183 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +class Test_mq_rabbitmq_broker_cluster_mode: + @mock_aws + def test_no_brokers(self): + from prowler.providers.aws.services.mq.mq_service import MQ + + 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, + ): + with mock.patch( + "prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode.mq_client", + new=MQ(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode import ( + mq_broker_cluster_deployment_mode, + ) + + check = mq_broker_cluster_deployment_mode() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_no_rabbitmq_brokers(self): + from prowler.providers.aws.services.mq.mq_service import MQ + + mq_client = client("mq", region_name=AWS_REGION_US_EAST_1) + mq_client.create_broker( + BrokerName="test-broker", + EngineType="ACTIVEMQ", + EngineVersion="5.15.0", + HostInstanceType="mq.t2.micro", + Users=[ + { + "Username": "admin", + "Password": "admin", + }, + ], + DeploymentMode="ACTIVE_STANDBY_MULTI_AZ", + PubliclyAccessible=False, + AutoMinorVersionUpgrade=True, + )["BrokerId"] + + 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, + ): + with mock.patch( + "prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode.mq_client", + new=MQ(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode import ( + mq_broker_cluster_deployment_mode, + ) + + check = mq_broker_cluster_deployment_mode() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_rabbitmq_broker_cluster_mode_enabled(self): + mq_client = client("mq", region_name=AWS_REGION_US_EAST_1) + broker_name = "test-broker" + broker_id = mq_client.create_broker( + BrokerName="test-broker", + EngineType="RABBITMQ", + EngineVersion="5.15.0", + HostInstanceType="mq.t2.micro", + Users=[ + { + "Username": "admin", + "Password": "admin", + }, + ], + DeploymentMode="CLUSTER_MULTI_AZ", + PubliclyAccessible=False, + AutoMinorVersionUpgrade=True, + )["BrokerId"] + + from prowler.providers.aws.services.mq.mq_service import MQ + + 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, + ): + with mock.patch( + "prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode.mq_client", + new=MQ(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode import ( + mq_broker_cluster_deployment_mode, + ) + + check = mq_broker_cluster_deployment_mode() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"MQ RabbitMQ Broker {broker_name} does have a cluster deployment mode." + ) + assert result[0].resource_id == broker_id + assert ( + result[0].resource_arn + == f"arn:{aws_provider.identity.partition}:mq:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:broker:{broker_id}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + + @mock_aws + def test_rabbitmq_broker_active_standby_mode_disabled(self): + mq_client = client("mq", region_name=AWS_REGION_US_EAST_1) + broker_name = "test-broker" + broker_id = mq_client.create_broker( + BrokerName=broker_name, + EngineType="RABBITMQ", + EngineVersion="5.15.0", + HostInstanceType="mq.t2.micro", + Users=[ + { + "Username": "admin", + "Password": "admin", + }, + ], + DeploymentMode="SINGLE_INSTANCE", + PubliclyAccessible=False, + AutoMinorVersionUpgrade=False, + )["BrokerId"] + + from prowler.providers.aws.services.mq.mq_service import MQ + + 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, + ): + with mock.patch( + "prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode.mq_client", + new=MQ(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.mq.mq_broker_cluster_deployment_mode.mq_broker_cluster_deployment_mode import ( + mq_broker_cluster_deployment_mode, + ) + + check = mq_broker_cluster_deployment_mode() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"MQ RabbitMQ Broker {broker_name} does not have a cluster deployment mode." + ) + assert result[0].resource_id == broker_id + assert ( + result[0].resource_arn + == f"arn:{aws_provider.identity.partition}:mq:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:broker:{broker_id}" + ) + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/mq/mq_service_test.py b/tests/providers/aws/services/mq/mq_service_test.py index 3c25f38b9a..33efeea231 100644 --- a/tests/providers/aws/services/mq/mq_service_test.py +++ b/tests/providers/aws/services/mq/mq_service_test.py @@ -1,7 +1,7 @@ from boto3 import client from moto import mock_aws -from prowler.providers.aws.services.mq.mq_service import MQ +from prowler.providers.aws.services.mq.mq_service import MQ, DeploymentMode, EngineType from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider @@ -34,7 +34,7 @@ class Test_MQ_Service: # Test MQ List Brokers @mock_aws def test_list_brokers(self): - # Generate moto MQ client + # Generate MQ client mq_client = client("mq", region_name=AWS_REGION_EU_WEST_1) broker = mq_client.create_broker( AutoMinorVersionUpgrade=True, @@ -55,7 +55,7 @@ class Test_MQ_Service: ) broker_arn = broker["BrokerArn"] - # MQ client for this test class + # MQ Client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) mq = MQ(aws_provider) @@ -68,7 +68,7 @@ class Test_MQ_Service: # Test MQ Describe Broker @mock_aws def test_describe_broker(self): - # Generate moto MQ client + # Generate MQ client mq_client = client("mq", region_name=AWS_REGION_EU_WEST_1) broker = mq_client.create_broker( AutoMinorVersionUpgrade=True, @@ -86,12 +86,11 @@ class Test_MQ_Service: "Username": "user", } ], - Tags={"key": "value"}, ) broker_arn = broker["BrokerArn"] broker["BrokerId"] - # MQ client for this test class + # MQ Client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) mq = MQ(aws_provider) @@ -100,5 +99,5 @@ class Test_MQ_Service: assert mq.brokers[broker_arn].name == "my-broker" assert mq.brokers[broker_arn].region == AWS_REGION_EU_WEST_1 assert mq.brokers[broker_arn].id == broker["BrokerId"] - assert mq.brokers[broker_arn].auto_minor_version_upgrade - assert mq.brokers[broker_arn].tags == [{"key": "value"}] + assert mq.brokers[broker_arn].engine_type == EngineType.ACTIVEMQ + assert mq.brokers[broker_arn].deployment_mode == DeploymentMode.SINGLE_INSTANCE