LoginSignup
2
0

More than 3 years have passed since last update.

AWS CloudFormationでWAFを設定したELBを構築しよう

Posted at

はじめに

AWS CloudFormationを利用してVPC構築のテンプレートのサンプルです。

テンプレートの概要が分からない場合は、はじめてのAWS CloudFormationテンプレートを理解するを参考にしてください。

コードはGitHubにもあります。

今回は、akane というシステムの dev 環境を想定しています。
同じ構成で違う環境を作成する場合は、{環境名}-parameters.jsonを別途作成します。

ディレクトリ構成
    akane (システム)
      ├── network (スタック)
      │   ├── dev-parameters.json (dev 環境のパラメータ)
      │   └── network.yml (CFnテンプレート)
      ├── sg (スタック)
      │   ├── dev-parameters.json (dev 環境のパラメータ)
      │   └── sg.yml (CFnテンプレート)
      ├── elb (スタック)
      │   ├── dev-parameters.json (dev 環境のパラメータ)
      │   └── elb.yml (CFnテンプレート)
      └── waf (スタック)
          ├── dev-parameters.json (dev 環境のパラメータ)
          └── waf.yml (CFnテンプレート)

AWS リソース構築内容

  1. networkスタック

    • VPC (10.0.0.0/16)
    • Publicサブネット1 (10.0.1.0/24)
    • Publicサブネット2 (10.0.2.0/24)
    • インターネットゲートウェイ
    • Publicルートテーブル
  2. sgスタック

    • セキュリティグループ(80ポートインバウンド許可)
  3. elbスタック

    • ELB
    • リスナー (80ポート)
    • ターゲットグループ (HealthCheckPathは/healthcheckとしています)
  4. wafスタック

    • ルールグループ(SQLインジェクション、XSS、ヘッダートークン認証[x-akane-id])
    • ウェブACL

実行環境の準備

AWS CloudFormationを動かすためのAWS CLIの設定を参考にしてください。

AWS リソース構築手順

  1. 下記を実行してスタックを作成

    ./create_stacks.sh
    
  2. 下記を実行してスタックを削除

    ./delete_stacks.sh
    

構築テンプレート

1. networkスタック

network.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Network For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  VPCCidrBlock:
    Type: String
    AllowedPattern: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/16
  PublicSubnet1:
    Type: String
    AllowedPattern: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/24
  PublicSubnet2:
    Type: String
    AllowedPattern: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/24

Mappings: 
  AzMap: 
    ap-northeast-1:
      1st: ap-northeast-1a
      2nd: ap-northeast-1c
      3rd: ap-northeast-1d

# Conditions

# Transform

Resources:
  # VPC作成
  akaneVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-vpc
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType

###########################################################################
# Publicサブネット関連
###########################################################################
  # Publicサブネット作成
  akanePublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref akaneVPC
      CidrBlock: !Ref PublicSubnet1
      AvailabilityZone: !FindInMap [AzMap, !Ref AWS::Region, 1st]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-public-subnet1
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akanePublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref akaneVPC
      CidrBlock: !Ref PublicSubnet2
      AvailabilityZone: !FindInMap [AzMap, !Ref AWS::Region, 2nd]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-public-subnet2
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # インターネットゲートウェイ作成
  akaneInternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-igw
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # インターネットゲートウェイをVPCにアタッチ
  akaneVPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref akaneVPC
      InternetGatewayId: !Ref akaneInternetGateway
  # Publicルートテーブル作成
  akanePublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref akaneVPC
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-public-route
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # Publicルートテーブル サブネット関連付け
  akanePublicSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref akanePublicSubnet1
      RouteTableId: !Ref akanePublicRouteTable
  akanePublicSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref akanePublicSubnet2
      RouteTableId: !Ref akanePublicRouteTable
  # Publicルートテーブル ルートにインターネットゲートウェイを関連付ける
  akanePublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref akanePublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref akaneInternetGateway

