diff --git a/Dockerfile b/Dockerfile index 096f4e29ee0..1daf59df519 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,9 +4,9 @@ LABEL maintainer="https://github.com/prowler-cloud/prowler" # Update system dependencies and install essential tools #hadolint ignore=DL3018 -RUN apk --no-cache upgrade && apk --no-cache add curl git +RUN apk --no-cache upgrade && apk --no-cache add curl git g++ -# Create nonroot user +# Create non-root user RUN mkdir -p /home/prowler && \ echo 'prowler:x:1000:1000:prowler:/home/prowler:' > /etc/passwd && \ echo 'prowler:x:1000:' > /etc/group && \ diff --git a/prowler/providers/aws/services/appsync/__init__.py b/prowler/providers/aws/services/appsync/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/appsync/appsync_client.py b/prowler/providers/aws/services/appsync/appsync_client.py new file mode 100644 index 00000000000..3ab8314a998 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_client.py @@ -0,0 +1,4 @@ +from prowler.providers.aws.services.appsync.appsync_service import AppSync +from prowler.providers.common.provider import Provider + +appsync_client = AppSync(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/appsync/appsync_service.py b/prowler/providers/aws/services/appsync/appsync_service.py new file mode 100644 index 00000000000..dd2985b828b --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_service.py @@ -0,0 +1,59 @@ +from typing import Optional + +from pydantic import BaseModel + +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 AppSync(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.graphql_apis = {} + self.__threading_call__(self._list_graphql_apis) + + def _list_graphql_apis(self, regional_client): + logger.info("AppSync - Describing APIs...") + try: + list_graphql_apis_paginator = regional_client.get_paginator( + "list_graphql_apis" + ) + for page in list_graphql_apis_paginator.paginate(): + for api in page["graphqlApis"]: + api_arn = api["arn"] + if not self.audit_resources or ( + is_resource_filtered( + api_arn, + self.audit_resources, + ) + ): + self.graphql_apis[api_arn] = GraphqlApi( + id=api["apiId"], + name=api["name"], + arn=api_arn, + region=regional_client.region, + type=api.get("apiType", "GRAPHQL"), + field_log_level=api.get("logConfig", {}).get( + "fieldLogLevel", "" + ), + authentication_type=api.get("authenticationType", ""), + tags=[api.get("tags", {})], + ) + + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class GraphqlApi(BaseModel): + id: str + name: str + arn: str + region: str + type: str + field_log_level: str + authentication_type: str + tags: Optional[list] = [] diff --git a/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/__init__.py b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json new file mode 100644 index 00000000000..471b27aee4a --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.metadata.json @@ -0,0 +1,32 @@ +{ + "Provider": "aws", + "CheckID": "dms_endpoint_redis_in_transit_encryption_enabled", + "CheckTitle": "Check if DMS endpoints for Redis OSS are encrypted in transit.", + "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 Redis OSS is configured with a TLS connection. The control fails if the endpoint doesn't have TLS enabled.", + "Risk": "Without TLS, data transmitted between databases may be vulnerable to interception or eavesdropping, increasing the risk of data breaches and other security incidents.", + "RelatedUrl": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Source.Redis.html", + "Remediation": { + "Code": { + "CLI": "aws dms modify-endpoint --endpoint-arn --redis-settings '{'SslSecurityProtocol': 'ssl-encryption'}'", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/dms-controls.html#dms-12", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable TLS for DMS endpoints for Redis OSS to ensure encrypted communication during data migration.", + "Url": "https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Target.Redis.html#CHAP_Target.Redis.EndpointSettings" + } + }, + "Categories": [], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.py b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled.py new file mode 100644 index 00000000000..84ef3a888f1 --- /dev/null +++ b/prowler/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_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_redis_in_transit_encryption_enabled(Check): + """ + Check if AWS DMS Endpoints for Redis OSS have TLS enabled. + + This class verifies whether each AWS DMS Endpoint configured for Redis OSS is encrypted in transit + by checking the `TlsEnabled` property in the endpoint's configuration. The check ensures that + TLS is enabled to secure data in transit, preventing unauthorized access and ensuring data integrity. + """ + + def execute(self) -> List[Check_Report_AWS]: + """ + Execute the DMS Redis TLS enabled check. + + Iterates over all DMS Endpoints and generates a report indicating whether + each Redis OSS endpoint is encrypted in transit. + + 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 == "redis": + 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 Redis OSS is not encrypted in transit." + if endpoint.redis_ssl_protocol == "ssl-encryption": + report.status = "PASS" + report.status_extended = f"DMS Endpoint {endpoint.id} for Redis OSS is encrypted in transit." + + 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 f013a51f3a1..33a8a8f9caf 100644 --- a/prowler/providers/aws/services/dms/dms_service.py +++ b/prowler/providers/aws/services/dms/dms_service.py @@ -75,6 +75,9 @@ def _describe_endpoints(self, regional_client): id=endpoint["EndpointIdentifier"], region=regional_client.region, ssl_mode=endpoint.get("SslMode", False), + redis_ssl_protocol=endpoint.get("RedisSettings", {}).get( + "SslSecurityProtocol", "plaintext" + ), mongodb_auth_type=endpoint.get("MongoDbSettings", {}).get( "AuthType", "no" ), diff --git a/prowler/providers/aws/services/servicecatalog/__init__.py b/prowler/providers/aws/services/servicecatalog/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/prowler/providers/aws/services/servicecatalog/servicecatalog_client.py b/prowler/providers/aws/services/servicecatalog/servicecatalog_client.py new file mode 100644 index 00000000000..bbede226596 --- /dev/null +++ b/prowler/providers/aws/services/servicecatalog/servicecatalog_client.py @@ -0,0 +1,6 @@ +from prowler.providers.aws.services.servicecatalog.servicecatalog_service import ( + ServiceCatalog, +) +from prowler.providers.common.provider import Provider + +servicecatalog_client = ServiceCatalog(Provider.get_global_provider()) diff --git a/prowler/providers/aws/services/servicecatalog/servicecatalog_service.py b/prowler/providers/aws/services/servicecatalog/servicecatalog_service.py new file mode 100644 index 00000000000..31a1f798fed --- /dev/null +++ b/prowler/providers/aws/services/servicecatalog/servicecatalog_service.py @@ -0,0 +1,101 @@ +from typing import Optional + +from pydantic import BaseModel + +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 + +PORTFOLIO_SHARE_TYPES = [ + "ACCOUNT", + "ORGANIZATION", + "ORGANIZATION_UNIT", + "ORGANIZATION_MEMBER_ACCOUNT", +] + + +class ServiceCatalog(AWSService): + def __init__(self, provider): + # Call AWSService's __init__ + super().__init__(__class__.__name__, provider) + self.portfolios = {} + self.__threading_call__(self._list_portfolios) + self.__threading_call__( + self._describe_portfolio_shares, self.portfolios.values() + ) + self.__threading_call__(self._describe_portfolio, self.portfolios.values()) + + def _list_portfolios(self, regional_client): + logger.info("ServiceCatalog - listing portfolios...") + try: + response = regional_client.list_portfolios() + for portfolio in response["PortfolioDetails"]: + portfolio_arn = portfolio["ARN"] + if not self.audit_resources or ( + is_resource_filtered(portfolio_arn, self.audit_resources) + ): + self.portfolios[portfolio_arn] = Portfolio( + arn=portfolio_arn, + id=portfolio["Id"], + name=portfolio["DisplayName"], + region=regional_client.region, + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_portfolio_shares(self, portfolio): + try: + logger.info("ServiceCatalog - describing portfolios shares...") + try: + regional_client = self.regional_clients[portfolio.region] + for portfolio_type in PORTFOLIO_SHARE_TYPES: + for share in regional_client.describe_portfolio_shares( + PortfolioId=portfolio.id, + Type=portfolio_type, + ).get("PortfolioShareDetails", []): + portfolio_share = PortfolioShare( + type=portfolio_type, + accepted=share["Accepted"], + ) + portfolio.shares.append(portfolio_share) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + def _describe_portfolio(self, portfolio): + try: + logger.info("ServiceCatalog - describing portfolios...") + try: + regional_client = self.regional_clients[portfolio.region] + portfolio.tags = regional_client.describe_portfolio( + PortfolioId=portfolio.id, + )["Tags"] + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + except Exception as error: + logger.error( + f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" + ) + + +class PortfolioShare(BaseModel): + type: str + accepted: bool + + +class Portfolio(BaseModel): + id: str + name: str + arn: str + region: str + shares: Optional[list[PortfolioShare]] = [] + tags: Optional[list] = [] diff --git a/tests/providers/aws/services/appsync/appsync_service_test.py b/tests/providers/aws/services/appsync/appsync_service_test.py new file mode 100644 index 00000000000..510a19d766a --- /dev/null +++ b/tests/providers/aws/services/appsync/appsync_service_test.py @@ -0,0 +1,66 @@ +from boto3 import client +from mock import patch +from moto import mock_aws + +from prowler.providers.aws.services.appsync.appsync_service import AppSync +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_US_EAST_1, + set_mocked_aws_provider, +) + + +def mock_generate_regional_clients(provider, service): + regional_client = provider._session.current_session.client( + service, region_name=AWS_REGION_US_EAST_1 + ) + regional_client.region = AWS_REGION_US_EAST_1 + return {AWS_REGION_US_EAST_1: regional_client} + + +@patch( + "prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_AppSync_Service: + # Test AppSync Service + def test_service(self): + aws_provider = set_mocked_aws_provider() + appsync = AppSync(aws_provider) + assert appsync.service == "appsync" + + # Test AppSync Client + def test_client(self): + aws_provider = set_mocked_aws_provider() + appsync = AppSync(aws_provider) + assert appsync.client.__class__.__name__ == "AppSync" + + # Test AppSync Session + def test__get_session__(self): + aws_provider = set_mocked_aws_provider() + appsync = AppSync(aws_provider) + assert appsync.session.__class__.__name__ == "Session" + + # Test AppSync Session + def test_audited_account(self): + aws_provider = set_mocked_aws_provider() + appsync = AppSync(aws_provider) + assert appsync.audited_account == AWS_ACCOUNT_NUMBER + + # Test AppSync Describe File Systems + @mock_aws + def test_list_graphql_apis(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1]) + appsync = client("appsync", region_name=AWS_REGION_US_EAST_1) + api = appsync.create_graphql_api( + name="test-api", + authenticationType="API_KEY", + logConfig={"fieldLogLevel": "ALL", "cloudWatchLogsRoleArn": "test"}, + ) + api_arn = api["graphqlApi"]["arn"] + appsync_client = AppSync(aws_provider) + + assert appsync_client.graphql_apis[api_arn].name == "test-api" + assert appsync_client.graphql_apis[api_arn].field_log_level == "ALL" + assert appsync_client.graphql_apis[api_arn].authentication_type == "API_KEY" + assert appsync_client.graphql_apis[api_arn].tags == [{}] diff --git a/tests/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled_test.py b/tests/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_enabled_test.py new file mode 100644 index 00000000000..639b5c5bcbb --- /dev/null +++ b/tests/providers/aws/services/dms/dms_endpoint_redis_in_transit_encryption_enabled/dms_endpoint_redis_in_transit_encryption_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_redis(self, operation_name, kwarg): + if operation_name == "DescribeEndpoints": + return { + "Endpoints": [ + { + "EndpointIdentifier": DMS_ENDPOINT_NAME, + "EndpointArn": DMS_ENDPOINT_ARN, + "SslMode": "require", + "RedisSettings": { + "SslSecurityProtocol": "ssl-encryption", + }, + "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", + "RedisSettings": { + "SslSecurityProtocol": "ssl-encryption", + }, + "EngineName": "redis", + } + ] + } + 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", + "RedisSettings": { + "SslSecurityProtocol": "plaintext", + }, + "EngineName": "redis", + } + ] + } + 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_redis_in_transit_encryption_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_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled import ( + dms_endpoint_redis_in_transit_encryption_enabled, + ) + + check = dms_endpoint_redis_in_transit_encryption_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_redis, + ): + + 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_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled import ( + dms_endpoint_redis_in_transit_encryption_enabled, + ) + + check = dms_endpoint_redis_in_transit_encryption_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_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled import ( + dms_endpoint_redis_in_transit_encryption_enabled, + ) + + check = dms_endpoint_redis_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "FAIL" + assert result[0].status_extended == ( + "DMS Endpoint dms-endpoint for Redis OSS is not encrypted in transit." + ) + 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_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled.dms_client", + new=DMS(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.dms.dms_endpoint_redis_in_transit_encryption_enabled.dms_endpoint_redis_in_transit_encryption_enabled import ( + dms_endpoint_redis_in_transit_encryption_enabled, + ) + + check = dms_endpoint_redis_in_transit_encryption_enabled() + result = check.execute() + + assert len(result) == 1 + assert result[0].status == "PASS" + assert result[0].status_extended == ( + "DMS Endpoint dms-endpoint for Redis OSS is encrypted in transit." + ) + 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 c320b02131c..7dc11c9a25b 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 @@ -33,6 +33,7 @@ def test_dms_endpoint_ssl_none(self): id="test-endpoint-no-ssl", mongodb_auth_type="no", engine_name="test-engine", + redis_ssl_protocol="plaintext", region=AWS_REGION_US_EAST_1, ssl_mode="none", tags=[{"Key": "Name", "Value": "test-endpoint-no-ssl"}], @@ -81,6 +82,7 @@ def test_dms_endpoint_ssl_require(self): id="test-endpoint-ssl-require", mongodb_auth_type="no", engine_name="test-engine", + redis_ssl_protocol="plaintext", region=AWS_REGION_US_EAST_1, ssl_mode="require", tags=[{"Key": "Name", "Value": "test-endpoint-ssl-require"}], @@ -126,6 +128,7 @@ def test_dms_endpoint_ssl_verify_ca(self): id="test-endpoint-ssl-verify-ca", engine_name="test-engine", mongodb_auth_type="no", + redis_ssl_protocol="plaintext", region=AWS_REGION_US_EAST_1, ssl_mode="verify-ca", tags=[{"Key": "Name", "Value": "test-endpoint-ssl-verify-ca"}], @@ -171,6 +174,7 @@ def test_dms_endpoint_ssl_verify_full(self): id="test-endpoint-ssl-verify-full", mongodb_auth_type="no", engine_name="test-engine", + redis_ssl_protocol="plaintext", region=AWS_REGION_US_EAST_1, ssl_mode="verify-full", tags=[{"Key": "Name", "Value": "test-endpoint-ssl-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 18e40b19c1f..4918c890ce4 100644 --- a/tests/providers/aws/services/dms/dms_service_test.py +++ b/tests/providers/aws/services/dms/dms_service_test.py @@ -47,6 +47,9 @@ def mock_make_api_call(self, operation_name, kwargs): "EndpointIdentifier": DMS_ENDPOINT_NAME, "EndpointArn": DMS_ENDPOINT_ARN, "SslMode": "require", + "RedisSettings": { + "SslSecurityProtocol": "ssl-encryption", + }, "MongoDbSettings": { "AuthType": "password", }, @@ -131,6 +134,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].redis_ssl_protocol == "ssl-encryption" 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" diff --git a/tests/providers/aws/services/servicecatalog/servicecatalog_service_test.py b/tests/providers/aws/services/servicecatalog/servicecatalog_service_test.py new file mode 100644 index 00000000000..3279d204db7 --- /dev/null +++ b/tests/providers/aws/services/servicecatalog/servicecatalog_service_test.py @@ -0,0 +1,108 @@ +from unittest.mock import patch + +import botocore +from moto import mock_aws + +from prowler.providers.aws.services.servicecatalog.servicecatalog_service import ( + ServiceCatalog, +) +from tests.providers.aws.utils import ( + AWS_ACCOUNT_NUMBER, + AWS_REGION_EU_WEST_1, + set_mocked_aws_provider, +) + +make_api_call = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListPortfolios": + return { + "PortfolioDetails": [ + { + "Id": "portfolio-id-test", + "ARN": "arn:aws:servicecatalog:eu-west-1:123456789012:portfolio/portfolio-id-test", + "DisplayName": "portfolio-name", + } + ], + } + elif operation_name == "DescribePortfolioShares": + return { + "PortfolioShareDetails": [ + { + "Type": "ACCOUNT", + "Accepted": True, + } + ], + } + elif operation_name == "DescribePortfolio": + return { + "Tags": {"tag1": "value1", "tag2": "value2"}, + } + return make_api_call(self, operation_name, kwarg) + + +def mock_generate_regional_clients(provider, service): + regional_client = provider._session.current_session.client( + service, region_name=AWS_REGION_EU_WEST_1 + ) + regional_client.region = AWS_REGION_EU_WEST_1 + return {AWS_REGION_EU_WEST_1: regional_client} + + +@patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) +@patch( + "prowler.providers.aws.aws_provider.AwsProvider.generate_regional_clients", + new=mock_generate_regional_clients, +) +class Test_ServiceCatalog_Service: + # Test ServiceCatalog Service + def test_service(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + service_catalog = ServiceCatalog(aws_provider) + assert service_catalog.service == "servicecatalog" + + # Test ServiceCatalog client + def test_client(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + service_catalog = ServiceCatalog(aws_provider) + for reg_client in service_catalog.regional_clients.values(): + assert reg_client.__class__.__name__ == "ServiceCatalog" + + # Test ServiceCatalog session + def test__get_session__(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + ses = ServiceCatalog(aws_provider) + assert ses.session.__class__.__name__ == "Session" + + @mock_aws + # Test ServiceCatalog list portfolios + def test_list_portfolios(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + service_catalog = ServiceCatalog(aws_provider) + arn = f"arn:aws:servicecatalog:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:portfolio/portfolio-id-test" + assert service_catalog.portfolios[arn].name == "portfolio-name" + assert service_catalog.portfolios[arn].id == "portfolio-id-test" + assert service_catalog.portfolios[arn].arn == arn + assert service_catalog.portfolios[arn].region == AWS_REGION_EU_WEST_1 + + @mock_aws + # Test ServiceCatalog describe portfolio shares + def test_describe_portfolio_shares(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + service_catalog = ServiceCatalog(aws_provider) + arn = f"arn:aws:servicecatalog:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:portfolio/portfolio-id-test" + assert len(service_catalog.portfolios[arn].shares) == 4 + assert service_catalog.portfolios[arn].shares[0].accepted + assert service_catalog.portfolios[arn].shares[0].type == "ACCOUNT" + + @mock_aws + # Test ServiceCatalog describe portfolio + def test_describe_portfolio(self): + aws_provider = set_mocked_aws_provider([AWS_REGION_EU_WEST_1]) + service_catalog = ServiceCatalog(aws_provider) + arn = f"arn:aws:servicecatalog:{AWS_REGION_EU_WEST_1}:{AWS_ACCOUNT_NUMBER}:portfolio/portfolio-id-test" + assert service_catalog.portfolios[arn].tags == { + "tag1": "value1", + "tag2": "value2", + }