AWS Managed Rules for AWS WAF を構成するCloudFormationテンプレートを作ってみた

AWS Managed Rules for AWS WAF をデプロイする CloudFormation テンプレートを作成してみました。
2019.12.19

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。 ご機嫌いかがでしょうか。 "No human labor is no human error" が大好きな吉井 亮です。

2019年11月25日(アメリカ時間) に AWS Managed Rules for AWS WAF が公開されています。 AWS WAF 導入へのハードルがかなり低くなってきたと感じています。 弊社カジもアップデート記事を書いています。

[アップデート] AWS WAF向けAWS製のManaged Rulesが出ました

AWS Managed Rules for AWS WAF をデプロイする CloudFormation テンプレートを作成してみました。 このテンプレートを実行すると以下のようなリソースがデプロイされます。

ルール

テンプレートを実行するとこのようなルールが入ります。

ルール名 アクション 優先度
Custom-IPaddress-BlackList Count 0
AWS-AWSManagedRulesAmazonIpReputationList Count 1
AWS-AWSManagedRulesCommonRuleSet Count 2
AWS-AWSManagedRulesKnownBadInputsRuleSet Count 3
AWS-AWSManagedRulesLinuxRuleSet Count 4
AWS-AWSManagedRulesUnixRuleSet Count 5
Custom-Ratebased Count 6

Custom-IPaddress-BlackList

接続を拒否する IP アドレスを指定します。 これはマネージドルールではなくユーザー管理のルールです。

AWS-AWSManagedRulesAmazonIpReputationList

通常ボットやその他の脅威に関連付けられている IP アドレスをブロックする場合に役立ちます。 ここの IP アドレスは AWS 管理です。

AWS-AWSManagedRulesCommonRuleSet

Web アプリケーション防御の一般的なルールが含まれています。 OWASP 出版物や CVE で解説されている脆弱性を含んでいます。 AWS 管理のルールです。

AWS-AWSManagedRulesKnownBadInputsRuleSet

脆弱性や悪用のあるパターンに報告されているインプットをブロックするルールが含まれています。 AWS 管理のルールです。

AWS-AWSManagedRulesLinuxRuleSet

Linux 固有の LFI 攻撃を防ぐルールが含まれています。 AWS 管理のルールです。 次の AWS-AWSManagedRulesUnixRuleSet と組み合わせて使用します。

AWS-AWSManagedRulesUnixRuleSet

POSIX 固有の LFI 攻撃を防ぐルールが含まれています。 AWS 管理のルールです。

Custom-Ratebased

5分間で100リクエスト (調整可能) 以上送信してくる IP アドレスをブロックします。 このルールには IP アドレスホワイトリストが含まれており ホワイトリストに載っている IP アドレスはリクエスト数に関係なく無条件に許可します。 ホワイトリストには自社拠点などを指定します。

やってみた

CloudFormation スタック

本エントリの末に記載した CloudFormation テンプレートをローカルに保存してカスタマイズします。

カスタマイズしたテンプレートを使って CloudFormation スタックを作成します。

説明
スタックの名前 任意の名称
BucketName WAF ログを保存する S3 バケット名。{BucketName}-{アカウントID} になります。
WebACLName WebACL の名称です。任意の名称を指定

WAF v2 ロギング

しばらくするとリソースが作成されます。 マネジメントコンソールで WebACL を開いてみます。 CloudFormation スタックを流したリージョンを選択すると無事に WebACL が作成されていることを確認できると思います。

その WebACL をクリックして、Logging and metrics タブを開きます。 Logging が Disabled になっています。 Enable logging をクリックします。

Amazon Kinesis Data Firehose Delivery Stream のドロップダウンから aws-waf-logs-リージョン-WebACL名称 を選択します。 Redacted Fields を任意で選択します。 Enable logging をクリックします。

関連付け

関連付けをします。 同じく WebACL の画面で Associated AWS resources タブを開きます。

Add AWS Resources をクリックします。

