AWS WAFの検証環境をCloudFormationで作ってみた

AWS WAFの検証環境をCloudFormationで作ってみた

Clock Icon2025.04.16

はじめに

こんにちは。クラウド事業本部の戸川です。

皆さんAWS WAFは使っていますか?
AWS WAFはELBやCloudFront、API Gateway、AppSyncなどのサービスの前面に立ってHTTPリクエストを監視してくれるWeb Application Firewallです。

非常に頼もしいAWS WAFですが、ELBやWebアプリなどのリソースと紐づくサービスなので、ちょっとした検証を行う為に環境を用意したり消したりするのが面倒だと感じました。
そんな私の思いに共感してくださる方も世の中にはきっといらっしゃると思い、AWS WAF検証用の環境を作成するCloudFormationテンプレートを作成しました。
ぜひ使ってやってください。

環境構成図

今回作成する環境は以下になります。
devio_20250416_diagram

CloudFormationテンプレート

作成したテンプレートは以下になります。
AWS WAf Web AclにはAWSManagedRulesCommonRuleSetを設定しました。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  Prefix:
    Type: String
    Default: test
    Description: "Fill in the name of the system name."
  Env:
    Type: String
    Default: develop
    Description: "Fill in the name of the environment."
  Scope:
    Type: String
    Default: REGIONAL
    AllowedValues:
      - REGIONAL
      - CLOUDFRONT
    Description: "Select in the scope of waf(REGIONAL or CLOUDFRONT)"
  InstanceType:
    Type: String
    Default: t2.micro
    Description: EC2 instance type

