From f231d8b080ee9dd20aaf82edf5d1d6576de2f729 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:23:13 +0100 Subject: [PATCH] feat(appsync): add new check `appsync_field_level_logging_enabled` (#5602) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../__init__.py | 0 ..._field_level_logging_enabled.metadata.json | 34 ++++ .../appsync_field_level_logging_enabled.py | 26 ++++ ...ppsync_field_level_logging_enabled_test.py | 145 ++++++++++++++++++ 4 files changed, 205 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..642e29059c --- /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 (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": { + "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..0863ccfeb6 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_field_level_logging_enabled/appsync_field_level_logging_enabled.py @@ -0,0 +1,26 @@ +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 = [] + # 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 + 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"}]