From e45301c54a93d4768e2e33414a1c924a2d437a32 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Wed, 30 Oct 2024 16:22:16 +0100 Subject: [PATCH 1/4] feat(appsync): Add new service AppSync with unit tests --- .../aws/services/appsync/__init__.py | 0 .../aws/services/appsync/appsync_client.py | 4 ++ .../aws/services/appsync/appsync_service.py | 62 +++++++++++++++++ .../services/appsync/appsync_service_test.py | 66 +++++++++++++++++++ 4 files changed, 132 insertions(+) create mode 100644 prowler/providers/aws/services/appsync/__init__.py create mode 100644 prowler/providers/aws/services/appsync/appsync_client.py create mode 100644 prowler/providers/aws/services/appsync/appsync_service.py create mode 100644 tests/providers/aws/services/appsync/appsync_service_test.py diff --git a/prowler/providers/aws/services/appsync/__init__.py b/prowler/providers/aws/services/appsync/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 0000000000..6f9bfb559f --- /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 + +fsx_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 0000000000..b744355901 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_service.py @@ -0,0 +1,62 @@ +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.graphqlapis = {} + 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, + ) + ): + aux_tags = [] + if api.get("tags"): + aux_tags.append(api.get("tags")) + self.graphqlapis[api_arn] = GraphqlApi( + id=api["apiId"], + name=api["name"], + arn=api_arn, + region=regional_client.region, + type=api.get("apiType"), + field_log_level=api.get("logConfig", {}).get( + "fieldLogLevel", "" + ), + authentication_type=api.get("authenticationType", ""), + tags=aux_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: Optional[str] + field_log_level: Optional[str] + authentication_type: Optional[str] + 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 0000000000..20f81bcb28 --- /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.graphqlapis[api_arn].name == "test-api" + assert appsync_client.graphqlapis[api_arn].field_log_level == "ALL" + assert appsync_client.graphqlapis[api_arn].authentication_type == "API_KEY" + assert appsync_client.graphqlapis[api_arn].tags == [] From 08e5438f6fec1b29c15b1429f1b051ca894d74ec Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Wed, 30 Oct 2024 16:56:14 +0100 Subject: [PATCH 2/4] fix(appsync): Fix typo --- prowler/providers/aws/services/appsync/appsync_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prowler/providers/aws/services/appsync/appsync_client.py b/prowler/providers/aws/services/appsync/appsync_client.py index 6f9bfb559f..3ab8314a99 100644 --- a/prowler/providers/aws/services/appsync/appsync_client.py +++ b/prowler/providers/aws/services/appsync/appsync_client.py @@ -1,4 +1,4 @@ from prowler.providers.aws.services.appsync.appsync_service import AppSync from prowler.providers.common.provider import Provider -fsx_client = AppSync(Provider.get_global_provider()) +appsync_client = AppSync(Provider.get_global_provider()) From 191b62eaccb4c2a71a2a29ec968f5bb6fe088c76 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Thu, 31 Oct 2024 15:29:24 +0100 Subject: [PATCH 3/4] feat(appsync): Add check logic with respective unit tests. Add metadata too --- .../__init__.py | 0 ..._field_level_logging_enabled.metadata.json | 34 ++++ .../appsync_field_level_logging_enabled.py | 25 +++ ...ppsync_field_level_logging_enabled_test.py | 145 ++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/__init__.py create mode 100644 prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json create mode 100644 prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py create mode 100644 tests/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled_test.py diff --git a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/__init__.py b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json new file mode 100644 index 0000000000..6bd1859bd8 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "appsync_field_level_logging_enabled", + "CheckTitle": "AWS AppSync should have field-level logging enabled", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "appsync", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:appsync:{region}:{account-id}:apis/{api-id}", + "Severity": "medium", + "ResourceType": "AwsAppSyncGraphQLApi", + "Description": "This control checks whether an AWS AppSync API has field-level logging turned on. The control fails if the field resolver log level is set to None.", + "Risk": "Without field-level logging enabled, it's difficult to monitor, troubleshoot, and optimize GraphQL API queries effectively.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/appsync-logging-enabled.html", + "Remediation": { + "Code": { + "CLI": "aws appsync update-graphql-api --api-id --log-config fieldLogLevel=", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/appsync-controls.html#appsync-2", + "Terraform": "" + }, + "Recommendation": { + "Text": "Enable field-level logging for your AWS AppSync API to monitor and troubleshoot GraphQL queries effectively.", + "Url": "https://docs.aws.amazon.com/appsync/latest/devguide/monitoring.html#setup-and-configuration" + } + }, + "Categories": [ + "logging" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py new file mode 100644 index 0000000000..25c1d535ab --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py @@ -0,0 +1,25 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.appsync.appsync_client import appsync_client + + +class appsync_field_level_logging_enabled(Check): + def execute(self): + findings = [] + for api in appsync_client.graphqlapis.values(): + report = Check_Report_AWS(self.metadata()) + report.region = api.region + report.resource_id = api.id + report.resource_arn = api.arn + report.resource_tags = api.tags + report.status = "PASS" + report.status_extended = ( + f"AppSync API {api.name} has field log level enabled." + ) + if api.field_log_level != "ALL" and api.field_log_level != "ERROR": + report.status = "FAIL" + report.status_extended = ( + f"AppSync API {api.name} does not have field log level enabled." + ) + findings.append(report) + + return findings diff --git a/tests/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled_test.py b/tests/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled_test.py new file mode 100644 index 0000000000..f39e23c8ff --- /dev/null +++ b/tests/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled_test.py @@ -0,0 +1,145 @@ +from unittest import mock + +import botocore +from boto3 import client +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, +) + +orig = botocore.client.BaseClient._make_api_call + + +def mock_make_api_call(self, operation_name, kwarg): + if operation_name == "ListGraphqlApis": + return { + "graphqlApis": [ + { + "name": "test-log-level", + "apiId": "idididid", + "apiType": "MERGED", + "arn": f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-log-level", + "authenticationType": "API_KEY", + "logConfig": {"fieldLogLevel": "ALL"}, + "region": AWS_REGION_US_EAST_1, + "tags": {"test": "test", "test2": "test2"}, + }, + ] + } + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_v2(self, operation_name, kwarg): + if operation_name == "ListGraphqlApis": + return { + "graphqlApis": [ + { + "name": "test-none-log-level", + "apiId": "idididid", + "apiType": "GRAPHQL", + "arn": f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-none-log-level", + "authenticationType": "AWS_IAM", + "logConfig": {"fieldLogLevel": "NONE"}, + "region": AWS_REGION_US_EAST_1, + "tags": {"test": "test", "test2": "test2"}, + }, + ] + } + return orig(self, operation_name, kwarg) + + +class Test_appsync_field_level_logging_enabled: + @mock_aws + def test_no_apis(self): + client("appsync", region_name=AWS_REGION_US_EAST_1) + + 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.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled import ( + appsync_field_level_logging_enabled, + ) + + check = appsync_field_level_logging_enabled() + result = check.execute() + + assert len(result) == 0 + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + def test_graphql_no_api_key(self): + + 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.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled import ( + appsync_field_level_logging_enabled, + ) + + check = appsync_field_level_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert ( + result[0].resource_arn + == f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-log-level" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "idididid" + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "AppSync API test-log-level has field log level enabled." + ) + assert result[0].resource_tags == [{"test": "test", "test2": "test2"}] + + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + def test_graphql_api_key(self): + + 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.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_field_level_logging_enabled.appsync_field_level_logging_enabled import ( + appsync_field_level_logging_enabled, + ) + + check = appsync_field_level_logging_enabled() + result = check.execute() + + assert len(result) == 1 + assert ( + result[0].resource_arn + == f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-none-log-level" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "idididid" + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "AppSync API test-none-log-level does not have field log level enabled." + ) + assert result[0].resource_tags == [{"test": "test", "test2": "test2"}] From 7297e8fe55514954068c033e642f426128488508 Mon Sep 17 00:00:00 2001 From: MarioRgzLpz Date: Thu, 7 Nov 2024 20:43:08 +0100 Subject: [PATCH 4/4] feat(servicecatalog): Clarify that we only check for GraphQL APIs because boto3 does not have a method to check for other types of AppSync APIs and fix typo in graphql_apis --- .../appsync_field_level_logging_enabled.metadata.json | 2 +- .../appsync_field_level_logging_enabled.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json index 6bd1859bd8..642e29059c 100644 --- a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json +++ b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.metadata.json @@ -10,7 +10,7 @@ "ResourceIdTemplate": "arn:aws:appsync:{region}:{account-id}:apis/{api-id}", "Severity": "medium", "ResourceType": "AwsAppSyncGraphQLApi", - "Description": "This control checks whether an AWS AppSync API has field-level logging turned on. The control fails if the field resolver log level is set to None.", + "Description": "This control checks whether an AWS AppSync API (only GraphQL APIs since boto3 doesnt have a method to return other APIs) field-level logging turned on. The control fails if the field resolver log level is set to None.", "Risk": "Without field-level logging enabled, it's difficult to monitor, troubleshoot, and optimize GraphQL API queries effectively.", "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/appsync-logging-enabled.html", "Remediation": { diff --git a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py index 25c1d535ab..0863ccfeb6 100644 --- a/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py +++ b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py @@ -5,7 +5,8 @@ class appsync_field_level_logging_enabled(Check): def execute(self): findings = [] - for api in appsync_client.graphqlapis.values(): + # Check only GraphQL APIs because boto3 does not have a method to get other types of AppSync APIs (list_apis is not working) + for api in appsync_client.graphql_apis.values(): report = Check_Report_AWS(self.metadata()) report.region = api.region report.resource_id = api.id