From f25b9fa9dc7f00706d068f28ca69d8649887c5b9 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 4 Nov 2024 12:41:58 +0100 Subject: [PATCH 1/7] fix: minor correction in mq __init__ --- prowler/providers/aws/services/mq/mq_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/mq/mq_service.py b/prowler/providers/aws/services/mq/mq_service.py index c66c05a4858..c4b5f99f001 100644 --- a/prowler/providers/aws/services/mq/mq_service.py +++ b/prowler/providers/aws/services/mq/mq_service.py @@ -11,7 +11,7 @@ class MQ(AWSService): def __init__(self, provider): # Call AWSService's __init__ - super().__init__("mq", provider) + super().__init__(__class__.__name__, provider) self.brokers = {} self.__threading_call__(self._list_brokers) self.__threading_call__(self._describe_broker, self.brokers.values()) From 832bc203fb8ffff76731485eba50957021727c65 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Mon, 4 Nov 2024 13:12:13 +0100 Subject: [PATCH 2/7] feat: create firehose client --- .../aws/services/firehose/__init__.py | 0 .../aws/services/firehose/firehose_client.py | 4 ++ .../aws/services/firehose/firehose_service.py | 61 ++++++++++++++++ .../firehose/firehose_service_test.py | 69 +++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 prowler/providers/aws/services/firehose/__init__.py create mode 100644 prowler/providers/aws/services/firehose/firehose_client.py create mode 100644 prowler/providers/aws/services/firehose/firehose_service.py create mode 100644 tests/providers/aws/services/firehose/firehose_service_test.py diff --git a/prowler/providers/aws/services/firehose/__init__.py b/prowler/providers/aws/services/firehose/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/firehose/firehose_client.py b/prowler/providers/aws/services/firehose/firehose_client.py new file mode 100644 index 00000000000..46d3bdeab7e --- /dev/null +++ b/prowler/providers/aws/services/firehose/firehose_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.firehose.firehose_service import Firehose +from prowler.providers.common.provider import Provider + +firehose_client = Firehose(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/firehose/firehose_service.py b/prowler/providers/aws/services/firehose/firehose_service.py new file mode 100644 index 00000000000..5933326b635 --- /dev/null +++ b/prowler/providers/aws/services/firehose/firehose_service.py @@ -0,0 +1,61 @@ +from typing import Dict, List, Optional + +from botocore.client import ClientError +from pydantic import BaseModel, Field + +from prowler.lib.logger import logger +from prowler.lib.scan_filters.scan_filters import is_resource_filtered +from prowler.providers.aws.lib.service.service import AWSService + + +class Firehose(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.delivery_streams = {} + self.tags = [] + self.__threading_call__(self._list_delivery_streams) + self.__threading_call__( + self._list_tags_for_delivery_stream, self.delivery_streams.values() + ) + + def _list_delivery_streams(self, regional_client): + logger.info("Firehose - Listing delivery streams...") + try: + for stream in regional_client.list_delivery_streams()[ + "DeliveryStreamNames" + ]: + if not self.audit_resources or ( + is_resource_filtered(stream, self.audit_resources) + ): + stream_region = regional_client.region + stream_arn = f"arn:{self.audited_partition}:firehose:{stream_region}:{self.audited_account}:deliverystream/{stream}" + self.delivery_streams[stream_arn] = DeliveryStream( + arn=stream_arn, + name=stream, + region=stream_region, + ) + except ClientError as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _list_tags_for_delivery_stream(self, stream): + try: + stream.tags = ( + self.regional_clients[stream.region] + .list_tags_for_delivery_stream(DeliveryStreamName=stream.name) + .get("Tags", []) + ) + print(stream.tags) + except ClientError as error: + logger.error( + f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class DeliveryStream(BaseModel): + arn: str + name: str + region: str + tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) diff --git a/tests/providers/aws/services/firehose/firehose_service_test.py b/tests/providers/aws/services/firehose/firehose_service_test.py new file mode 100644 index 00000000000..46d6c8c5226 --- /dev/null +++ b/tests/providers/aws/services/firehose/firehose_service_test.py @@ -0,0 +1,69 @@ +from boto3 import client +from moto import mock_aws + +from prowler.providers.aws.services.firehose.firehose_service import Firehose +from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider + + +class Test_Firehose_Service: + # Test Firehose Service + @mock_aws + def test_service(self): + # Firehose client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + assert firehose.service == "firehose" + + # Test Firehose Client + @mock_aws + def test_client(self): + # Firehose client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + for regional_client in firehose.regional_clients.values(): + assert regional_client.__class__.__name__ == "Firehose" + + # Test Firehose Session + @mock_aws + def test__get_session__(self): + # Firehose client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + assert firehose.session.__class__.__name__ == "Session" + + # Test Firehose List Delivery Streams + @mock_aws + def test_list_delivery_streams(self): + # Generate S3 client + s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1) + s3_client.create_bucket( + Bucket="test-bucket", + CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1}, + ) + + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + delivery_stream_name = arn.split("/")[-1] + + # Firehose Client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + + assert len(firehose.delivery_streams) == 1 + assert firehose.delivery_streams[arn].arn == arn + assert firehose.delivery_streams[arn].name == delivery_stream_name + assert firehose.delivery_streams[arn].region == AWS_REGION_EU_WEST_1 + assert firehose.delivery_streams[arn].tags == [{"Key": "key", "Value": "value"}] From c1f6a06c7ad0a85a8c7d1722a6c23608cb65fb27 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 5 Nov 2024 09:59:03 +0100 Subject: [PATCH 3/7] feat: add check metadata --- .../__init__.py | 0 ...ose_stream_encrypted_at_rest.metadata.json | 32 +++++++++++++++++++ .../firehose_stream_encrypted_at_rest.py | 0 3 files changed, 32 insertions(+) create mode 100644 prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/__init__.py create mode 100644 prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.metadata.json create mode 100644 prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.metadata.json new file mode 100644 index 00000000000..007994df8a2 --- /dev/null +++ b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "firehose_stream_encrypted_at_rest", + "CheckTitle": "DataFirehose delivery streams should be encrypted at rest.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "DataFirehose", + "SubServiceName": "Ensure DataFirehose delivery streams are encrypted at rest.", + "ResourceIdTemplate": "arn:partition:firehose:region:account-id:deliverystream/delivery-stream-id", + "Severity": "medium", + "ResourceType": "AwsKinesisFirehoseDeliveryStream", + "Description": "", + "Risk": "Without encryption at rest, data in Amazon Kinesis Data Firehose delivery streams is vulnerable to unauthorized access if the storage layer is compromised. This increases the risk of sensitive information exposure, potentially leading to data breaches or non-compliance with security regulations.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html", + "Remediation": { + "Code": { + "CLI": "aws firehose update-delivery-stream --delivery-stream-name --delivery-stream-encryption-configuration-input '{ \"KeyType\": \"CUSTOMER_MANAGED_CMK\", \"KeyARN\": \"\" }'", + "NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/ensure-aws-kinesis-firehoses-delivery-stream-is-encrypted/", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/datafirehose-controls.html#datafirehose-1", + "Terraform": "https://www.trendmicro.com/cloudoneconformity/knowledge-base/aws/Firehose/delivery-stream-encrypted-with-kms-customer-master-keys.html" + }, + "Recommendation": { + "Text": "Enable server-side encryption for Kinesis Firehose delivery streams using AWS Key Management Service (KMS). This encrypts data at rest, ensuring that sensitive information remains secure and compliant with regulatory standards.", + "Url": "https://docs.aws.amazon.com/firehose/latest/dev/encryption.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py new file mode 100644 index 00000000000..e69de29bb2d From 07a74906b5a7f708d29cac9b25a2e9dbcf78ea0e Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 5 Nov 2024 12:01:48 +0100 Subject: [PATCH 4/7] feat: added check logic --- .../aws/services/firehose/firehose_service.py | 36 +++++++++++++++++++ .../firehose_stream_encrypted_at_rest.py | 29 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/prowler/providers/aws/services/firehose/firehose_service.py b/prowler/providers/aws/services/firehose/firehose_service.py index 5933326b635..ac75f577536 100644 --- a/prowler/providers/aws/services/firehose/firehose_service.py +++ b/prowler/providers/aws/services/firehose/firehose_service.py @@ -1,3 +1,4 @@ +from enum import Enum from typing import Dict, List, Optional from botocore.client import ClientError @@ -18,6 +19,9 @@ def __init__(self, provider): self.__threading_call__( self._list_tags_for_delivery_stream, self.delivery_streams.values() ) + self.__threading_call__( + self._describe_delivery_stream, self.delivery_streams.values() + ) def _list_delivery_streams(self, regional_client): logger.info("Firehose - Listing delivery streams...") @@ -53,9 +57,41 @@ def _list_tags_for_delivery_stream(self, stream): f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _describe_delivery_stream(self, stream): + try: + describe_stream = self.regional_clients[ + stream.region + ].describe_delivery_stream(DeliveryStreamName=stream.name) + encryption_config = describe_stream.get( + "DeliveryStreamDescription", {} + ).get("DeliveryStreamEncryptionConfiguration", {}) + stream.kms_encryption = EncryptionStatus( + encryption_config.get("Status", "DISABLED") + ) + stream.kms_key_arn = encryption_config.get("KeyARN", "") + except ClientError as error: + logger.error( + f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class EncryptionStatus(Enum): + """Possible values for the status of the encryption of a Firehose stream""" + + ENABLED = "ENABLED" + DISABLED = "DISABLED" + ENABLING = "ENABLING" + DISABLING = "DISABLING" + ENABLING_FAILED = "ENABLING_FAILED" + DISABLING_FAILED = "DISABLING_FAILED" + class DeliveryStream(BaseModel): + """Model for a Firehose Delivery Stream""" + arn: str name: str region: str tags: Optional[List[Dict[str, str]]] = Field(default_factory=list) + kms_key_arn: Optional[str] = "" + kms_encryption: Optional[str] = "" diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py index e69de29bb2d..d066a0744a1 100644 --- a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py +++ b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py @@ -0,0 +1,29 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.firehose.firehose_client import firehose_client +from prowler.providers.aws.services.firehose.firehose_service import EncryptionStatus + + +class firehose_stream_encrypted_at_rest(Check): + def execute(self): + findings = [] + for stream in firehose_client.delivery_streams.values(): + report = Check_Report_AWS(self.metadata()) + report.region = stream.region + report.resource_id = stream.name + report.resource_arn = stream.arn + report.resource_tags = stream.tags + report.status = "PASS" + report.status_extended = ( + f"Firehose Stream {stream.name} does have at rest encryption enabled." + ) + + if ( + stream.kms_encryption != EncryptionStatus.ENABLED + or not stream.kms_key_arn + ): + report.status = "FAIL" + report.status_extended = f"Firehose Stream {stream.name} does not have at rest encryption enabled." + + findings.append(report) + + return findings From e65f776000eeb2219f5cb889e0a74c5a6c1051a9 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 5 Nov 2024 12:02:17 +0100 Subject: [PATCH 5/7] feat: added testing --- .../firehose/firehose_service_test.py | 68 +++++- .../firehose_stream_encrypted_at_rest_test.py | 195 ++++++++++++++++++ 2 files changed, 258 insertions(+), 5 deletions(-) create mode 100644 tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py diff --git a/tests/providers/aws/services/firehose/firehose_service_test.py b/tests/providers/aws/services/firehose/firehose_service_test.py index 46d6c8c5226..bef7abebf8b 100644 --- a/tests/providers/aws/services/firehose/firehose_service_test.py +++ b/tests/providers/aws/services/firehose/firehose_service_test.py @@ -34,13 +34,35 @@ def test__get_session__(self): # Test Firehose List Delivery Streams @mock_aws def test_list_delivery_streams(self): - # Generate S3 client - s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1) - s3_client.create_bucket( - Bucket="test-bucket", - CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1}, + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], ) + arn = delivery_stream["DeliveryStreamARN"] + delivery_stream_name = arn.split("/")[-1] + + # Firehose Client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + + assert len(firehose.delivery_streams) == 1 + assert firehose.delivery_streams[arn].arn == arn + assert firehose.delivery_streams[arn].name == delivery_stream_name + assert firehose.delivery_streams[arn].region == AWS_REGION_EU_WEST_1 + assert firehose.delivery_streams[arn].tags == [{"Key": "key", "Value": "value"}] + @mock_aws + def test_list_tags_for_delivery_stream(self): # Generate Firehose client firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) delivery_stream = firehose_client.create_delivery_stream( @@ -67,3 +89,39 @@ def test_list_delivery_streams(self): assert firehose.delivery_streams[arn].name == delivery_stream_name assert firehose.delivery_streams[arn].region == AWS_REGION_EU_WEST_1 assert firehose.delivery_streams[arn].tags == [{"Key": "key", "Value": "value"}] + + @mock_aws + def test_describe_delivery_stream(self): + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + delivery_stream_name = arn.split("/")[-1] + + # Firehose Client for this test class + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + firehose = Firehose(aws_provider) + + assert len(firehose.delivery_streams) == 1 + assert firehose.delivery_streams[arn].arn == arn + assert firehose.delivery_streams[arn].name == delivery_stream_name + assert firehose.delivery_streams[arn].region == AWS_REGION_EU_WEST_1 + assert firehose.delivery_streams[arn].tags == [{"Key": "key", "Value": "value"}] + assert firehose.delivery_streams[arn].kms_encryption == "ENABLED" + assert ( + firehose.delivery_streams[arn].kms_key_arn + == delivery_stream["DeliveryStreamDescription"]["Destinations"][0][ + "ExtendedS3DestinationDescription" + ]["EncryptionConfiguration"]["KMSEncryptionConfig"]["AWSKMSKeyARN"] + ) diff --git a/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py b/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py new file mode 100644 index 00000000000..d2a1fa40a9a --- /dev/null +++ b/tests/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest_test.py @@ -0,0 +1,195 @@ +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, + set_mocked_aws_provider, +) + + +class Test_firehose_stream_encrypted_at_rest: + @mock_aws + def test_no_streams(self): + from prowler.providers.aws.services.firehose.firehose_service import Firehose + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ), mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( + firehose_stream_encrypted_at_rest, + ) + + check = firehose_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_stream_kms_encryption_enabled(self): + # Generate S3 client + s3_client = client("s3", region_name=AWS_REGION_EU_WEST_1) + s3_client.create_bucket( + Bucket="test-bucket", + CreateBucketConfiguration={"LocationConstraint": AWS_REGION_EU_WEST_1}, + ) + + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + stream_name = arn.split("/")[-1] + + firehose_client.start_delivery_stream_encryption( + DeliveryStreamName=stream_name, + DeliveryStreamEncryptionConfigurationInput={ + "KeyARN": f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id", + "KeyType": "CUSTOMER_MANAGED_CMK", + }, + ) + + from prowler.providers.aws.services.firehose.firehose_service import Firehose + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( + firehose_stream_encrypted_at_rest, + ) + + check = firehose_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Firehose Stream {stream_name} does have at rest encryption enabled." + ) + + @mock_aws + def test_stream_kms_encryption_not_enabled(self): + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + stream_name = arn.split("/")[-1] + + from prowler.providers.aws.services.firehose.firehose_service import Firehose + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( + firehose_stream_encrypted_at_rest, + ) + + check = firehose_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Firehose Stream {stream_name} does not have at rest encryption enabled." + ) + + @mock_aws + def test_stream_kms_encryption_disabled(self): + # Generate Firehose client + firehose_client = client("firehose", region_name=AWS_REGION_EU_WEST_1) + delivery_stream = firehose_client.create_delivery_stream( + DeliveryStreamName="test-delivery-stream", + DeliveryStreamType="DirectPut", + S3DestinationConfiguration={ + "RoleARN": "arn:aws:iam::012345678901:role/firehose-role", + "BucketARN": "arn:aws:s3:::test-bucket", + "Prefix": "", + "BufferingHints": {"IntervalInSeconds": 300, "SizeInMBs": 5}, + "CompressionFormat": "UNCOMPRESSED", + }, + Tags=[{"Key": "key", "Value": "value"}], + ) + arn = delivery_stream["DeliveryStreamARN"] + stream_name = arn.split("/")[-1] + + firehose_client.start_delivery_stream_encryption( + DeliveryStreamName=stream_name, + DeliveryStreamEncryptionConfigurationInput={ + "KeyARN": f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id", + "KeyType": "CUSTOMER_MANAGED_CMK", + }, + ) + + firehose_client.stop_delivery_stream_encryption(DeliveryStreamName=stream_name) + + from prowler.providers.aws.services.firehose.firehose_service import Firehose + + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + with mock.patch( + "prowler.providers.common.provider.Provider.get_global_provider", + return_value=aws_provider, + ): + with mock.patch( + "prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest.firehose_client", + new=Firehose(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.firehose.firehose_stream_encrypted_at_rest.firehose_stream_encrypted_at_rest import ( + firehose_stream_encrypted_at_rest, + ) + + check = firehose_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Firehose Stream {stream_name} does not have at rest encryption enabled." + ) From bfd6373da243efc3b26acb4d459524f81f9cfaa5 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 5 Nov 2024 12:09:40 +0100 Subject: [PATCH 6/7] feat: add docstrings --- .../firehose_stream_encrypted_at_rest.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py index d066a0744a1..fe63558366a 100644 --- a/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py +++ b/prowler/providers/aws/services/firehose/firehose_stream_encrypted_at_rest/firehose_stream_encrypted_at_rest.py @@ -1,10 +1,24 @@ +from typing import List + from prowler.lib.check.models import Check, Check_Report_AWS from prowler.providers.aws.services.firehose.firehose_client import firehose_client from prowler.providers.aws.services.firehose.firehose_service import EncryptionStatus class firehose_stream_encrypted_at_rest(Check): - def execute(self): + """Check if Firehose Streams are encrypted at rest. + + This class verifies that all Firehose Streams have at rest encryption enabled by checking if KMS encryption is active and a KMS Key is configured. + """ + + def execute(self) -> List[Check_Report_AWS]: + """Execute the Firehose Stream Encrypted at Rest check. + + Iterates over all Firehose Streams and checks if KMS encryption is enabled and a KMS Key is configured. + + Returns: + List[Check_Report_AWS]: A list of reports for each Firehose Stream. + """ findings = [] for stream in firehose_client.delivery_streams.values(): report = Check_Report_AWS(self.metadata()) From 79d3e4e871624848e2fe8ee139630500e5863729 Mon Sep 17 00:00:00 2001 From: HugoPBrito Date: Tue, 5 Nov 2024 13:06:19 +0100 Subject: [PATCH 7/7] fix: service tests --- .../firehose/firehose_service_test.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/providers/aws/services/firehose/firehose_service_test.py b/tests/providers/aws/services/firehose/firehose_service_test.py index bef7abebf8b..4761c104fde 100644 --- a/tests/providers/aws/services/firehose/firehose_service_test.py +++ b/tests/providers/aws/services/firehose/firehose_service_test.py @@ -1,8 +1,15 @@ from boto3 import client from moto import mock_aws -from prowler.providers.aws.services.firehose.firehose_service import Firehose -from tests.providers.aws.utils import AWS_REGION_EU_WEST_1, set_mocked_aws_provider +from prowler.providers.aws.services.firehose.firehose_service import ( + EncryptionStatus, + Firehose, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) class Test_Firehose_Service: @@ -109,6 +116,14 @@ def test_describe_delivery_stream(self): arn = delivery_stream["DeliveryStreamARN"] delivery_stream_name = arn.split("/")[-1] + firehose_client.start_delivery_stream_encryption( + DeliveryStreamName=delivery_stream_name, + DeliveryStreamEncryptionConfigurationInput={ + "KeyARN": f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id", + "KeyType": "CUSTOMER_MANAGED_CMK", + }, + ) + # Firehose Client for this test class aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) firehose = Firehose(aws_provider) @@ -118,10 +133,8 @@ def test_describe_delivery_stream(self): assert firehose.delivery_streams[arn].name == delivery_stream_name assert firehose.delivery_streams[arn].region == AWS_REGION_EU_WEST_1 assert firehose.delivery_streams[arn].tags == [{"Key": "key", "Value": "value"}] - assert firehose.delivery_streams[arn].kms_encryption == "ENABLED" + assert firehose.delivery_streams[arn].kms_encryption == EncryptionStatus.ENABLED assert ( firehose.delivery_streams[arn].kms_key_arn - == delivery_stream["DeliveryStreamDescription"]["Destinations"][0][ - "ExtendedS3DestinationDescription" - ]["EncryptionConfiguration"]["KMSEncryptionConfig"]["AWSKMSKeyARN"] + == f"arn:aws:kms:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:key/test-kms-key-id" )