WAFのレートベースルールでリソースを保護する

目次

WAFのレートベースルールでリソース(CloudFront)を保護する

AWS SOAの出題範囲の1つでもある、ネットワークとコンテンツ配信に関する内容です。

WAF Web ACLでは、様々なルールを定義することができますが、その1つにレートベースのルールがあります。

レートベースのルールは、各発生元 IP アドレスからのリクエストのレートを追跡し、レートが制限を超えると、IP に対するルールアクションをトリガーします。制限は、5 分間あたりのリクエスト数として設定します。

レートベースのルールステートメント

レートベースのルールを作成することによって、DoS攻撃からリソースを保護することができます。

今回はCloudFrontをWAFを適用する対象とします。
ただし他のリソース(ALB、API Gateway、AppSync)でも、今回と同様の構成で設定可能です。

構築する環境

Diagram of protect resource with WAF rate-based rules.

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の基本的な事項については、以下のページをご確認ください。

あわせて読みたい
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に適用する...

CloudFormationのカスタムリソースについては、以下のページをご確認ください。

あわせて読みたい
CFNカスタムリソースでS3オブジェクトを作成・削除する 【CloudFormationカスタムリソースを使って、スタック生成/削除時にS3オブジェクトを作成/削除する方法】 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も一緒にインストールされます。

インスタンスの初期化処理の詳細については、以下のページをご確認ください。

あわせて読みたい
Linuxインスタンスの初期化方法4選 【Linuxインスタンスを初期化する4つの方法】 EC2インスタンスの起動時に初期化処理を実行する方法を考えます。 EC2インスタンスを構築時に初期化する以下の4つの手法を...

環境構築

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

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

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

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【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です。

WAF is applied to CloudFront.

CloudFrontディストリビューションが作成され、AWS WAFが適用されていることがわかります。

次にWeb ACLを確認します。

WAF is applied to CloudFront.

Web ACL側からも、このACLがCloudFrontに適用されていることがわかります。

次にWeb ACL内に作成されたルールを確認します。

Detail of WAF Web ACL.

ルールグループを参照する形で、ルールが定義されています。

ルールの内容も確認します。

Details of WAF rate-based rules.

レートベースのルールで、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に関する詳細は、以下のページをご確認ください。

あわせて読みたい
LinuxインスタンスにSSM Session Manager経由でアクセスする 【LinuxインスタンスにSSM Session Manager経由でアクセスする】 EC2インスタンスにSSM Session Manager経由でアクセスする構成を確認します。 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の状況を確認します。

Traffic from the EIP is allowed.

NATゲートウェイにアタッチされたEIPからのリクエストが許可されています。
今の時点では、レートベースのルールで制限が有効化されていないため、通常通りにアクセスが可能ということです。

次にApache Benchを使用して、リクエスト数の急増を再現します。

sh-4.2$ ab -n 100 https://d1suf56e055zgg.cloudfront.net/Code language: JavaScript (javascript)

CloudFrontディストリビューションのURLに対して、100回リクエストを実施します。

WAFの状況を確認します。

Traffic is allowed from EIPs generated by Apache bench.

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も確認します。

Traffic from EIP is blocked.

今度はブロックされたことがわかります。
レートベースルールによって、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でレートベースのルールを作成する方法を確認しました。

目次