From 64aa0435e86c69907ef64b31421301a2d0b9e2fc Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Wed, 28 Feb 2024 10:03:43 +0100 Subject: [PATCH] fix(security_hub): Handle user facing errors (#3456) --- .../aws/lib/security_hub/security_hub.py | 26 +++- .../aws/lib/security_hub/security_hub_test.py | 122 ++++++++++++++++++ 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/prowler/providers/aws/lib/security_hub/security_hub.py b/prowler/providers/aws/lib/security_hub/security_hub.py index 221cdc3c..23569e5c 100644 --- a/prowler/providers/aws/lib/security_hub/security_hub.py +++ b/prowler/providers/aws/lib/security_hub/security_hub.py @@ -1,4 +1,5 @@ from boto3 import session +from botocore.client import ClientError from prowler.config.config import timestamp_utc from prowler.lib.logger import logger @@ -11,7 +12,7 @@ SECURITY_HUB_MAX_BATCH = 100 def prepare_security_hub_findings( - findings: [], audit_info: AWS_Audit_Info, output_options, enabled_regions: [] + findings: list, audit_info: AWS_Audit_Info, output_options, enabled_regions: list ) -> dict: security_hub_findings_per_region = {} @@ -72,15 +73,32 @@ def verify_security_hub_integration_enabled_per_region( if security_hub_prowler_integration_arn not in str( security_hub_client.list_enabled_products_for_import() ): - logger.error( + logger.warning( f"Security Hub is enabled in {region} but Prowler integration does not accept findings. More info: https://docs.prowler.cloud/en/latest/tutorials/aws/securityhub/" ) else: prowler_integration_enabled = True + # Handle all the permissions / configuration errors + except ClientError as client_error: + # Check if Account is subscribed to Security Hub + error_code = client_error.response["Error"]["Code"] + error_message = client_error.response["Error"]["Message"] + if ( + error_code == "InvalidAccessException" + and f"Account {aws_account_number} is not subscribed to AWS Security Hub in region {region}" + in error_message + ): + logger.warning( + f"{client_error.__class__.__name__} -- [{client_error.__traceback__.tb_lineno}]: {client_error}" + ) + else: + logger.error( + f"{client_error.__class__.__name__} -- [{client_error.__traceback__.tb_lineno}]: {client_error}" + ) except Exception as error: logger.error( - f"{error.__class__.__name__} -- [{error.__traceback__.tb_lineno}]:{error} in region {region}" + f"{error.__class__.__name__} -- [{error.__traceback__.tb_lineno}]: {error}" ) finally: @@ -169,7 +187,7 @@ def resolve_security_hub_previous_findings( def __send_findings_to_security_hub__( - findings: [dict], region: str, security_hub_client + findings: list[dict], region: str, security_hub_client ): """Private function send_findings_to_security_hub chunks the findings in groups of 100 findings and send them to AWS Security Hub. It returns the number of sent findings.""" success_count = 0 diff --git a/tests/providers/aws/lib/security_hub/security_hub_test.py b/tests/providers/aws/lib/security_hub/security_hub_test.py index 5e584000..c4e47b21 100644 --- a/tests/providers/aws/lib/security_hub/security_hub_test.py +++ b/tests/providers/aws/lib/security_hub/security_hub_test.py @@ -1,7 +1,9 @@ +from logging import ERROR, WARNING from os import path import botocore from boto3 import session +from botocore.client import ClientError from mock import MagicMock, patch from prowler.config.config import prowler_version, timestamp_utc @@ -130,6 +132,126 @@ class Test_SecurityHub: AWS_COMMERCIAL_PARTITION, AWS_REGION_EU_WEST_1, session, AWS_ACCOUNT_NUMBER ) + def test_verify_security_hub_integration_enabled_per_region_security_hub_disabled( + self, caplog + ): + caplog.set_level(WARNING) + session = self.set_mocked_session(AWS_REGION_EU_WEST_1) + + with patch( + "prowler.providers.aws.lib.security_hub.security_hub.session.Session.client", + ) as mock_security_hub: + error_message = f"Account {AWS_ACCOUNT_NUMBER} is not subscribed to AWS Security Hub in region {AWS_REGION_EU_WEST_1}" + error_code = "InvalidAccessException" + error_response = { + "Error": { + "Code": error_code, + "Message": error_message, + } + } + operation_name = "DescribeHub" + mock_security_hub.side_effect = ClientError(error_response, operation_name) + + assert not verify_security_hub_integration_enabled_per_region( + AWS_COMMERCIAL_PARTITION, + AWS_REGION_EU_WEST_1, + session, + AWS_ACCOUNT_NUMBER, + ) + assert caplog.record_tuples == [ + ( + "root", + WARNING, + f"ClientError -- [68]: An error occurred ({error_code}) when calling the {operation_name} operation: {error_message}", + ) + ] + + def test_verify_security_hub_integration_enabled_per_region_prowler_not_subscribed( + self, caplog + ): + caplog.set_level(WARNING) + session = self.set_mocked_session(AWS_REGION_EU_WEST_1) + + with patch( + "prowler.providers.aws.lib.security_hub.security_hub.session.Session.client", + ) as mock_security_hub: + mock_security_hub.describe_hub.return_value = None + mock_security_hub.list_enabled_products_for_import.return_value = [] + + assert not verify_security_hub_integration_enabled_per_region( + AWS_COMMERCIAL_PARTITION, + AWS_REGION_EU_WEST_1, + session, + AWS_ACCOUNT_NUMBER, + ) + assert caplog.record_tuples == [ + ( + "root", + WARNING, + f"Security Hub is enabled in {AWS_REGION_EU_WEST_1} but Prowler integration does not accept findings. More info: https://docs.prowler.cloud/en/latest/tutorials/aws/securityhub/", + ) + ] + + def test_verify_security_hub_integration_enabled_per_region_another_ClientError( + self, caplog + ): + caplog.set_level(WARNING) + session = self.set_mocked_session(AWS_REGION_EU_WEST_1) + + with patch( + "prowler.providers.aws.lib.security_hub.security_hub.session.Session.client", + ) as mock_security_hub: + error_message = f"Another exception in region {AWS_REGION_EU_WEST_1}" + error_code = "AnotherException" + error_response = { + "Error": { + "Code": error_code, + "Message": error_message, + } + } + operation_name = "DescribeHub" + mock_security_hub.side_effect = ClientError(error_response, operation_name) + + assert not verify_security_hub_integration_enabled_per_region( + AWS_COMMERCIAL_PARTITION, + AWS_REGION_EU_WEST_1, + session, + AWS_ACCOUNT_NUMBER, + ) + assert caplog.record_tuples == [ + ( + "root", + ERROR, + f"ClientError -- [68]: An error occurred ({error_code}) when calling the {operation_name} operation: {error_message}", + ) + ] + + def test_verify_security_hub_integration_enabled_per_region_another_Exception( + self, caplog + ): + caplog.set_level(WARNING) + session = self.set_mocked_session(AWS_REGION_EU_WEST_1) + + with patch( + "prowler.providers.aws.lib.security_hub.security_hub.session.Session.client", + ) as mock_security_hub: + error_message = f"Another exception in region {AWS_REGION_EU_WEST_1}" + mock_security_hub.side_effect = Exception(error_message) + + assert not verify_security_hub_integration_enabled_per_region( + AWS_COMMERCIAL_PARTITION, + AWS_REGION_EU_WEST_1, + session, + AWS_ACCOUNT_NUMBER, + ) + assert caplog.record_tuples == [ + ( + "root", + ERROR, + f"Exception -- [68]: {error_message}", + ) + ] + def test_prepare_security_hub_findings_enabled_region_not_quiet(self): enabled_regions = [AWS_REGION_EU_WEST_1] output_options = self.set_mocked_output_options(is_quiet=False)