WAFのレートベースルールでリソース(CloudFront)を保護する
AWS SOAの出題範囲の1つでもある、ネットワークとコンテンツ配信に関する内容です。
WAF Web ACLでは、様々なルールを定義することができますが、その1つにレートベースのルールがあります。
レートベースのルールは、各発生元 IP アドレスからのリクエストのレートを追跡し、レートが制限を超えると、IP に対するルールアクションをトリガーします。制限は、5 分間あたりのリクエスト数として設定します。
レートベースのルールステートメント
レートベースのルールを作成することによって、DoS攻撃からリソースを保護することができます。
今回はCloudFrontをWAFを適用する対象とします。
ただし他のリソース(ALB、API Gateway、AppSync)でも、今回と同様の構成で設定可能です。
構築する環境
WAF Web ACLを作成します。
レートベースのルールを作成します。
作成したWeb ACLをCloudFrontに適用します。
CloudFrontディストリビューションを作成します。
オリジンにS3バケットを指定します。
CloudFrontにOAIを作成し、S3バケットポリシーでOAIからのアクセスのみを許可する制限を行います。
Lambda関数を作成します。
この関数をCloudFormationカスタムリソースとして設定します。
この関数の働きですが、スタック生成・削除時に、自動的にバケットにオブジェクト生成・削除を実行するように設定します。
生成するオブジェクトは静的ウェブサイトホスティング用のindex.htmlとします。
関数のランタイムはPython3.8とします。
VPCを作成し、2つのサブネットを準備します。
1つをプライベートサブネットとし、内部にEC2インスタンスを配置します。
もう1つをパブリックサブネットとし、NATゲートウェイを配置します。
インスタンスは、最新版のAmazon Linux 2で作成し、CloudFrontディストリビューションにアクセスするためのクライアントとして使用します。
レートベースのルールを試験するために、Apache Benchを使用します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置してます。
https://github.com/awstut-an-r/awstut-soa/tree/main/05/002
テンプレートファイルのポイント解説
本ページでは、WAF Web ACLにレートベースのルールを定義する方法を中心として取り上げます。
WAFの基本的な事項については、以下のページをご確認ください。
WAFをCloudFrontに適用する方法については、以下のページをご確認ください。
CloudFormationのカスタムリソースについては、以下のページをご確認ください。
レートベースのWAF Web ACLルール
Resources:
RuleGroup:
Type: AWS::WAFv2::RuleGroup
Properties:
Capacity: 10
Name: !Sub "${Prefix}-RateRestrictionRuleGroup"
Rules:
- Action:
Block: {}
Name: !Sub "${Prefix}-RateRestrictionRule"
Priority: 0
Statement:
RateBasedStatement:
AggregateKeyType: IP
Limit: !Ref Limit
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${Prefix}-RateRestrictionRule"
SampledRequestsEnabled: true
Scope: CLOUDFRONT
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: !Sub "${Prefix}-RateRestrictionRuleGroup"
SampledRequestsEnabled: true
Code language: YAML (yaml)
今回はルールグループ内にレートベースのルールを定義します。
ポイントはRateBasedStatementプロパティです。
レートベースのルールを作成する場合は、このプロパティを使用します。
AggregateKeyTypeプロパティはリクエストをカウントする対象を設定するものです。
リクエスト元のIPアドレスベースでカウントする場合は「IP」を、HTTPヘッダーに含まれるIPアドレスベースでカウントする場合は「FORWARDED_IP」を指定します。
今回は前者を選択します。
Limitプロパティでカウントの上限を設定します。
このプロパティの最小値である「100」を設定します。
つまり5分間の間に、特定のIPアドレスからのリクエスト数が100を超えた場合、そのIPアドレスからのアクセスをブロックするという挙動になります。
(参考)検証用EC2インスタンス
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref PrivateSubnet
GroupSet:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash -xe
yum update -y
yum install -y httpd
Code language: YAML (yaml)
特別な設定は行いません。
ユーザーデータでインスタンスの初期化処理を定義します。
Apacheのインストールを行うことで、Apache Benchも一緒にインストールされます。
インスタンスの初期化処理の詳細については、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
今回はスタックを作成するリージョンに気をつけてください。
AWS CLIでスタックを作成する場合、以下で示したコマンドの通り、regionオプションでus-east-1リージョンを指定してください。
CloudFrontに対してWAFを適用するためには、WAFをヴァージニアリージョンに作成する必要があるからです。
$ aws cloudformation create-stack \
--stack-name soa-05-002 \
--template-url https://[bucket-name].s3.[region].amazonaws.com/[folder-name]/soa-05-002.yaml \
--capabilities CAPABILITY_IAM \
--region us-east-1
Code language: Bash (bash)
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- S3バケット:soa-05-002
- CloudFrontディストリビューションのドメイン:https://d1suf56e055zgg.cloudfront.net
- WAF Web ACLの名前:soa-05-002-WebACL
- WAF Web ACLのID:2cec36bc-ec03-4e00-b70a-aab7cb984c7a
- WAF Web ACL内のルールの名前:soa-05-002-WebACL-RateRestriction
- WAFルールグループの名前:soa-05-002-RateRestrictionRuleGroup
- WAFルールグループ内に作成したルールの名前:soa-05-002-RateRestrictionRule
- EC2インスタンスのID:i-0e5e54ddc051b5f55
- NATゲートウェイにアタッチされたEIP:3.233.183.144
AWS Management Consoleからもリソースを確認します。
まずCloudFrontです。
CloudFrontディストリビューションが作成され、AWS WAFが適用されていることがわかります。
次にWeb ACLを確認します。
Web ACL側からも、このACLがCloudFrontに適用されていることがわかります。
次にWeb ACL内に作成されたルールを確認します。
ルールグループを参照する形で、ルールが定義されています。
ルールの内容も確認します。
レートベースのルールで、IPアドレスベースでリクエストをカウントし、リミットが100に設定されています。
動作確認
準備が整いましたので、検証用クライアントであるEC2インスタンスにアクセスします。
アクセスはSSM Session Managerを使用します。
% aws ssm start-session \
--target i-0e5e54ddc051b5f55 \
--region us-east-1
Starting session with SessionId: root-09545bd848d2d2fae
sh-4.2$
Code language: Bash (bash)
SSM Session Managerに関する詳細は、以下のページをご確認ください。
この状況でCloudFrontディストリビューションにアクセスします。
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net
<html>
<head></head>
<body>
<h1>index.html</h1>
<p>soa-05-002</p>
</body>
</html>
Code language: Bash (bash)
CloudFrontのオリジンであるS3バケットに配置されているindex.htmlが返ってきました。
このタイミングでのWAFの状況を確認します。
NATゲートウェイにアタッチされたEIPからのリクエストが許可されています。
今の時点では、レートベースのルールで制限が有効化されていないため、通常通りにアクセスが可能ということです。
次にApache Benchを使用して、リクエスト数の急増を再現します。
sh-4.2$ ab -n 100 https://d1suf56e055zgg.cloudfront.net/
Code language: JavaScript (javascript)
CloudFrontディストリビューションのURLに対して、100回リクエストを実施します。
WAFの状況を確認します。
NATゲートウェイにアタッチされたEIPから、大量にリクエストを受けていることが確認できます。
これでレートベースルールのリミットに達したことになります。
改めてCloudFrontにアクセスします。
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>403 ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
<BR clear="all">
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: 1ULqdHi16O_YVBoiAcGSy6_cc8_Lhw7TyWlxsJ0OEEk5Tb7CTRsrNA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>
Code language: Bash (bash)
今度はエラーページが返ってきました。
WAFも確認します。
今度はブロックされたことがわかります。
レートベースルールによって、NATゲートウェイにアタッチされているEIPは、リクエストがブロックされたということです。
AWS CLIからブロックされているIPアドレスを確認することができます。
$ aws wafv2 get-rate-based-statement-managed-keys \
--scope CLOUDFRONT \
--region us-east-1 \
--web-acl-name soa-05-002-WebACL \
--web-acl-id 2cec36bc-ec03-4e00-b70a-aab7cb984c7a \
--rule-group-rule-name soa-05-002-WebACL-RateRestriction \
--rule-name soa-05-002-RateRestrictionRule
{
"ManagedKeysIPV4": {
"IPAddressVersion": "IPV4",
"Addresses": [
"3.233.183.144/32"
]
},
"ManagedKeysIPV6": {
"IPAddressVersion": "IPV6",
"Addresses": []
}
}
Code language: Bash (bash)
ブロックされているIPアドレスはAWS CLIから確認することが可能です。
https://docs.aws.amazon.com/ja_jp/waf/latest/developerguide/listing-managed-ips.html
詳細は以下のページに詳しいですが、ポイントは3つです。
https://docs.aws.amazon.com/cli/latest/reference/wafv2/get-rate-based-statement-managed-keys.html
1つ目はregionオプションです。
scopeオプションに「CLOUDFRONT」を指定する場合、regionオプションの指定も必須であり、値も「us-east-1」である必要があります。
2つ目はrule-group-rule-nameオプションです。
このオプションはルールグループを使用して、Web ACLを作成する場合は必須のオプションです。
このオプションの値は、WAF Web ACL内で定義されているルール名を指定します。
3つ目はrule-nameオプションです。
ルールグループを使用している場合、このオプションの値は、ルールグループ内で定義されているルール名を指定します。
レートベースのグループは5分あたりのリクエスト数に基づいてアクションが実行されます。
ですからしばらく時間を置き、ブロックされたIPアドレスのリストを確認します。
$ aws wafv2 get-rate-based-statement-managed-keys \
--scope CLOUDFRONT \
--region us-east-1 \
--web-acl-name soa-05-002-WebACL \
--web-acl-id 2cec36bc-ec03-4e00-b70a-aab7cb984c7a \
--rule-group-rule-name soa-05-002-WebACL-RateRestriction \
--rule-name soa-05-002-RateRestrictionRule
{
"ManagedKeysIPV4": {
"IPAddressVersion": "IPV4",
"Addresses": []
},
"ManagedKeysIPV6": {
"IPAddressVersion": "IPV6",
"Addresses": []
}
}
Code language: Bash (bash)
EIPが消えました。
これでEIPはレートベースルールの対象外となったということです。
最後にもう一度CloudFrontにアクセスします。
sh-4.2$ curl https://d1suf56e055zgg.cloudfront.net
<html>
<head></head>
<body>
<h1>index.html</h1>
<p>soa-05-002</p>
</body>
</html>
Code language: Bash (bash)
index.hmlが返ってきました。
レートベースのルールによる制限が解除されたため、アクセス可能な状態に戻ったということです。
まとめ
WAF Web ACLでレートベースのルールを作成する方法を確認しました。