はじめに
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 リソース構築内容
-
networkスタック
- VPC (10.0.0.0/16)
- Publicサブネット1 (10.0.1.0/24)
- Publicサブネット2 (10.0.2.0/24)
- インターネットゲートウェイ
- Publicルートテーブル -
sgスタック
- セキュリティグループ(80ポートインバウンド許可) -
elbスタック
- ELB
- リスナー (80ポート)
- ターゲットグループ (HealthCheckPathは/healthcheckとしています) -
wafスタック
- ルールグループ(SQLインジェクション、XSS、ヘッダートークン認証[x-akane-id])
- ウェブACL
実行環境の準備
AWS CloudFormationを動かすためのAWS CLIの設定を参考にしてください。
AWS リソース構築手順
-
下記を実行してスタックを作成
./create_stacks.sh
-
下記を実行してスタックを削除
./delete_stacks.sh
構築テンプレート
1. networkスタック
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}
{
"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. セキュリティグループスタック
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}
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
}
]
}
3. ELBスタック
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}
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
},
{
"ParameterKey": "TargetGroupHealthCheckPath",
"ParameterValue": "/healthcheck"
}
]
}
4. WAFスタック
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:
{
"Parameters": [
{
"ParameterKey": "SystemName",
"ParameterValue": "akane"
},
{
"ParameterKey": "EnvType",
"ParameterValue": "dev"
},
{
"ParameterKey": "WAFHeaderName",
"ParameterValue": "x-akane-id"
},
{
"ParameterKey": "WAFHeaderToken",
"ParameterValue": "4HGVKikFK4EBHHDiBFmW-mKVxHZAt9hTsJyCCXz3M"
}
]
}
5. 実行ファイル
#!/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
#!/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