関連付けたいリソースを選択します。 Add をクリックします。

アクションの変更

テンプレートを使って作成した WebACL のルールは アクションがカウントになっています。 この状態で十分なテストを行って過検知がないことの確認や 細かい修正を行ってアクションをブロックなどに変更します。

CloudFormation テンプレート

テンプレートはカスタマイズしてお使いください。

カスタマイズ1

AWS::WAFv2::WebACL のプロパティで scopeREGIONAL になっています。 これは ALB または API Gateway 用のテンプレートです。 CloudFront 用の WebACL を作る場合は CLOUDFRONT へ変更します。

カスタマイズ2

IPWhiteList には無条件で許可する IP アドレスを IPBlackList には接続を拒否する IP アドレスを記述ください

カスタマイズ3

WebACL に付けるルールは要件に合わせて増減してください。 AWS 管理のルール一覧は こちら です。

カスタマイズ4

ルール名 Custom-Ratebased の設定値 limit: 100 を 要件に合わせて調整してください。

AWSTemplateFormatVersion: 2010-09-09
Description: WAF v2 Web ACL

Parameters: 
  WebACLName:
    Description: The Name of WebACL.
    Type: String

  BucketName:
    Type: String
    Description: The name for the bucket.
    Default: waf-logs

Resources: 
### Waf logs bucket
  S3Backet:
    Type: "AWS::S3::Bucket"
    Properties: 
      BucketName: !Sub ${BucketName}-${AWS::AccountId}
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault:
            SSEAlgorithm: AES256 
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

### Firehose
  DeliveryStream:
    Type: "AWS::KinesisFirehose::DeliveryStream"
    Properties:
      DeliveryStreamName: !Sub aws-waf-logs-${AWS::Region}-${WebACLName}
      DeliveryStreamType: DirectPut
      S3DestinationConfiguration:
        BucketARN: !GetAtt S3Backet.Arn
        BufferingHints:
          IntervalInSeconds: 300
          SizeInMBs: 5
        CompressionFormat: GZIP
        RoleARN: !GetAtt FirehoseRole.Arn

