diff --git a/prowler/providers/aws/services/kinesis/kinesis_client.py b/prowler/providers/aws/services/kinesis/kinesis_client.py new file mode 100644 index 0000000000..294f8d5e90 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis +from prowler.providers.common.provider import Provider + +kinesis_client = Kinesis(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/kinesis/kinesis_service.py b/prowler/providers/aws/services/kinesis/kinesis_service.py index 3c49bc5370..530710b965 100644 --- a/prowler/providers/aws/services/kinesis/kinesis_service.py +++ b/prowler/providers/aws/services/kinesis/kinesis_service.py @@ -14,6 +14,7 @@ class Kinesis(AWSService): super().__init__(__class__.__name__, provider) self.streams = {} self.__threading_call__(self._list_streams) + self.__threading_call__(self._describe_stream, self.streams.values()) self.__threading_call__(self._list_tags_for_stream, self.streams.values()) def _list_streams(self, regional_client): @@ -37,6 +38,22 @@ class Kinesis(AWSService): f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" ) + def _describe_stream(self, stream): + logger.info(f"Kinesis - Describing Stream {stream.name}...") + try: + stream_description = ( + self.regional_clients[stream.region] + .describe_stream(StreamName=stream.name) + .get("StreamDescription", {}) + ) + stream.encrypted_at_rest = EncryptionType( + stream_description.get("EncryptionType", "NONE") + ) + except Exception as error: + logger.error( + f"{stream.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + def _list_tags_for_stream(self, stream): logger.info(f"Kinesis - Listing tags for Stream {stream.name}...") try: @@ -51,6 +68,13 @@ class Kinesis(AWSService): ) +class EncryptionType(Enum): + """Enum for Kinesis Stream Encryption Type""" + + NONE = "NONE" + KMS = "KMS" + + class StreamStatus(Enum): """Enum for Kinesis Stream Status""" @@ -68,3 +92,4 @@ class Stream(BaseModel): name: str status: StreamStatus tags: Optional[list] + encrypted_at_rest: EncryptionType = EncryptionType.NONE diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/__init__.py b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json new file mode 100644 index 0000000000..4741297398 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "kinesis_stream_encrypted_at_rest", + "CheckTitle": "Kinesis streams should be encrypted at rest.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "kinesis", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:kinesis::account-id:stream/stream-name", + "Severity": "medium", + "ResourceType": "AwsKinesisStream", + "Description": "Ensure Kinesis streams use server-side encryption with AWS KMS keys for data protection.", + "Risk": "If Kinesis streams are not encrypted at rest, sensitive data stored in the stream could be exposed to unauthorized access or breaches. This could lead to potential data theft or misuse of unencrypted data.", + "RelatedUrl": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html", + "Remediation": { + "Code": { + "CLI": "aws kinesis start-stream-encryption --stream-name --encryption-type KMS --key-id ", + "NativeIaC": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_22/#cloudformation", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/kinesis-controls.html#kinesis-1", + "Terraform": "https://docs.prowler.com/checks/aws/general-policies/bc_aws_general_22/#terraform" + }, + "Recommendation": { + "Text": "Enable server-side encryption for Kinesis streams using AWS KMS keys to ensure that all data is encrypted before it is stored, protecting data at rest and reducing the risk of unauthorized access.", + "Url": "https://docs.aws.amazon.com/streams/latest/dev/getting-started-with-sse.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py new file mode 100644 index 0000000000..af46a212c4 --- /dev/null +++ b/prowler/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest.py @@ -0,0 +1,28 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.kinesis.kinesis_client import kinesis_client +from prowler.providers.aws.services.kinesis.kinesis_service import EncryptionType + + +class kinesis_stream_encrypted_at_rest(Check): + def execute(self): + findings = [] + for stream in kinesis_client.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 = "FAIL" + report.status_extended = ( + f"Kinesis Stream {stream.name} is not encrypted at rest." + ) + + if stream.encrypted_at_rest == EncryptionType.KMS: + report.status = "PASS" + report.status_extended = ( + f"Kinesis Stream {stream.name} is encrypted at rest." + ) + + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py b/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py new file mode 100644 index 0000000000..f277b6ad83 --- /dev/null +++ b/tests/providers/aws/services/kinesis/kinesis_stream_encrypted_at_rest/kinesis_stream_encrypted_at_rest_test.py @@ -0,0 +1,123 @@ +from unittest import mock + +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import AWS_REGION_US_EAST_1, set_mocked_aws_provider + + +class Test_kinesis_encrypted_at_rest: + @mock_aws + def test_no_streams(self): + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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, + ), mock.patch( + "prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_encrypted_stream(self): + kinesis_client = client("kinesis", region_name=AWS_REGION_US_EAST_1) + stream_name = "stream_test_us" + kinesis_client.create_stream( + StreamName=stream_name, + ShardCount=1, + StreamModeDetails={"StreamMode": "PROVISIONED"}, + ) + + kinesis_client.start_stream_encryption( + StreamName=stream_name, + EncryptionType="KMS", + KeyId="arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", + ) + + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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, + ), mock.patch( + "prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Kinesis Stream {stream_name} is encrypted at rest." + ) + assert result[0].resource_id == stream_name + assert ( + result[0].resource_arn + == f"arn:aws:kinesis:{AWS_REGION_US_EAST_1}:123456789012:stream/{stream_name}" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1 + + @mock_aws + def test_non_encrypted_stream(self): + kinesis_client = client("kinesis", region_name=AWS_REGION_US_EAST_1) + stream_name = "stream_test_us" + kinesis_client.create_stream( + StreamName=stream_name, + ShardCount=1, + StreamModeDetails={"StreamMode": "PROVISIONED"}, + ) + + from prowler.providers.aws.services.kinesis.kinesis_service import Kinesis + + 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, + ), mock.patch( + "prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest.kinesis_client", + new=Kinesis(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.kinesis.kinesis_stream_encrypted_at_rest.kinesis_stream_encrypted_at_rest import ( + kinesis_stream_encrypted_at_rest, + ) + + check = kinesis_stream_encrypted_at_rest() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Kinesis Stream {stream_name} is not encrypted at rest." + ) + assert result[0].resource_id == stream_name + assert ( + result[0].resource_arn + == f"arn:aws:kinesis:{AWS_REGION_US_EAST_1}:123456789012:stream/{stream_name}" + ) + assert result[0].resource_tags == [] + assert result[0].region == AWS_REGION_US_EAST_1