From 5fbf8ddfe9b547511a16daf3dc6aa67e274cdda8 Mon Sep 17 00:00:00 2001 From: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:30:18 +0100 Subject: [PATCH] feat(transfer): add new check `transfer_server_encryption_in_transit` (#5590) Co-authored-by: Sergio --- .../__init__.py | 0 ...n_transit_encryption_enabled.metadata.json | 32 ++++ ...er_server_in_transit_encryption_enabled.py | 40 +++++ .../aws/services/transfer/transfer_service.py | 3 +- ...rver_in_transit_encryption_enabled_test.py | 156 ++++++++++++++++++ .../transfer/transfer_service_test.py | 7 +- 6 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/__init__.py create mode 100644 prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.metadata.json create mode 100644 prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.py create mode 100644 tests/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled_test.py diff --git a/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/__init__.py b/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.metadata.json b/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.metadata.json new file mode 100644 index 0000000000..a760ddfe25 --- /dev/null +++ b/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "transfer_server_in_transit_encryption_enabled", + "CheckTitle": "Transfer Family Servers should have encryption in transit enabled.", + "CheckType": [ + "Software and Configuration Checks/Industry and Regulatory Standards/NIST 800-53 Controls" + ], + "ServiceName": "transfer", + "SubServiceName": "", + "ResourceIdTemplate": "arn:partition:transfer:region:account-id:server/server-id", + "Severity": "medium", + "ResourceType": "AwsTransferServer", + "Description": "Ensure that your Transfer Family servers have encryption in transit enabled.", + "Risk": "Using FTP for endpoint connections leaves data in transit unencrypted, making it susceptible to interception by attackers. FTP lacks encryption, which exposes your data to person-in-the-middle and other interception risks. Adopting encrypted protocols such as SFTP, FTPS, or AS2 provides a layer of protection that helps secure sensitive data during transfer.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/transfer-family-server-no-ftp.html", + "Remediation": { + "Code": { + "CLI": "aws transfer update-server --server-id --protocols SFTP FTPS AS2", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/transfer-controls.html#transfer-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Configure AWS Transfer Family servers to use secure protocols, such as SFTP, FTPS, or AS2, instead of FTP to protect data in transit. These protocols offer encryption, reducing exposure to interception and manipulation attacks.", + "Url": "https://docs.aws.amazon.com/transfer/latest/userguide/edit-server-config.html#edit-protocols" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.py b/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.py new file mode 100644 index 0000000000..88c6396e1c --- /dev/null +++ b/prowler/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled.py @@ -0,0 +1,40 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.transfer.transfer_client import transfer_client +from prowler.providers.aws.services.transfer.transfer_service import Protocol + + +class transfer_server_in_transit_encryption_enabled(Check): + """Check if Transfer Servers have encryption in transit enabled. + + This class checks if Transfer Servers have encryption in transit enabled. + """ + + def execute(self) -> List[Check_Report_AWS]: + """Execute the server in transit encyption check. + + Iterate over all Transfer Servers and check if they have FTP as one of the valid protocols. + + Returns: + List[Check_Report_AWS]: A list of reports for each Transfer Server. + """ + findings = [] + for server in transfer_client.servers.values(): + report = Check_Report_AWS(self.metadata()) + report.region = server.region + report.resource_id = server.id + report.resource_arn = server.arn + report.resource_tags = server.tags + report.status = "PASS" + report.status_extended = ( + f"Transfer Server {server.id} does have encryption in transit enabled." + ) + + if Protocol.FTP in server.protocols: + report.status = "FAIL" + report.status_extended = f"Transfer Server {server.id} does not have encryption in transit enabled." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/transfer/transfer_service.py b/prowler/providers/aws/services/transfer/transfer_service.py index 22bff6202a..e1d037fdae 100644 --- a/prowler/providers/aws/services/transfer/transfer_service.py +++ b/prowler/providers/aws/services/transfer/transfer_service.py @@ -46,8 +46,7 @@ class Transfer(AWSService): ) for protocol in server_description.get("Protocols", []): server.protocols.append(Protocol(protocol)) - for tag in server_description.get("Tags", []): - server.tags.append(tag) + server.tags = server_description.get("Tags", []) except Exception as error: logger.error( f"{server.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" diff --git a/tests/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled_test.py b/tests/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled_test.py new file mode 100644 index 0000000000..04a62e47bd --- /dev/null +++ b/tests/providers/aws/services/transfer/transfer_server_in_transit_encryption_enabled/transfer_server_in_transit_encryption_enabled_test.py @@ -0,0 +1,156 @@ +from unittest import mock +from unittest.mock import patch + +import botocore +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +SERVER_ID = "s-01234567890abcdef" +SERVER_ARN = ( + f"arn:aws:transfer:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:server/{SERVER_ID}" +) + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call_encrypted(self, operation_name, kwarg): + if operation_name == "ListServers": + return { + "Servers": [ + { + "Arn": f"arn:aws:transfer:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:server/{SERVER_ID}", + "ServerId": SERVER_ID, + } + ] + } + if operation_name == "DescribeServer": + return { + "Server": { + "Arn": SERVER_ARN, + "ServerId": SERVER_ID, + "Protocols": ["SFTP"], + } + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_unencrypted(self, operation_name, kwarg): + if operation_name == "ListServers": + return { + "Servers": [ + { + "Arn": f"arn:aws:transfer:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:server/{SERVER_ID}", + "ServerId": SERVER_ID, + } + ] + } + if operation_name == "DescribeServer": + return { + "Server": { + "Arn": SERVER_ARN, + "ServerId": SERVER_ID, + "Protocols": ["FTP", "FTPS", "SFTP", "AS2"], + } + } + return make_api_call(self, operation_name, kwarg) + + +class Test_transfer_server_encryption_in_transit: + @mock_aws + def test_no_servers(self): + from prowler.providers.aws.services.transfer.transfer_service import Transfer + + 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.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled.transfer_client", + new=Transfer(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled import ( + transfer_server_in_transit_encryption_enabled, + ) + + check = transfer_server_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 0 + + @patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_encrypted + ) + @mock_aws + def test_transfer_server_encryption_enabled(self): + from prowler.providers.aws.services.transfer.transfer_service import Transfer + + 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.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled.transfer_client", + new=Transfer(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled import ( + transfer_server_in_transit_encryption_enabled, + ) + + check = transfer_server_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == f"Transfer Server {SERVER_ID} does have encryption in transit enabled." + ) + assert result[0].resource_id == SERVER_ID + assert result[0].resource_arn == SERVER_ARN + assert result[0].region == AWS_REGION_US_EAST_1 + + @patch( + "botocore.client.BaseClient._make_api_call", new=mock_make_api_call_unencrypted + ) + @mock_aws + def test_transfer_server_encryption_disabled(self): + from prowler.providers.aws.services.transfer.transfer_service import Transfer + + 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.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled.transfer_client", + new=Transfer(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.transfer.transfer_server_in_transit_encryption_enabled.transfer_server_in_transit_encryption_enabled import ( + transfer_server_in_transit_encryption_enabled, + ) + + check = transfer_server_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == f"Transfer Server {SERVER_ID} does not have encryption in transit enabled." + ) + assert result[0].resource_id == SERVER_ID + assert result[0].resource_arn == SERVER_ARN + assert result[0].region == AWS_REGION_US_EAST_1 diff --git a/tests/providers/aws/services/transfer/transfer_service_test.py b/tests/providers/aws/services/transfer/transfer_service_test.py index 4c5b8db30b..61a963ab09 100644 --- a/tests/providers/aws/services/transfer/transfer_service_test.py +++ b/tests/providers/aws/services/transfer/transfer_service_test.py @@ -16,6 +16,9 @@ SERVER_ID = "SERVICE_MANAGED::s-01234567890abcdef" SERVER_ARN = f"arn:aws:transfer:us-east-1:{AWS_ACCOUNT_NUMBER}:server/{SERVER_ID}" +make_api_call = botocore.client.BaseClient._make_api_call + + def mock_make_api_call(self, operation_name, kwarg): if operation_name == "ListServers": return { @@ -32,7 +35,7 @@ def mock_make_api_call(self, operation_name, kwarg): "Arn": SERVER_ARN, "ServerId": SERVER_ID, "Protocols": ["SFTP"], - "Tags": [{"key": "value"}], + "Tags": [{"Key": "key", "Value": "value"}], } } return make_api_call(self, operation_name, kwarg) @@ -78,5 +81,5 @@ class Test_transfer_service: assert transfer.servers[SERVER_ARN].id == SERVER_ID assert len(transfer.servers[SERVER_ARN].protocols) == 1 assert transfer.servers[SERVER_ARN].region == "us-east-1" - assert transfer.servers[SERVER_ARN].tags == [{"key": "value"}] + assert transfer.servers[SERVER_ARN].tags == [{"Key": "key", "Value": "value"}] assert transfer.servers[SERVER_ARN].protocols[0] == Protocol.SFTP