From 6bdcb509e1a3cf5c5545c7c502807da9fff4e6c0 Mon Sep 17 00:00:00 2001 From: Mario Rodriguez Lopez <101330800+MarioRgzLpz@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:42:07 +0100 Subject: [PATCH] feat(appsync): add new check `appsync_graphql_apis_no_api_key_authentication` (#5591) Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com> --- .../__init__.py | 0 ...pi_no_api_key_authentication.metadata.json | 34 ++++ ...c_graphql_api_no_api_key_authentication.py | 22 +++ .../aws/services/appsync/appsync_service.py | 4 +- ...phql_api_no_api_key_authentication_test.py | 186 ++++++++++++++++++ 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/__init__.py create mode 100644 prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.metadata.json create mode 100644 prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.py create mode 100644 tests/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication_test.py diff --git a/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/__init__.py b/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.metadata.json b/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.metadata.json new file mode 100644 index 0000000000..6accf88801 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.metadata.json @@ -0,0 +1,34 @@ +{ + "Provider": "aws", + "CheckID": "appsync_graphql_api_no_api_key_authentication", + "CheckTitle": "AWS AppSync GraphQL APIs should not be authenticated with API keys", + "CheckType": [ + "Software and Configuration Checks/AWS Security Best Practices" + ], + "ServiceName": "appsync", + "SubServiceName": "", + "ResourceIdTemplate": "arn:aws:appsync:{region}:{account-id}:apis/{api-id}", + "Severity": "high", + "ResourceType": "AwsAppSyncGraphQLApi", + "Description": "This control checks whether your application uses an API key to interact with an AWS AppSync GraphQL API. The control fails if an AWS AppSync GraphQL API is authenticated with an API key.", + "Risk": "API keys in AppSync can expose applications to unauthorized access if compromised. Avoiding API keys helps reduce the risk of unintended access.", + "RelatedUrl": "https://docs.aws.amazon.com/config/latest/developerguide/appsync-authorization-check.html", + "Remediation": { + "Code": { + "CLI": "aws appsync update-graphql-api --api-id --authentication-type ", + "NativeIaC": "", + "Other": "https://docs.aws.amazon.com/securityhub/latest/userguide/appsync-controls.html#appsync-5", + "Terraform": "" + }, + "Recommendation": { + "Text": "Use authentication methods other than API keys for AWS AppSync GraphQL APIs, such as AWS_IAM or Amazon Cognito.", + "Url": "https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html" + } + }, + "Categories": [ + "trustboundaries" + ], + "DependsOn": [], + "RelatedTo": [], + "Notes": "" +} diff --git a/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.py b/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.py new file mode 100644 index 0000000000..cdb491a755 --- /dev/null +++ b/prowler/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication.py @@ -0,0 +1,22 @@ +from prowler.lib.check.models import Check, Check_Report_AWS +from prowler.providers.aws.services.appsync.appsync_client import appsync_client + + +class appsync_graphql_api_no_api_key_authentication(Check): + def execute(self): + findings = [] + for api in appsync_client.graphql_apis.values(): + if api.type == "GRAPHQL": + 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 GraphQL API {api.name} is not using an API KEY for authentication." + if api.authentication_type == "API_KEY": + report.status = "FAIL" + report.status_extended = f"AppSync GraphQL API {api.name} is using an API KEY for authentication." + findings.append(report) + + return findings diff --git a/prowler/providers/aws/services/appsync/appsync_service.py b/prowler/providers/aws/services/appsync/appsync_service.py index dd2985b828..cb39aa8f5d 100644 --- a/prowler/providers/aws/services/appsync/appsync_service.py +++ b/prowler/providers/aws/services/appsync/appsync_service.py @@ -38,7 +38,9 @@ class AppSync(AWSService): field_log_level=api.get("logConfig", {}).get( "fieldLogLevel", "" ), - authentication_type=api.get("authenticationType", ""), + authentication_type=api.get( + "authenticationType", "API_KEY" + ), tags=[api.get("tags", {})], ) diff --git a/tests/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication_test.py b/tests/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication_test.py new file mode 100644 index 0000000000..caf7dbf74b --- /dev/null +++ b/tests/providers/aws/services/appsync/appsync_graphql_api_no_api_key_authentication/appsync_graphql_api_no_api_key_authentication_test.py @@ -0,0 +1,186 @@ +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-merged-api", + "apiId": "api_id", + "apiType": "MERGED", + "arn": f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-merged-api", + "authenticationType": "API_KEY", + "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-graphql-no-api-key", + "apiId": "api_id", + "apiType": "GRAPHQL", + "arn": f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-graphql-no-api-key", + "authenticationType": "AWS_IAM", + "region": AWS_REGION_US_EAST_1, + "tags": {"test": "test", "test2": "test2"}, + }, + ] + } + return orig(self, operation_name, kwarg) + + +def mock_make_api_call_v3(self, operation_name, kwarg): + if operation_name == "ListGraphqlApis": + return { + "graphqlApis": [ + { + "name": "test-graphql-api-key", + "apiId": "api_id", + "apiType": "GRAPHQL", + "arn": f"arn:aws:appsync:{AWS_REGION_US_EAST_1}:{AWS_ACCOUNT_NUMBER}:graphqlapi/test-graphql-api-key", + "authenticationType": "API_KEY", + "region": AWS_REGION_US_EAST_1, + "tags": {"test": "test", "test2": "test2"}, + }, + ] + } + return orig(self, operation_name, kwarg) + + +class Test_appsync_graphql_api_no_api_key_authentication: + @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_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication import ( + appsync_graphql_api_no_api_key_authentication, + ) + + check = appsync_graphql_api_no_api_key_authentication() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call) + def test_merged_api(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_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication import ( + appsync_graphql_api_no_api_key_authentication, + ) + + check = appsync_graphql_api_no_api_key_authentication() + result = check.execute() + + assert len(result) == 0 + + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v2) + 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_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication import ( + appsync_graphql_api_no_api_key_authentication, + ) + + check = appsync_graphql_api_no_api_key_authentication() + 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-graphql-no-api-key" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "api_id" + assert result[0].status == "PASS" + assert ( + result[0].status_extended + == "AppSync GraphQL API test-graphql-no-api-key is not using an API KEY for authentication." + ) + assert result[0].resource_tags == [{"test": "test", "test2": "test2"}] + + @mock_aws + @mock.patch("botocore.client.BaseClient._make_api_call", new=mock_make_api_call_v3) + 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_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication.appsync_client", + new=AppSync(aws_provider), + ): + # Test Check + from prowler.providers.aws.services.appsync.appsync_graphql_api_no_api_key_authentication.appsync_graphql_api_no_api_key_authentication import ( + appsync_graphql_api_no_api_key_authentication, + ) + + check = appsync_graphql_api_no_api_key_authentication() + 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-graphql-api-key" + ) + assert result[0].region == AWS_REGION_US_EAST_1 + assert result[0].resource_id == "api_id" + assert result[0].status == "FAIL" + assert ( + result[0].status_extended + == "AppSync GraphQL API test-graphql-api-key is using an API KEY for authentication." + ) + assert result[0].resource_tags == [{"test": "test", "test2": "test2"}]