### IAM Role for firehose
  FirehoseRole:
    Type: AWS::IAM::Role
    Properties:
      Path: '/'
      AssumeRolePolicyDocument: 
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - firehose.amazonaws.com
            Action:
              - sts:AssumeRole
            Condition:
              StringEquals:
                sts:ExternalId: !Sub ${AWS::AccountId}
      Policies:
        - 
         PolicyName: firehose_delivery_role
         PolicyDocument:
           Version: "2012-10-17"
           Statement:
              - 
                Effect: "Allow"
                Action: 
                  - glue:GetTable
                  - glue:GetTableVersion
                  - glue:GetTableVersions
                Resource: "*"
              -
                Effect: "Allow"
                Action: 
                  - s3:AbortMultipartUpload
                  - s3:GetBucketLocation
                  - s3:GetObject
                  - s3:ListBucket
                  - s3:ListBucketMultipartUploads
                  - s3:PutObject
                Resource:
                  - !Sub arn:aws:s3:::${BucketName}-${AWS::AccountId}
                  - !Sub arn:aws:s3:::${BucketName}-${AWS::AccountId}/*
                  - arn:aws:s3:::%FIREHOSE_BUCKET_NAME%
                  - arn:aws:s3:::%FIREHOSE_BUCKET_NAME%/*
              -
                Effect: "Allow"
                Action: 
                  - lambda:InvokeFunction
                  - lambda:GetFunctionConfiguration
                Resource:
                  - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:%FIREHOSE_DEFAULT_FUNCTION%:%FIREHOSE_DEFAULT_VERSION%
              -
                Effect: "Allow"
                Action: 
                  - kms:GenerateDataKey
                  - kms:Decrypt
                Resource:
                  - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/s3
                Condition:
                  StringEquals:
                    kms:ViaService: !Sub s3.${AWS::Region}.amazonaws.com
                  StringLike:
                    kms:EncryptionContext:aws:s3:arn:
                      - !Sub arn:aws:s3:::${BucketName}-${AWS::AccountId}/*
                      - !Sub arn:aws:s3:::${BucketName}-${AWS::AccountId}/%FIREHOSE_BUCKET_PREFIX%*
              -
                Effect: "Allow"
                Action: 
                  - logs:PutLogEvents
                Resource:
                  - !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kinesisfirehose/aws-waf-logs-${AWS::Region}-${WebACLName}:log-stream:*
              -
                Effect: "Allow"
                Action: 
                  - kinesis:DescribeStream
                  - kinesis:GetShardIterator
                  - kinesis:GetRecords
                Resource:
                  - !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME%
              -
                Effect: "Allow"
                Action: 
                  - kms:Decrypt
                Resource:
                  - !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/%SSE_KEY_ID%
                Condition:
                  StringEquals:
                    kms:ViaService: kinesis.%REGION_NAME%.amazonaws.com
                  StringLike:
                    kms:EncryptionContext:aws:kinesis:arn: !Sub arn:aws:kinesis:%REGION_NAME%:${AWS::AccountId}:stream/%FIREHOSE_STREAM_NAME%

### WAF WebACL
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Ref WebACLName
      DefaultAction: 
        Allow: {}
      Scope: REGIONAL
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        MetricName: !Ref WebACLName
        SampledRequestsEnabled: false
      Rules:
        - 
          Name: AWS-AWSManagedRulesAmazonIpReputationList
          Priority: 1
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesAmazonIpReputationList
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesAmazonIpReputationList
            SampledRequestsEnabled: false
        - 
          Name: AWS-AWSManagedRulesCommonRuleSet
          Priority: 2
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesCommonRuleSet
            SampledRequestsEnabled: false
        - 
          Name: AWS-AWSManagedRulesKnownBadInputsRuleSet
          Priority: 3
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesKnownBadInputsRuleSet
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesKnownBadInputsRuleSet
            SampledRequestsEnabled: false
        - 
          Name: AWS-AWSManagedRulesLinuxRuleSet
          Priority: 4
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesLinuxRuleSet
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesLinuxRuleSet
            SampledRequestsEnabled: false
        - 
          Name: AWS-AWSManagedRulesUnixRuleSet
          Priority: 5
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesUnixRuleSet
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: AWSManagedRulesUnixRuleSet
            SampledRequestsEnabled: false
        - 
          Action: 
            Count: {}
          Name: Custom-IPaddress-BlackList
          Priority: 0
          Statement:
            IPSetReferenceStatement:
              Arn: !GetAtt IPBlackList.Arn
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: Custom-IPaddress-BlackList
            SampledRequestsEnabled: false
        -
          Action: 
            Count: {}
          Name: Custom-Ratebased
          Priority: 6
          Statement:
            RateBasedStatement:
              AggregateKeyType: IP
              Limit: 100
              ScopeDownStatement:
                NotStatement:
                  Statement:
                    IPSetReferenceStatement:
                      Arn: !GetAtt IPWhiteList.Arn
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            MetricName: Custom-Ratebased
            SampledRequestsEnabled: false


  IPWhiteList:
    Type: "AWS::WAFv2::IPSet"
    Properties:
      Name: Custom-ipaddress-whitelist
      Scope: REGIONAL
      IPAddressVersion: IPV4
      Addresses:
        - nnn.nnn.nnn.nnn/32 

  IPBlackList:
    Type: "AWS::WAFv2::IPSet"
    Properties:
      Name: Custom-ipaddress-blacklist
      Scope: REGIONAL
      IPAddressVersion: IPV4
      Addresses:
        - nnn.nnn.nnn.nnn/32

参考

Announcing AWS Managed Rules for AWS WAF AWS Managed Rules for AWS WAF CloudFormation User Guide

以上、吉井 亮 がお届けしました。