feat(appsync): add new check appsync_graphql_apis_no_api_key_authentication (#5591)

Co-authored-by: Sergio Garcia <38561120+sergargar@users.noreply.github.com>
This commit is contained in:
Mario Rodriguez Lopez
2024-11-07 17:42:07 +01:00
committed by GitHub
parent ce1e9de104
commit 6bdcb509e1
5 changed files with 245 additions and 1 deletions
@@ -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 <api-id> --authentication-type <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": ""
}
@@ -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
@@ -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", {})],
)
@@ -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"}]