Outputs:
  akaneVPC:
    Value: !Ref akaneVPC
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-vpc
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
  akanePublicSubnet1:
    Value: !Ref akanePublicSubnet1
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-public-subnet1
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
  akanePublicSubnet2:
    Value: !Ref akanePublicSubnet2
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-public-subnet2
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
  akanePublicRouteTable:
    Value: !Ref akanePublicRouteTable
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-public-route-table
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "VPCCidrBlock",
            "ParameterValue": "10.0.0.0/16"
        },
        {
            "ParameterKey": "PublicSubnet1",
            "ParameterValue": "10.0.0.0/24"
        },
        {
            "ParameterKey": "PublicSubnet2",
            "ParameterValue": "10.0.1.0/24"
        }
    ]
}

2. セキュリティグループスタック

sg.yml
AWSTemplateFormatVersion: 2010-09-09
Description: SG For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [dev, stg, prod]
    ConstraintDescription: must specify dev, stg or prod.

Mappings: 
  AzMap: 
    ap-northeast-1:
      1st: ap-northeast-1a
      2nd: ap-northeast-1d
      3rd: ap-northeast-1c

# Conditions

# Transform

Resources:
  # ELBのSG(セキュリティグループ)作成
  akaneSecurityGroupELB:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: !Sub
        - ${SystemName}-${EnvType}-sg-elb
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      GroupName: !Sub
        - ${SystemName}-${EnvType}-sg-elb
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      SecurityGroupIngress: 
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      Tags: 
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-sg-elb
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      VpcId:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-vpc
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}

Outputs:
  akaneSecurityGroupELB:
    Value: !Ref akaneSecurityGroupELB
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-sg-elb
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        }
    ]
}

3. ELBスタック

elb.yml
AWSTemplateFormatVersion: 2010-09-09
Description: ELB For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [dev, stg, prod]
    ConstraintDescription: must specify dev stg or prod.
  TargetGroupHealthCheckPath:
    Type: String

Mappings: 
  AzMap: 
    ap-northeast-1:
      1st: ap-northeast-1a
      2nd: ap-northeast-1c
      3nd: ap-northeast-1d

# Conditions

# Transform

Resources:
  # ELB作成
  akaneElasticLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties: 
      IpAddressType: ipv4
      Name: !Sub
        - ${SystemName}-${EnvType}-elb
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Scheme: internet-facing
      SecurityGroups: 
        - Fn::ImportValue: !Sub
            - ${SystemName}-${EnvType}-sg-elb
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Subnets: 
        - Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-public-subnet1
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-public-subnet2
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags: 
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-elb
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Type: application

  # TargetGroup作成
  akaneTargetGroup80:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckEnabled: true
      HealthCheckIntervalSeconds: 15
      HealthCheckPath: !Ref TargetGroupHealthCheckPath
      HealthCheckPort: traffic-port
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 5
      Name: !Sub
        - ${SystemName}-${EnvType}-tg-80
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Port: 80
      Protocol: HTTP
      Tags: 
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-tg-80
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      TargetType: instance
      UnhealthyThresholdCount: 2
      VpcId:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-vpc
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}

  # ELB リスナー80作成
  akaneELBListener80:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions: 
        - Type: forward
          TargetGroupArn: !Ref akaneTargetGroup80
      LoadBalancerArn: !Ref akaneElasticLoadBalancer
      Port: 80
      Protocol: HTTP

Outputs:
  akaneElasticLoadBalancer:
    Value: !Ref akaneElasticLoadBalancer
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-elb
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "TargetGroupHealthCheckPath",
            "ParameterValue": "/healthcheck"
        }
    ]
}

4. WAFスタック

waf.yml
AWSTemplateFormatVersion: 2010-09-09
Description: WAF For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [dev, stg, prod]
    ConstraintDescription: must specify dev, stg or prod.
  WAFHeaderName:
    Type: String
  WAFHeaderToken:
    Type: String

Mappings: 
  AzMap: 
    ap-northeast-1:
      1st: ap-northeast-1a
      2nd: ap-northeast-1d
      3rd: ap-northeast-1c

# Conditions

# Transform

