WAF Web ACLをAppSyncに適用する

WAF Web ACLをAppSyncに適用する

WAF Web ACLをAppSyncに適用する

AWS WAFは以下の4種類のリソースに適用することができます。

  • ALB
  • CloudFront
  • API Gateway
  • AppSync

今回はAppSyncに適用する方法を確認します。

なおWAFをALBに適用する方法については、以下のページをご確認ください。

あわせて読みたい
CFNでWAF Web ACL入門 – ALB向け 【CloudFormationを使用してWAF Web ACLを作成する】 AWS WAF(Web Application Firewall)はAWSが提供するセキュリティサービスです。WAFはいくつかのサービスで構成され...

WAFをCloudFrontに適用する方法については、以下のページをご確認ください。

あわせて読みたい
WAF Web ACLをCloudFrontに適用する 【WAF Web ACLをCloudFrontに適用する】 AWS WAFは以下の4種類のリソースに適用することができます。 ALB CloudFront API Gateway AppSync 今回はCloudFrontに適用する...

WAFをAPI Gatewayに適用する方法については、以下のページをご確認ください。

あわせて読みたい
WAF Web ACLをAPI Gatewayに適用する 【WAF Web ACLをAPI Gatewayに適用する】 AWS WAFは以下の4種類のリソースに適用することができます。 ALB CloudFront API Gateway AppSync 今回はAPI Gatewayに適用す...

構築する環境

Diagram of apply WAF Web ACL to AppSync

WAF Web ACLを作成します。
地理制限を行います。
具体的にはルールグループを自作し、その中に日本(JP)からのアクセスをブロックするルールを設定します。
作成したWeb ACLをAppSyncに適用します。

AppSync内でスキーマを定義し、以下の操作が実現できるよう設定を行います。

  • 扱うデータは2つのフィールド(keyとdatetime)を持つ。
  • 保存されている全データを取得するクエリ。

今回の構成では、AppSyncにデータソースは作成しません。
ですから上記の動作を実現するために、リゾルバに擬似的な仮データを定義します。

AppSyncによるGraphQL APIを実行するクライアントとして、Lambda関数を作成します。
Function URLを有効化し、URLクエリパラメータで実行する操作を指定できるようにします。
関数のランタイム環境はPython3.8とします。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

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

テンプレートファイルのポイント解説

本ページは、AppSyncにWAFを適用する方法を中心に取り上げます。

WAFで地理制限する方法については、以下のページをご確認ください。

あわせて読みたい
WAF Web ACLで地理制限 【WAF Web ACLのルールグループを自作して地理制限する】 WAF Web ACLでは様々な条件でルールを設定することができます。今回は地理制限に関するルールグループを自作し...

データソースがないタイプのAppSyncを構築する方法については、以下のページをご確認ください。

あわせて読みたい
AppSync – データソース:None 【データソースなしのAppSyncを構築する】 AppSyncは以下のサービスの中からデータソースを選択することができます。 Lambda DynamoDB OpenSearch None HTTPエンドポイ...

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)

Scopeプロパティがポイントです。
Web ACLをCloudFrontに適用する場合は「CLOUDFRONT」、それ以外のリソースの場合は「REGIONAL」となります。
今回はAppSyncですから後者となります。

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

AWS::WAFv2::WebACLAssociationリソースを作成することで、AppSyncにWAFを適用することができます。
ResourceArnにAppSync APIのARNを指定します。

(参考)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)

データソースなしのAppSyncを作成します。
データソースを用意しないため、リゾルバ内のレスポンスマッピングにデータを定義することで、クライアントからのクエリに応答できるように設定します。

(参考)GraphQLクライアントLambda関数

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)

GraphQLクエリを実行するLambda関数です。
今回はPython用GraphQLクライアントライブラリとして、GQLを使用します。

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

GQLを使って、スキーマで定義したクエリを実行します。
実行結果をクライアントに返します。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

Lambda関数用のデプロイパッケージを用意する

Lambda関数を作成する場合、3つの方法があります。
今回はデプロイパッケージをS3バケットにアップロードする方法を選択します。
詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CloudFormationでLambdaを作成する3パータン(S3/インライン/コンテナ) 【CloudFormationでLambdaを作成する】 CloudFormationでLambdaを作成する場合、大別すると以下の3パターンあります。 S3バケットにコードをアップロードする インライ...

Lambdaレイヤー用のデプロイパッケージを用意する

先述のGQLをLambdaレイヤーとして用意します。
Lambdaレイヤーに関する詳細は、以下のページをご確認ください。

あわせて読みたい
CFNでLambdaレイヤー作成 【CloudFormationでLambdaレイヤー作成】 本ページでは、CloudFormationでLambdaレイヤーを作成する方法を確認します。 Lambda レイヤーは、Lambda 関数で使用できるラ...

なおLambdaレイヤー用パッケージを作成ためのコマンドは以下となります。

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

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

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • AppSync API:fa-055-GraphQLApi
  • GraphQLクライアントLambda関数のFunction URL:https://ygsu4o3c4grz5sjsdxquyqyngi0rxvbw.lambda-url.ap-northeast-1.on.aws/

AWS Management Consoleからもリソースを確認します。
まずAppSyncを確認します。

Detail of AppSync 1.
Detail of AppSync 2.

AppSync APIが作成され、WAFが適用されています。
つまりAPI URLにアクセスしようとすると、その前にWAFで検査が行われるという挙動になります。

ちなみに作成されたAppSync APIの詳細は以下の通りです。

Schema and Resolvers of AppSync.
Detail of Resolvers.

CloudFormationテンプレートで定義した通りにAPIが作成されています。
このAPIにはデータソースが存在しないため、listSamapleDatasクエリを実行すると、レスポンスマッピングに定義された値が返されるという挙動になります。

次にWAFを確認します。

WAF Web ACL is applied to AppSync.

WAF側からも、適用する対象がAppSyncであることが確認できます。

動作確認

準備が整いましたので、GraphQLクライアントLambda関数のFunction URLにアクセスします。

Access to AppSync was blocked by WAF Web ACL.

エラーが返ってきました。
日本リージョンに作成したLambda関数からAppSyncにアクセスしたため、その前面に配置されているWAFによってブロックされたということです。

確認のために、地理制限のアクションを変更します。

WAF rule changed from block to count.

ルールのアクションをブロックからカウント、つまりルールは生かしつつ、ルールに該当するトラフィック数を数えるだけに変更しました。

改めてLambda関数にアクセスします。

AppSync was accessible through the WAF Web ACL.

AppSyncの実行結果が返ってきました。
ルールのアクションがブロックからカウントだけになり、アクセスが可能になったためです。

まとめ

WAF ACLをAppSyncに適用する方法を確認しました。