feat(kinesis): add new check kinesis_stream_encrypted_at_rest (#5292)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Hugo Pereira Brito
2024-10-04 19:59:49 +02:00
committed by GitHub
parent 2b66368cf2
commit 9138ecdce9
6 changed files with 212 additions and 0 deletions
@@ -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())
@@ -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
@@ -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 <your-stream-name> --encryption-type KMS --key-id <your-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": ""
}
@@ -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
@@ -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