Resources:
  # WAF RuleGroupを作成
  akaneWAFRuleGroup:
    Type: AWS::WAFv2::RuleGroup
    Properties: 
      Capacity: 1500
      Description: !Sub
        - ${SystemName}-${EnvType}-waf-rule-group
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Name: !Sub
        - ${SystemName}-${EnvType}-waf-rule-group
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Rules:
        - Name: !Sub
            - ${SystemName}-${EnvType}-waf-rule-sqli
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Priority: 0
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: !Sub
              - ${SystemName}-${EnvType}-waf-rule-sqli
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Statement:
            OrStatement:
              Statements:
                - SqliMatchStatement:
                    FieldToMatch:
                      UriPath: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
                - SqliMatchStatement:
                    FieldToMatch:
                      QueryString: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
                - SqliMatchStatement:
                    FieldToMatch:
                      Body: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
        - Name: !Sub
            - ${SystemName}-${EnvType}-waf-rule-xss
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Priority: 1
          Action:
            Block: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: !Sub
              - ${SystemName}-${EnvType}-waf-rule-xss
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Statement:
            OrStatement:
              Statements:
                - XssMatchStatement:
                    FieldToMatch:
                      UriPath: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
                - XssMatchStatement:
                    FieldToMatch:
                      QueryString: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
                - XssMatchStatement:
                    FieldToMatch:
                      Body: {}
                    TextTransformations:
                      - Priority: 0
                        Type: NONE
        - Name: !Sub
            - ${SystemName}-${EnvType}-waf-rule-header
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Priority: 2
          Action:
            Allow: {}
          VisibilityConfig:
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: !Sub
              - ${SystemName}-${EnvType}-waf-rule-header
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          Statement:
            ByteMatchStatement:
              FieldToMatch:
                SingleHeader:
                  Name: !Ref WAFHeaderName
              PositionalConstraint: EXACTLY
              SearchString: !Ref WAFHeaderToken
              TextTransformations:
                - Priority: 0
                  Type: NONE        
      Scope: REGIONAL
      Tags: 
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-waf-rule-group
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      VisibilityConfig: 
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: !Sub
          - ${SystemName}-${EnvType}-waf-rule-group
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}

  # WAF WebACLを作成
  akaneWAFWebACL:
    Type: AWS::WAFv2::WebACL
    Properties:
      DefaultAction:
        Allow: {}
      Description: !Sub
        - ${SystemName}-${EnvType}-waf-web-acl
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Name: !Sub
        - ${SystemName}-${EnvType}-waf-web-acl
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Rules:
        - Name: !Sub
            - ${SystemName}-${EnvType}-waf-web-acl-rule
            - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
          OverrideAction:
            None: {}
          Statement:
            RuleGroupReferenceStatement: 
              Arn: !GetAtt akaneWAFRuleGroup.Arn
          Priority: 0
          VisibilityConfig: 
            SampledRequestsEnabled: true
            CloudWatchMetricsEnabled: true
            MetricName: !Sub
              - ${SystemName}-${EnvType}-waf-web-acl-rule
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Scope: REGIONAL
      Tags: 
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-waf-web-acl
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      VisibilityConfig: 
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: !Sub
          - ${SystemName}-${EnvType}-waf-web-acl
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}

  # WAF WebACLをELBにを関連付ける
  akaneWebACLAssociation:
    Type: AWS::WAFv2::WebACLAssociation
    Properties: 
      ResourceArn:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-elb
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      WebACLArn: !GetAtt akaneWAFWebACL.Arn         

# Outputs:
dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "WAFHeaderName",
            "ParameterValue": "x-akane-id"
        },
        {
            "ParameterKey": "WAFHeaderToken",
            "ParameterValue": "4HGVKikFK4EBHHDiBFmW-mKVxHZAt9hTsJyCCXz3M"
        }
    ]
}

5. 実行ファイル

create_stacks.sh
#!/bin/sh
SYSTEM_NAME=akane
ENV_TYPE=dev

function create_stack() {
STACK_NAME=$1
aws cloudformation create-stack \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME} \
--template-body file://./${SYSTEM_NAME}/${STACK_NAME}/${STACK_NAME}.yml \
--cli-input-json file://./${SYSTEM_NAME}/${STACK_NAME}/${ENV_TYPE}-parameters.json

aws cloudformation wait stack-create-complete \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

create_stack network
create_stack sg
create_stack elb
create_stack waf
delete_stacks.sh
#!/bin/sh
SYSTEM_NAME=akane
ENV_TYPE=dev

function delete_stack() {
STACK_NAME=$1
aws cloudformation delete-stack \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}

aws cloudformation wait stack-delete-complete \
--stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

delete_stack waf
delete_stack elb
delete_stack sg
delete_stack network
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0