From 9802fc141a7844016e052ce3c7e85c50d28db1d3 Mon Sep 17 00:00:00 2001 From: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:58:55 +0100 Subject: [PATCH] feat(dms): add new check `dms_endpoint_mongodb_authentication_enabled` (#5578) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../__init__.py | 0 ...ngodb_authentication_enabled.metadata.json | 32 +++ ...endpoint_mongodb_authentication_enabled.py | 42 +++ .../providers/aws/services/dms/dms_service.py | 4 + ...int_mongodb_authentication_enabled_test.py | 271 ++++++++++++++++++ .../dms_endpoint_ssl_enabled_test.py | 4 + .../aws/services/dms/dms_service_test.py | 4 + 7 files changed, 357 insertions(+) create mode 100644 prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/__init__.py create mode 100644 prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json create mode 100644 prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.py create mode 100644 tests/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled_test.py diff --git a/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/__init__.py b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json new file mode 100644 index 00000000000..8a0b8002958 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "dms_endpoint_mongodb_authentication_enabled", + "CheckTitle": "Check if DMS endpoints for MongoDB have an authentication mechanism enabled.", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "dms", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:dms:region:account-id:endpoint/endpoint-id", + "Severity": "medium", + "ResourceType": "AwsDmsEndpoint", + "Description": "This control checks whether an AWS DMS endpoint for MongoDB is configured with an authentication mechanism. The control fails if an authentication type isn't set for the endpoint.", + "Risk": "Without an authentication mechanism enabled, unauthorized users may gain access to sensitive data during migration, increasing the risk of data breaches and security incidents.", + "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MongoDB.html", + "Remediation": { + "Code": { + "CLI": "aws dms modify-endpoint --endpoint-arn --username --password --authentication-type ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-11", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable an authentication mechanism on DMS endpoints for MongoDB to ensure secure access control during migration.", + "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.MongoDB.html" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.py b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.py new file mode 100644 index 00000000000..b19d8f1eeb7 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled.py @@ -0,0 +1,42 @@ +from typing import List + +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.dms.dms_client import dms_client + + +class dms_endpoint_mongodb_authentication_enabled(Check): + """ + Check if AWS DMS Endpoints for MongoDB have an authentication mechanism enabled. + + This class verifies whether each AWS DMS Endpoint configured for MongoDB has an authentication + mechanism enabled by checking the `AuthType` property in the endpoint's configuration. The check + ensures that the `AuthType` is not set to "no", indicating that an authentication method is in place. + """ + + def execute(self) -> List[Check_Report_AWS]: + """ + Execute the DMS MongoDB authentication type configured check. + + Iterates over all DMS Endpoints and generates a report indicating whether + each MongoDB endpoint has an authentication mechanism enabled. + + Returns: + List[Check_Report_AWS]: A list of report objects with the results of the check. + """ + findings = [] + for endpoint_arn, endpoint in dms_client.endpoints.items(): + if endpoint.engine_name == "mongodb": + report = Check_Report_AWS(self.metadata()) + report.resource_id = endpoint.id + report.resource_arn = endpoint_arn + report.region = endpoint.region + report.resource_tags = endpoint.tags + report.status = "FAIL" + report.status_extended = f"DMS Endpoint '{endpoint.id}' for MongoDB does not have an authentication mechanism enabled." + if endpoint.mongodb_auth_type != "no": + report.status = "PASS" + report.status_extended = f"DMS Endpoint '{endpoint.id}' for MongoDB has {endpoint.mongodb_auth_type} as the authentication mechanism." + + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/dms/dms_service.py b/prowler/providers/aws/services/dms/dms_service.py index 27ec105ea39..12e40a2ac90 100644 --- a/prowler/providers/aws/services/dms/dms_service.py +++ b/prowler/providers/aws/services/dms/dms_service.py @@ -71,6 +71,9 @@ def _describe_endpoints(self, regional_client): id=endpoint["EndpointIdentifier"], region=regional_client.region, ssl_mode=endpoint.get("SslMode", False), + mongodb_auth_type=endpoint.get("MongoDbSettings", {}).get( + "AuthType", "no" + ), neptune_iam_auth_enabled=endpoint.get( "NeptuneSettings", {} ).get("IamAuthEnabled", False), @@ -98,6 +101,7 @@ class Endpoint(BaseModel): region: str ssl_mode: str tags: Optional[list] + mongodb_auth_type: str neptune_iam_auth_enabled: bool = False engine_name: str diff --git a/tests/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled_test.py b/tests/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled_test.py new file mode 100644 index 00000000000..f7a2c8dea62 --- /dev/null +++ b/tests/providers/aws/services/dms/dms_endpoint_mongodb_authentication_enabled/dms_endpoint_mongodb_authentication_enabled_test.py @@ -0,0 +1,271 @@ +from unittest import mock + +import botocore +from boto3 import client +from moto import mock_aws + +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + +make_api_call = botocore.client.BaseClient._make_api_call + +DMS_ENDPOINT_NAME = "dms-endpoint" +DMS_ENDPOINT_ARN = f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:endpoint:{DMS_ENDPOINT_NAME}" +DMS_INSTANCE_NAME = "rep-instance" +DMS_INSTANCE_ARN = ( + f"arn:aws:dms:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:rep:{DMS_INSTANCE_NAME}" +) + + +def mock_make_api_call_enabled_not_mongodb(self, operation_name, kwarg): + if operation_name == "DescribeEndpoints": + return { + "Endpoints": [ + { + "EndpointIdentifier": DMS_ENDPOINT_NAME, + "EndpointArn": DMS_ENDPOINT_ARN, + "SslMode": "require", + "MongoDbSettings": { + "AuthType": "password", + }, + "EngineName": "oracle", + } + ] + } + elif operation_name == "ListTagsForResource": + if kwarg["ResourceArn"] == DMS_INSTANCE_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "rep-instance"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + elif kwarg["ResourceArn"] == DMS_ENDPOINT_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "dms-endpoint"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_enabled(self, operation_name, kwarg): + if operation_name == "DescribeEndpoints": + return { + "Endpoints": [ + { + "EndpointIdentifier": DMS_ENDPOINT_NAME, + "EndpointArn": DMS_ENDPOINT_ARN, + "SslMode": "require", + "MongoDbSettings": { + "AuthType": "password", + }, + "EngineName": "mongodb", + } + ] + } + elif operation_name == "ListTagsForResource": + if kwarg["ResourceArn"] == DMS_INSTANCE_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "rep-instance"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + elif kwarg["ResourceArn"] == DMS_ENDPOINT_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "dms-endpoint"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + return make_api_call(self, operation_name, kwarg) + + +def mock_make_api_call_not_enabled(self, operation_name, kwarg): + if operation_name == "DescribeEndpoints": + return { + "Endpoints": [ + { + "EndpointIdentifier": DMS_ENDPOINT_NAME, + "EndpointArn": DMS_ENDPOINT_ARN, + "SslMode": "require", + "MongoDbSettings": { + "AuthType": "no", + }, + "EngineName": "mongodb", + } + ] + } + elif operation_name == "ListTagsForResource": + if kwarg["ResourceArn"] == DMS_INSTANCE_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "rep-instance"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + elif kwarg["ResourceArn"] == DMS_ENDPOINT_ARN: + return { + "TagList": [ + {"Key": "Name", "Value": "dms-endpoint"}, + {"Key": "Owner", "Value": "admin"}, + ] + } + return make_api_call(self, operation_name, kwarg) + + +class Test_dms_endpoint_mongodb_authentication_enabled: + @mock_aws + def test_no_dms_endpoints(self): + dms_client = client("dms", region_name=AWS_REGION_US_EAST_1) + dms_client.endpoints = {} + + from prowler.providers.aws.services.dms.dms_service import DMS + + 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.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled import ( + dms_endpoint_mongodb_authentication_enabled, + ) + + check = dms_endpoint_mongodb_authentication_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_dms_not_mongodb_auth_mecanism_enabled(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_enabled_not_mongodb, + ): + + from prowler.providers.aws.services.dms.dms_service import DMS + + 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.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled import ( + dms_endpoint_mongodb_authentication_enabled, + ) + + check = dms_endpoint_mongodb_authentication_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + def test_dms_mongodb_auth_mecanism_not_enabled(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_not_enabled, + ): + + from prowler.providers.aws.services.dms.dms_service import DMS + + 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.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled import ( + dms_endpoint_mongodb_authentication_enabled, + ) + + check = dms_endpoint_mongodb_authentication_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "DMS Endpoint 'dms-endpoint' for MongoDB does not have an authentication mechanism enabled." + ) + assert result[0].resource_id == "dms-endpoint" + assert ( + result[0].resource_arn + == "arn:aws:dms:us-east-1:123456789012:endpoint:dms-endpoint" + ) + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": "dms-endpoint", + }, + { + "Key": "Owner", + "Value": "admin", + }, + ] + assert result[0].region == "us-east-1" + + @mock_aws + def test_dms_mongodb_auth_mecanism_enabled(self): + with mock.patch( + "botocore.client.BaseClient._make_api_call", + new=mock_make_api_call_enabled, + ): + + from prowler.providers.aws.services.dms.dms_service import DMS + + 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.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_mongodb_authentication_enabled.dms_endpoint_mongodb_authentication_enabled import ( + dms_endpoint_mongodb_authentication_enabled, + ) + + check = dms_endpoint_mongodb_authentication_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "DMS Endpoint 'dms-endpoint' for MongoDB has password as the authentication mechanism." + ) + assert result[0].resource_id == "dms-endpoint" + assert ( + result[0].resource_arn + == "arn:aws:dms:us-east-1:123456789012:endpoint:dms-endpoint" + ) + assert result[0].resource_tags == [ + { + "Key": "Name", + "Value": "dms-endpoint", + }, + { + "Key": "Owner", + "Value": "admin", + }, + ] + assert result[0].region == "us-east-1" diff --git a/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py b/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py index f9cd64921cc..c320b02131c 100644 --- a/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py +++ b/tests/providers/aws/services/dms/dms_endpoint_ssl_enabled_test/dms_endpoint_ssl_enabled_test.py @@ -31,6 +31,7 @@ def test_dms_endpoint_ssl_none(self): endpoint_arn: Endpoint( arn=endpoint_arn, id="test-endpoint-no-ssl", + mongodb_auth_type="no", engine_name="test-engine", region=AWS_REGION_US_EAST_1, ssl_mode="none", @@ -78,6 +79,7 @@ def test_dms_endpoint_ssl_require(self): endpoint_arn: Endpoint( arn=endpoint_arn, id="test-endpoint-ssl-require", + mongodb_auth_type="no", engine_name="test-engine", region=AWS_REGION_US_EAST_1, ssl_mode="require", @@ -123,6 +125,7 @@ def test_dms_endpoint_ssl_verify_ca(self): arn=endpoint_arn, id="test-endpoint-ssl-verify-ca", engine_name="test-engine", + mongodb_auth_type="no", region=AWS_REGION_US_EAST_1, ssl_mode="verify-ca", tags=[{"Key": "Name", "Value": "test-endpoint-ssl-verify-ca"}], @@ -166,6 +169,7 @@ def test_dms_endpoint_ssl_verify_full(self): endpoint_arn: Endpoint( arn=endpoint_arn, id="test-endpoint-ssl-verify-full", + mongodb_auth_type="no", engine_name="test-engine", region=AWS_REGION_US_EAST_1, ssl_mode="verify-full", diff --git a/tests/providers/aws/services/dms/dms_service_test.py b/tests/providers/aws/services/dms/dms_service_test.py index e89311406c0..495eb6eb2d6 100644 --- a/tests/providers/aws/services/dms/dms_service_test.py +++ b/tests/providers/aws/services/dms/dms_service_test.py @@ -44,6 +44,9 @@ def mock_make_api_call(self, operation_name, kwargs): "EndpointIdentifier": DMS_ENDPOINT_NAME, "EndpointArn": DMS_ENDPOINT_ARN, "SslMode": "require", + "MongoDbSettings": { + "AuthType": "password", + }, "NeptuneSettings": { "IamAuthEnabled": True, }, @@ -125,6 +128,7 @@ def test_describe_endpoints(self): assert len(dms.endpoints) == 1 assert dms.endpoints[DMS_ENDPOINT_ARN].id == DMS_ENDPOINT_NAME assert dms.endpoints[DMS_ENDPOINT_ARN].ssl_mode == "require" + assert dms.endpoints[DMS_ENDPOINT_ARN].mongodb_auth_type == "password" assert dms.endpoints[DMS_ENDPOINT_ARN].neptune_iam_auth_enabled assert dms.endpoints[DMS_ENDPOINT_ARN].engine_name == "neptune"