CloudformationにてAWS WAF マネージドルールの例外条件を設定する

# はじめに
CloudformationにてAWS WAFを構築した際にマネージドルールの例外条件を設定する機会がありまして、せっかくなので記事に残そうと思い執筆します。
普段はNoUserAgent_Headerの通信はAWS WAFにて遮断するも、特定のURIを持つ通信だけは例外的に通信を許可するという設定を行う必要性があり、こちらの例外処理をCloudformationにて作成しました。

# 目次
1. WAFとは
2. WAFのマネージドルール
3. 例外処理の設定
4. Cloudformationにて記載
5. 参考資料

# AWS WAFとは
まずは、AWS WAFとは何かを記載するところからはじめます。AWS WAFとは、AWSが提供しているクラウド型のWebアプリケーションファイアウォールサービスです。Webアプリケーションに対する様々な攻撃を検知し、防御するためのサービスとなります。ALBやCloudFrontなどに紐づけてインターネットからの通信で不正な検知し遮断します。

<img width="400" alt="mojikyo45_640-2.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1717761/1ffda433-43dc-8eb9-8f60-d87f846d6ffe.jpeg">


# WAFのマネージドルール
AWS WAFではルールを設定し遮断します。自分でカスタマイズすることもできますが、AWSが提供するマネージドルールを利用することもできます。マネージドルールとは、AWSが定期的に更新する、一般的なWeb攻撃に対するルールの集合です。マネージドルールを利用することで、WAFのルールの作成や管理の手間を省くことができます。例えば、マネージドルールには、以下のような種類があります。

* AWSManagedRulesCommonRuleSet: 一般的なWeb攻撃に対する基本的なルールセット
* AWSManagedRulesAmazonIpReputationList: AWSが管理するIPリピュテーションリストに基づいて、不正なアクセスをブロックするルールセット
* AWSManagedRulesKnownBadInputsRuleSet: SQLインジェクションやクロスサイトスクリプティングなどの既知の悪意のある入力をブロックするルールセット
* AWSManagedRulesAnonymousIpList: 匿名化されたIPアドレスからのアクセスをブロックするルールセット
* AWSManagedRulesBotControlRuleSet: ボットやスクレイピングなどの自動化されたアクセスをブロックするルールセット

このようなルールセットの中に複数のルールがあり、それぞれのルールを元に通信の検知を行っています。


# 例外処理の設定
マネージドルールを利用する場合でも、特定の条件下でルールをカスタマイズでき例外処理を設定できます。今回は、AWSManagedRulesCommonRuleSetのうち、NoUserAgent_HEADERというHTTPリクエストのヘッダ「User-Agent」の項目が設定されていない場合にアクセスを遮断するルールにて例外条件を設定します。
User-Agentという項目は、リクエストを送信したブラウザやアプリケーションの種類やバージョンなどを識別するために使われており、User-Agentがないリクエストは、不正なアクセスや攻撃の可能性が高いと考えられるため、NoUserAgent_HEADERというルールでブロックされます。
ただし、特定のプログラム処理において、意図的にUser-Agentがないリクエストを送信する仕組みがある場合、その通信も誤って誤検知しWAFが遮断することもあり、例外処理を設定する必要があります。


具体的に例外処理を行うときの仕組みは以下の通りです。
1. 例外条件を適用したいルールについてはWAFでの検知時のアクションをBLOCKからCOUNTへ変更
2. COUNTモードのルールで検知された通信は、遮断しないが検知されたことを表す「ラベル」が付与
    * 今回のラベルは `awswaf:managed:aws:core-rule-set:NoUserAgent_Header` となる
3. COUNTモードのラベルが付与された通信に対して、別途カスタムルールを作成し以下のように条件を設定
    * 条件1： ラベル `awswaf:managed:aws:core-rule-set:NoUserAgent_Header` が付与されている
    * 条件2： 対象URIパスが/hogehoge/で始まるもの以外 (NOT条件)

この設定によって、以下のように振る舞いが変わります。
* 対象URIパスが/hogehoge/で始まるもの　　 → ブロックされない
* 対象URIパスが/hogehoge/で始まるもの以外 → ブロックされる

図で表すと以下の様になります。

<img width="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1717761/aa1bd4bf-7b31-4f7e-9d99-4f1164fbbb46.png">



# Cloudformationにて記載
ここから例外処理の内容をCloudformationにて記述していきます。

まずは、マネージドルールについて抜粋して記述していきます。
Statementを記載する際、`ExcludedRules`にて`NoUserAgent_HEADER`を指定します。
この記述でNoUserAgent_HEADERのルールがOverride to COUNTとなりカウントモードに変更されます。

