WAF Web ACLをAppSyncに適用する
AWS WAFは以下の4種類のリソースに適用することができます。
- ALB
- CloudFront
- API Gateway
- AppSync
今回はAppSyncに適用する方法を確認します。
なおWAFをALBに適用する方法については、以下のページをご確認ください。
WAFをCloudFrontに適用する方法については、以下のページをご確認ください。
WAFをAPI Gatewayに適用する方法については、以下のページをご確認ください。
構築する環境
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で地理制限する方法については、以下のページをご確認ください。
データソースがないタイプのAppSyncを構築する方法については、以下のページをご確認ください。
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バケットにアップロードする方法を選択します。
詳細につきましては、以下のページをご確認ください。
Lambdaレイヤー用のデプロイパッケージを用意する
先述のGQLをLambdaレイヤーとして用意します。
Lambdaレイヤーに関する詳細は、以下のページをご確認ください。
なおLambdaレイヤー用パッケージを作成ためのコマンドは以下となります。
$ sudo pip3 install --pre gql[all] -t python
$ zip -r layer.zip python
Code language: Bash (bash)
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- AppSync API:fa-055-GraphQLApi
- GraphQLクライアントLambda関数のFunction URL:https://ygsu4o3c4grz5sjsdxquyqyngi0rxvbw.lambda-url.ap-northeast-1.on.aws/
AWS Management Consoleからもリソースを確認します。
まずAppSyncを確認します。
AppSync APIが作成され、WAFが適用されています。
つまりAPI URLにアクセスしようとすると、その前にWAFで検査が行われるという挙動になります。
ちなみに作成されたAppSync APIの詳細は以下の通りです。
CloudFormationテンプレートで定義した通りにAPIが作成されています。
このAPIにはデータソースが存在しないため、listSamapleDatasクエリを実行すると、レスポンスマッピングに定義された値が返されるという挙動になります。
次にWAFを確認します。
WAF側からも、適用する対象がAppSyncであることが確認できます。
動作確認
準備が整いましたので、GraphQLクライアントLambda関数のFunction URLにアクセスします。
エラーが返ってきました。
日本リージョンに作成したLambda関数からAppSyncにアクセスしたため、その前面に配置されているWAFによってブロックされたということです。
確認のために、地理制限のアクションを変更します。
ルールのアクションをブロックからカウント、つまりルールは生かしつつ、ルールに該当するトラフィック数を数えるだけに変更しました。
改めてLambda関数にアクセスします。
AppSyncの実行結果が返ってきました。
ルールのアクションがブロックからカウントだけになり、アクセスが可能になったためです。
まとめ
WAF ACLをAppSyncに適用する方法を確認しました。