WAFv2+S3+CloudFrontの構成をCloudFormationでつくる

# はじめに
CloudFrontのWAFv2設定をIaCしたので簡単にまとめてみます。

# つくりかた
①　waf.ymlのスタック作成(下記IaC参照)
②　「出力」からWebACLのARNをコピー
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/09500a44-ca3e-96a4-4a65-163941402df4.png)

③　cloudfront.ymlのスタック作成時にコピーしてきたARNを入力(下記IaC参照)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2889192/6e0ed476-5478-647d-44d0-d7b7d300eb1e.png)


# IaC
```waf.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: WAF for Cloud Front
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String
Resources:
  # ------------------------------------------------------------#
  #  WebACL for CloudFront
  # ------------------------------------------------------------#
  WebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: !Sub "${ProjectName}WebACL"
      Scope: CLOUDFRONT
      DefaultAction:
        Block: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: webaclcloudfront
      Rules:
        - Name: !Sub "${ProjectName}aclrules"
          Priority: 0
          Action:
            Allow: {}
          Statement:
            IPSetReferenceStatement:
              Arn: !GetAtt WAFIPSet.Arn
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: !Sub "${ProjectName}aclrules"

  # ------------------------------------------------------------#
  #  IPSet for WAF
  # ------------------------------------------------------------#
  WAFIPSet:
    Type: AWS::WAFv2::IPSet
    Properties:
      Name: !Sub "${ProjectName}"
      Scope: CLOUDFRONT
      IPAddressVersion: IPV4
      # アクセスを許可するIPアドレスを指定
      Addresses:
        - "--.--.--.--/32"
        - "--.--.--.--/32"
Outputs:
  WebACLArn:
    Value: !GetAtt WebACL.Arn
```

```cloudfront.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create S3 and CloudFront
Parameters:
  ProjectName:
    Description: Type of this ProjectName.
    Type: String
  BucketName:
    Description: Type of this BacketName.
    Type: String
  WebACLArn:
    Type: String

Resources:
  # ------------------------------------------------------------#
  #  S3 Policy for CloudFront
  # ------------------------------------------------------------#
  BucketPolicy:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket: !Ref Bucket
      PolicyDocument:
        Statement:
          - Action:
              - "s3:GetObject"
            Effect: Allow
            Resource: !Sub "arn:aws:s3:::${BucketName}/*"
            Principal:
              AWS: !Sub "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${CloudFrontOriginAccessIdentity}"

  # ------------------------------------------------------------#
  #  S3 Bucket
  # ------------------------------------------------------------#
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${BucketName}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      AccessControl: Private

  # ------------------------------------------------------------#
  #  CloudFront Distribution
  # ------------------------------------------------------------#
  AssetsDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        WebACLId: !Sub ${WebACLArn}
        Origins:
          - Id: S3Origin
            DomainName:
              Fn::GetAtt: Bucket.DomainName
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}"
        Enabled: true
        DefaultRootObject: index.html
        Comment: !Sub ${AWS::StackName} distribution
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ForwardedValues:
            QueryString: false
          ViewerProtocolPolicy: redirect-to-https
        CustomErrorResponses:
          - ErrorCode: 403
            ResponsePagePath: /
            ResponseCode: 200
            ErrorCachingMinTTL: 0
  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub ${AWS::StackName}
```

# つまづいたこと
- WAFv2を利用する際はリージョンをus-east-1(バージニア)に指定する必要があった
    - cloudformationの画面でリージョンを変更してからスタックの作成をする必要がある

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html

- スタックの作成順はWAF→CloudFront
    - WAFにはCloudFrontを参照できるプロパティがないため、先にWAFを作成しCloudFront側から関連付ける必要がある(AWS::CloudFront::DistributionのDistributionConfigで関連付けています)


# おわりに
コンソールで環境を立てた際は難しさを感じなかったのですが、思ったより時間がかかってしまいました。
公式ドキュメントを読みながら必要な項目を確認し、まずは自分で雛形を作ることが大事でした。



# 参考

https://qiita.com/dnpds-yuharagi/items/c41b90a80847cf0272c3

https://dev.classmethod.jp/articles/cloudformation-webacl-cloudfront-error/