Resources:
  # ------------------------------------------------------------#
  # VPC
  # ------------------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    DeletionPolicy: Delete
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-vpc

  # Public Subnets
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-public-subnet-1

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: !Select [1, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-public-subnet-2

  # Private Subnets
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.11.0/24
      AvailabilityZone: !Select [0, !GetAZs ""]
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-private-subnet-1

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DeletionPolicy: Delete
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-igw

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  # NAT Gateway
  NatGatewayEIP:
    Type: AWS::EC2::EIP
    DeletionPolicy: Delete
    DependsOn: AttachGateway
    Properties:
      Domain: vpc

  NatGateway:
    Type: AWS::EC2::NatGateway
    DeletionPolicy: Delete
    Properties:
      AllocationId: !GetAtt NatGatewayEIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-nat-gw

  # Route Tables
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-public-rt

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    DeletionPolicy: Delete
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-private-rt

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    DeletionPolicy: Delete
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateRoute:
    Type: AWS::EC2::Route
    DeletionPolicy: Delete
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    DeletionPolicy: Delete
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  # ------------------------------------------------------------#
  # Security Groups
  # ------------------------------------------------------------#
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DeletionPolicy: Delete
    Properties:
      GroupDescription: Security group for ALB
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: "Name"
          Value: !Sub "${Prefix}-alb-sg"

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    DeletionPolicy: Delete
    Properties:
      GroupDescription: Security group for EC2 instances
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: 0.0.0.0/0

  # ------------------------------------------------------------#
  # ALB
  # ------------------------------------------------------------#
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    DeletionPolicy: Delete
    DependsOn:
      - PublicSubnet1RouteTableAssociation
      - PublicSubnet2RouteTableAssociation
      - AttachGateway
    Properties:
      Scheme: internet-facing
      LoadBalancerAttributes:
        - Key: idle_timeout.timeout_seconds
          Value: '60'
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-alb

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DeletionPolicy: Delete
    DependsOn: EC2Instance
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 5
      VpcId: !Ref VPC
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: '300'
      Targets:
        - Id: !Ref EC2Instance
          Port: 80
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-tg

  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DeletionPolicy: Delete
    DependsOn: ALBTargetGroup
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: 80
      Protocol: HTTP

  # ------------------------------------------------------------#
  # EC2 Instance
  # ------------------------------------------------------------#
  EC2Instance:
    Type: AWS::EC2::Instance
    DeletionPolicy: Delete
    Properties:
      InstanceType: !Ref InstanceType
      ImageId: ami-05506fa68391b4cb1
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      SubnetId: !Ref PrivateSubnet1
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          dnf update -y
          dnf install -y httpd
          systemctl start httpd
          systemctl enable httpd
          systemctl restart httpd
          systemctl status httpd >> /var/log/user-data.log 2>&1
      Tags:
        - Key: Name
          Value: !Sub ${Env}-${Prefix}-ec2

# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
  S3BucketForWaflog:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub aws-waf-logs-${Env}-${Prefix}-${AWS::AccountId}
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
  S3BucketForAthenaQuery:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    Properties:
      BucketName: !Sub athena-query-results-${Env}-${Prefix}-${AWS::AccountId}
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

# ------------------------------------------------------------#
# WAF v2
# ------------------------------------------------------------#
  WebAcl:
    Type: AWS::WAFv2::WebACL
    DeletionPolicy: Delete
    Properties:
      Name: !Sub ${Env}-${Prefix}-web-acl
      Scope: !Ref Scope
      DefaultAction:
        Allow: {}
      VisibilityConfig:
        CloudWatchMetricsEnabled: true
        SampledRequestsEnabled: true
        MetricName: !Sub ${Env}-${Prefix}-web-acl
      Rules:
        -
          Name: AWS-AWSManagedRulesCommonRuleSet
          Priority: 1
          Statement:
            ManagedRuleGroupStatement:
              VendorName: AWS
              Name: AWSManagedRulesCommonRuleSet
              ExcludedRules:
                - Name: NoUserAgent_HEADER
                - Name: UserAgent_BadBots_HEADER
                - Name: SizeRestrictions_QUERYSTRING
                - Name: SizeRestrictions_Cookie_HEADER
                - Name: SizeRestrictions_BODY
                - Name: SizeRestrictions_URIPATH
                - Name: EC2MetaDataSSRF_BODY
                - Name: EC2MetaDataSSRF_COOKIE
                - Name: EC2MetaDataSSRF_URIPATH
                - Name: EC2MetaDataSSRF_QUERYARGUMENTS
                - Name: GenericLFI_QUERYARGUMENTS
                - Name: GenericLFI_URIPATH
                - Name: GenericLFI_BODY
                - Name: RestrictedExtensions_URIPATH
                - Name: RestrictedExtensions_QUERYARGUMENTS
                - Name: GenericRFI_QUERYARGUMENTS
                - Name: GenericRFI_BODY
                - Name: GenericRFI_URIPATH
                - Name: CrossSiteScripting_COOKIE
                - Name: CrossSiteScripting_QUERYARGUMENTS
                - Name: CrossSiteScripting_BODY
                - Name: CrossSiteScripting_URIPATH
          OverrideAction:
            Count: {}
          VisibilityConfig:
            CloudWatchMetricsEnabled: true
            SampledRequestsEnabled: true
            MetricName: AWS-AWSManagedRulesCommonRuleSet
  WAFLogConfig:
    Type: AWS::WAFv2::LoggingConfiguration
    DeletionPolicy: Delete
    Properties:
      LogDestinationConfigs:
        - !GetAtt S3BucketForWaflog.Arn
      ResourceArn: !GetAtt WebAcl.Arn
  WebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    DeletionPolicy: Delete
    Properties:
      ResourceArn: !GetAtt ApplicationLoadBalancer.LoadBalancerArn
      WebACLArn: !GetAtt WebAcl.Arn

動作確認

Allow

作成されたALBのDNSに向けてブラウザからアクセスしてみましたが、きちんと表示されていますね!
itworks

Block

AWSManagedRulesCommonRuleSetに引っかかりそうなリクエストを投げたところ、しっかり検知してくれています!
waf_samplerequest

最後に

今回はAWS WAF検証環境を作成するCloudFormationテンプレートを作成しました。
この記事が少しでもどなたかのお役に立てば幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.