Apply WAF Web ACL to AppSync

Apply WAF Web ACL to AppSync

Apply WAF Web ACL to AppSync

AWS WAF can be applied to the following four types of resources

  • ALB
  • CloudFront
  • API Gateway
  • AppSync

In this article, we will check how to apply WAF to AppSync.

For details on how to apply WAF to ALB, please refer to the following page.

あわせて読みたい
Introduction to WAF Web ACL with CNF – ALB Ver. 【Creating WAF Web ACL with CloudFormation】 AWS WAF (Web Application Firewall) is a security service provided by AWS.WAF consists of several services, but t...

For information on how to apply WAF to CloudFront, please refer to the following page

あわせて読みたい
Apply WAF Web ACL to CloudFront 【Apply WAF Web ACL to CloudFront】 AWS WAF can be applied to the following four types of resources ALB CloudFront API Gateway AppSync In this article, we wi...

For information on how to apply WAF to API Gateway, please refer to the following page.

あわせて読みたい
Apply WAF Web ACL to API Gateway 【Apply WAF Web ACL to API Gateway】 AWS WAF can be applied to the following four types of resources ALB CloudFront API Gateway AppSync In this article, we w...

Environment

Diagram of apply WAF Web ACL to AppSync

Create a WAF Web ACL.
Restrict geography.
Specifically, create your own rule group and set a rule to block access from Japan (JP) in it.
Apply the created Web ACL to AppSync.

Define a schema in AppSync and configure it so that the following operations can be realized.

  • The data to be handled has two fields (key and datetime).
  • A query to retrieve all stored data.

In this configuration, no data source will be created in AppSync.
So to achieve the above action, we define pseudo temporary data in the resolver.

Create a Lambda function as a client to execute the GraphQL API with AppSync.
Enable the Function URL so that the URL query parameter can specify the operation to be performed.
The runtime environment for the function is Python 3.8.

CloudFormation template files

The above configuration is built using CloudFormation.
The CloudFormation template is located at the following URL

https://github.com/awstut-an-r/awstut-fa/tree/main/055

Explanation of key points of template files

This page focuses on how to apply WAF to AppSync.

For more information on how to restrict geography with WAF, please refer to the following page

あわせて読みたい
Geographic Restrictions using WAF Web ACL 【Create your own rule groups in WAF Web ACL to restrict geography】 WAF Web ACL allows you to set up rules with various conditions.In this article, we will ...

For information on how to build a type of AppSync with no data source, please check the following page

あわせて読みたい
AppSync – Data Source: None 【Building AppSync without Data Source】 AppSync allows you to choose a data source from the following services Lambda DynamoDB OpenSearch None HTTP endpoint...

Web ACL

Resources:
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction:
        Allow: {}
      Name: !Sub "${Prefix}-WebACL"
      Rules:
        - Name: !Sub "${Prefix}-WebACL-GeoRestriction"
          OverrideAction:
            None: {}
          Priority: 0
          Statement:
            RuleGroupReferenceStatement:
              Arn: !GetAtt RuleGroup.Arn
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${Prefix}-WebACL-GeoRestriction"
            SampledRequestsEnabled: true
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Ref Prefix
        SampledRequestsEnabled: true
Code language: YAML (yaml)

The Scope property is the key.
It is “CLOUDFRONT” if the Web ACL is applied to CloudFront, and “REGIONAL” for other resources.
In this case, since this is AppSync, the latter is used.

Resources:
  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      ResourceArn: !Ref ApiArn
      WebACLArn: !GetAtt WebACL.Arn
Code language: YAML (yaml)

By creating an AWS::WAFv2::WebACLAssociation resource, you can apply WAF to AppSync.
Specify the ARN of the AppSync API in the ResourceArn.

(Reference) AppSync

Resources:
  GraphQLApi:
    Type: AWS::AppSync::GraphQLApi
    Properties:
      AuthenticationType: API_KEY
      Name: !Sub "${Prefix}-GraphQLApi"

  DataSource:
    Type: AWS::AppSync::DataSource
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId
      Name: DataSource
      Type: NONE

  GraphQLSchema:
    Type: AWS::AppSync::GraphQLSchema
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId
      Definition: |
        schema {
          query: Query
        }

        type Query {
          listSampleDatas: [SampleData]
        }

        type SampleData {
          key: String!
          datetime: String
        }

  ListSampleDatasResolver:
    Type: AWS::AppSync::Resolver
    DependsOn:
      - GraphQLSchema
    Properties:
      ApiId: !GetAtt GraphQLApi.ApiId
      DataSourceName: !GetAtt DataSource.Name
      FieldName: listSampleDatas
      Kind: UNIT
      RequestMappingTemplate: |
        {
          "version": "2018-05-29",
        }
      ResponseMappingTemplate: |
        [
          {
            "key": "hoge",
            "datetime": "2022-05-15 12:34:56.7890"
          },
          {
            "key": "foo",
            "datetime": "2022-05-16 12:34:56.7890"
          },
          {
            "key": "bar",
            "datetime": "2022-05-17 12:34:56.7890"
          }
        ]
      TypeName: Query