```yaml
# Web ACL ルール設定
Rules:
  # AWSマネージドルール(ウェブアプリケーションにとっての一般的な脅威をブロック)
  - Name: AWS-AWSManagedRulesCommonRuleSet
    Priority: 1
    Statement:
      ManagedRuleGroupStatement:
        VendorName: AWS
        Name: AWSManagedRulesCommonRuleSet
        # NoUserAgent_HEADERをカウントモードへ変更
        ExcludedRules:
          - Name: NoUserAgent_HEADER
    OverrideAction:
      None: {}
    VisibilityConfig:
      CloudWatchMetricsEnabled: true
      SampledRequestsEnabled: true
      MetricName: AWS-AWSManagedRulesCommonRuleSet
```

次にCOUNTモードのラベルが付与された通信に対して、別途カスタムルールを作成します。
`AndStatement`で2つの条件を組み合わせたルールを作成します。
`LabelMatchStatement`の条件にてScopeをLABELに設定し、`awswaf:managed:aws:core-rule-set:NoUserAgent_Header`のラベルを持つ通信を検知するようにします。
NotStatementの条件にてラベルが付与されている通信のうち、`/hogehoge/`の通信を持たない通信はAction Blockで遮断するようにします。
ActionをBlockにしたことによって、対象URIパスが/hogehoge/で始まるもの以外はブロックされる仕組みになります。

```yaml
Rules:
  # カスタムルール(NoUserAgent_HEADERの通信のうち、/hogehoge/hoge.pyのURIを持つもののみアクセス許可)
  # NoUserAgent_Headerのラベルを持つ通信のうち、/hogehoge/hoge.pyのURI以外を全てブロックする
  - Name: NoUserAgentHeaderCustomeRule
    Priority: 2
    Statement:
      AndStatement:
        Statements:
          # awswaf:managed:aws:core-rule-set:NoUserAgent_Headerのラベルを持つ通信を検知する条件
          - LabelMatchStatement:
              Scope: LABEL
              Key: awswaf:managed:aws:core-rule-set:NoUserAgent_Header
          # ラベルを持つ通信のうち/hogehoge/のURIを持たないものを検知する条件
          - NotStatement:
              Statement:
                ByteMatchStatement:
                  SearchString: /hogehoge/
                  FieldToMatch:
                    UriPath: {}
                  TextTransformations:
                    - Priority: 0
                      Type: NONE
                  PositionalConstraint: STARTS_WITH
```

最後にAWS WAFに関するCloudformationの全体像を記述します。
なお、今回は例外処理を記載するための最低限の記述となりまして、この内容でセキュリティが担保されるものではないことをご了承ください。

```yaml
# ------------------------------------------------------------#
# WAF (Web ACL) 作成
# 
# ------------------------------------------------------------#
AWSTemplateFormatVersion: "2010-09-09"
Description: Waf Rule Create.

Resources:
# WAF (Web ACL)を作成
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: custome-waf
      Scope: REGIONAL
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        SampledRequestsEnabled: true
        MetricName: !Sub custome-waf
      # Web ACL ルール設定
      Rules:
        # AWSマネージドルール(ウェブアプリケーションにとっての一般的な脅威をブロック)
        - Name: AWS-AWSManagedRulesCommonRuleSet
          Priority: 1
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
              # NoUserAgent_HEADERをカウントモードへ変更
              ExcludedRules:
                - Name: NoUserAgent_HEADER
          OverrideAction:
            None: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            SampledRequestsEnabled: true
            MetricName: AWS-AWSManagedRulesCommonRuleSet
        # カスタムルール(NoUserAgent_HEADERの通信のうち、/hogehoge/のURIを持つもののみアクセス許可)
        # NoUserAgent_Headerのラベルを持つ通信のうち、/hogehoge/のURI以外を全てブロックする
        - Name: NoUserAgentHeaderCustomeRule
          Priority: 2
          Statement:
            AndStatement:
              Statements:
                # awswaf:managed:aws:core-rule-set:NoUserAgent_Headerのラベルを持つ通信を検知する条件
                - LabelMatchStatement:
                    Scope: LABEL
                    Key: awswaf:managed:aws:core-rule-set:NoUserAgent_Header
                # ラベルを持つ通信のうち/hogehoge/のURIを持たないものを検知する条件
                - NotStatement:
                    Statement:
                      ByteMatchStatement:
                        SearchString: /hogehoge/
                        FieldToMatch:
                          UriPath: {}
                        TextTransformations:
                          - Priority: 0
                            Type: NONE
                        PositionalConstraint: STARTS_WITH
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: NoUserAgentHeaderCustomeRule
```



# 参考資料
今回の例外処理の設定、Cloudformationを記載するにあたり以下の記事が大変参考になりました。
ありがとうございました。
[[AWS WAF] マネージドルールグループに対して例外条件を設定する](https://dev.classmethod.jp/articles/aws-waf-managed-rule-group-exception-conditions/)