Code language: YAML (yaml)

Create AppSync without data source.
Since no data source is provided, configure it so that it can respond to queries from clients by defining data in response mappings in the resolver.

(Reference) GraphQL client Lambda function

import json
import os
import time
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport

api_key = os.environ['API_KEY']
graphql_url = os.environ['GRAPHQL_URL']

transport = AIOHTTPTransport(
  url=graphql_url,
  headers={
    'x-api-key': api_key
  })
client = Client(transport=transport, fetch_schema_from_transport=True)


def lambda_handler(event, context):
  document = gql(
    """
    query ListSampleDatas {
      listSampleDatas {
        key
      	datetime
      }
    }
    """
    )
  result = client.execute(document)

  return {
    'statusCode': 200,
    'body': json.dumps(result, indent=2)
  }
Code language: Python (python)

Lambda function to execute a GraphQL query.
In this case, we will use GQL as the GraphQL client library for Python.

https://github.com/graphql-python/gql

Using GQL, execute the query defined in the schema.
The execution results are returned to the client.

Architecting

Use CloudFormation to build this environment and check the actual behavior.

Prepare deployment package for Lambda function

There are three ways to create a Lambda function.
In this case, we will choose the method of uploading a deployment package to an S3 bucket.
For more information, please refer to the following page

あわせて読みたい
3 parterns to create Lambda with CloudFormation (S3/Inline/Container) 【Creating Lambda with CloudFormation】 When creating a Lambda with CloudFormation, there are three main patterns as follows. Uploading the code to an S3 buc...

Prepare deployment package for Lambda layer

Prepare the aforementioned GQL as a Lambda layer.
For more information on the Lambda layer, please refer to the following page.

あわせて読みたい
Create Lambda layer using CFN 【Creating Lambda Layer using CloudFormation】 This page reviews how to create a Lambda layer in CloudFormation. Lambda layers provide a convenient way to pa...

The command to create a package for the Lambda layer is as follows

$ sudo pip3 install --pre gql[all] -t python

$ zip -r layer.zip python
Code language: Bash (bash)

Create CloudFormation stacks and check resources in stacks

Create a CloudFormation stacks.
For information on how to create stacks and check each stack, please refer to the following page

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After checking the resources in each stack, information on the main resources created this time is as follows

  • AppSync API: fa-055-GraphQLApi
  • Function URL for the GraphQL client Lambda function: https://ygsu4o3c4grz5sjsdxquyqyngi0rxvbw.lambda-url.ap-northeast-1.on.aws/

We will also check the resources from the AWS Management Console.
First, check AppSync.

Detail of AppSync 1.
Detail of AppSync 2.

The AppSync API has been created and WAF has been applied.
In other words, when you try to access the API URL, it will be inspected by WAF.

The details of the created AppSync API are as follows.

Schema and Resolvers of AppSync.
Detail of Resolvers.

The API is created as defined in the CloudFormation template.
Since there is no data source for this API, executing a listSamapleDatas query will return the values defined in the response mapping.

Next, check the WAF.

WAF Web ACL is applied to AppSync.

From the WAF side, we can confirm that the target to be applied is AppSync.

Checking Action

Now that everything is ready, access the Function URL of the GraphQL client Lambda function.

Access to AppSync was blocked by WAF Web ACL.

An error is returned.
This means that AppSync was accessed from a Lambda function created in the Japan region and was blocked by the WAF deployed in front of it.

To confirm, change the action of the geo-restriction.

WAF rule changed from block to count.

We changed the action of the rule from blocking to counting, i.e., keeping the rule alive but only counting the number of traffic that corresponds to the rule.

We access the Lambda function again.

AppSync was accessible through the WAF Web ACL.

The result of the AppSync execution is returned.
This is because the rule action has been changed from block to count only, and access is now possible.

Summary

We have confirmed how to apply WAF ACL to AppSync.