こんにちは、AWS 事業本部の平木です!
フィラデルフィアで開催されている AWS re:Inforce 2024 に参加しています。
本記事は AWS re:Inforce 2024 のセッション「Build multilayered network security for Amazon VPC」のセッションレポートです。
セッション概要
このワークショップでは、複数のレイヤーでセキュリティを大規模に構築します。各モジュールは、レイヤーセキュリティを提供するのに役立つ 1 つまたは複数の AWS サービスの実装です。ネットワークレイヤーを作成し、すべてのレイヤーでトラフィックを制御し、ネットワーク保護を自動化し、複数のレイヤーで検査と保護を実装する方法をご覧ください。また、様々な AWS サービスで何ができるのか、それらがどのように連携するのかを学びます。参加にはノートパソコンが必要です。 (Deepl 翻訳)
イントロダクション
このセッションはワークショップとなっており、VPC 内リソースを保護するための様々なセキュリティレイヤーの仕組みについて実際に触ってみながら体感してみるセッションでした。
まずはイントロダクションということで、VPC を保護するリソースとして代表的な Network Firewall と Route 53 Resolver DNS Firewall の解説から行われました。
Network Firewall
Network Firewall はハイアベイラビリティなマネージドサービスであり、きめ細かい制御による柔軟な保護を実現できます。
Network Firewall の機能としては、
- Packet filtering
- Visibility and reporting
- Central management
など豊富な機能を備えているのが特徴的です。
他のセキュリティリソースと比較すると以下のようになります。
セキュリティレイヤーとしては拡張性・柔軟性に優れたサービスと言えます。
ぜひ詳細はこちらもご参照ください。
Route 53 Resolver DNS Firewall
続いては Route 53 Resolver DNS Firewall です。
Route 53 Resolver DNS Firewall は、Route 53 Resolver で機能するサービスであり、DNS トラフィックを制御することができます。
他にもクロスアカウントに集中管理できたり、モニタリングも可能です。
続いてのセッションブログではさらに深く記載しますが、マルウェアの大半が DNS を利用しているため追加のセキュリティレイヤーとしては非常に重要なものになるかと思います。
デプロイモデルとしては以下のようになります。
ハンズオン
解説が終わったところでハンズオンに入ります。
今回ワークショップで実践してみた構成は以下です。
提示されているセキュリティレイヤーは以下となります。
- CloudFront
- AWS WAF
- セキュリティグループ/ネットワーク ACL
- Route 53 Resolver DNS Firewall
- Network Firewall
- Ingress
- Egress
上記から抜粋して、AWS WAF による SQLi やレートベースによるトラフィック防御ができるまでを構築・検証してみます。
環境
環境は以下のような形となります。
VPC 上では ALB,EC2,RDS が稼働しており、ALB をオリジンとして CloudFront でキャッシュ配信、CloudFront に紐づける形で AWS WAF を構築します。
デモ
構築の詳細は一部割愛しますが、オリジンに CloudFront を指定したディストリビューションを作成し、その CloudFront に AWS WAF を紐づけまず以下のマネージドルールを追加して webACL を構築します。
- AWS-AWSManagedRulesCommanRuleSet
- AWS-AWSManagedRulesAmazonIpReputationList
- AWS-AWSManagedRulesSQLiRuleSet
構築が出来たら外部から以下のようなコマンドでクロスサイトスクリプティングを試します。
curl -X POST (CloudFront の DNS) -F "user='<script><alert>Hello</alert></script>'"
すると以下のように拒否されました。
続いて SQL インジェクションも試します。
curl -X POST (CloudFront の DNS) -F "user='OR 1=1;"
同様に拒否されていることが確認できました。
続いては特定のヘッダーがリクエストに含まれていた場合にブロックする挙動を見てみます。
カスタマーマネージドルールを作成し以下のように、X-sampleAttack
というヘッダーが含まれていた場合にブロックするようにします。
作成できたら以下のコマンドを打鍵すると、
curl -H "X-sampleAttack: attack1" "(CloudFront の DNS)"
以下のようにブロックされていることが分かります。
では最後レートベースのルールを作成してみます。
以下の画像のように 500 回リクエストがきた場合にブロックするルールを作成しました。
流量による攻撃を再現するためにloadtestをインストールします。
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash && export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm install 20 && npm install -g loadtest
では実際に 45 秒間の loadtest を実施すると、
loadtest -n 9000 -c 1 --rps 200 (CloudFront の DNS)
以下のようにエラーカウントを返すようになりました。
参考
今回のベースラインとなる環境は以下の CloudFormation テンプレートから構築できます。 CloudFront や WAF,Route 53 Resolver DNS Firewall などは手動作成となりますのでご注意ください。
コードを展開する
Parameters:
###
### Centralized Egress VPC
###
EgressVpcCidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.0.0.0/23
Description: CIDR block for the VPC
Type: String
EgressVpcPrivateSubnet1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.0.0.0/25
Description: CIDR block for the Private Subnet 1 located in AZ 1
Type: String
EgressVpcPublicSubnet1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.0.1.0/25
Description: CIDR block for the Public Subnet 1 located in AZ 1
Type: String
###
### Workload VPC 1
###
WorkloadVpc1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.0.0/23
Description: CIDR block for the VPC
Type: String
WorkloadVpc1PrivateSubnet1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.0.0/26
Description: CIDR block for the Private Subnet 1 located in AZ 1
Type: String
WorkloadVpc1PrivateSubnet2Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.0.64/26
Description: CIDR block for the Private Subnet 1 located in AZ 2
Type: String
WorkloadVpc1DbSubnet1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.0.128/26
Description: CIDR block for the DB subnet located in AZ 1
Type: String
WorkloadVpc1DbSubnet2Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.0.192/26
Description: CIDR block for the DB subnet located in AZ 2
Type: String
WorkloadVpc1PublicSubnet1Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.1.0/25
Description: CIDR block for the Public Subnet 1 located in AZ 1
Type: String
WorkloadVpc1PublicSubnet2Cidr:
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$
Default: 10.1.1.128/25
Description: CIDR block for the Public Subnet 2 located in AZ 2
Type: String
###
### Other Parameters
###
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64
Resources:
###
### TGW
###
TGW:
DependsOn:
- WorkloadVpc1S3Gateway
- WorkloadVpc1Ec2MessagesVpcEndpoint
- WorkloadVpc1SsmMessagesEndpoint
- WorkloadVpc1SsmEndpoint
Type: AWS::EC2::TransitGateway
Properties:
AmazonSideAsn: 65000
Description: TGW-Network-Security
AutoAcceptSharedAttachments: disable
DefaultRouteTableAssociation: disable
DefaultRouteTablePropagation: disable
DnsSupport: enable
VpnEcmpSupport: enable
Tags:
- Key: Name
Value: TGW
TgwMainRouteDomain:
Type: AWS::EC2::TransitGatewayRouteTable
Properties:
Tags:
- Key: Name
Value: Main Route Domain
TransitGatewayId: !Ref TGW
TgwDefaultEgressRoute:
Type: AWS::EC2::TransitGatewayRoute
Properties:
DestinationCidrBlock: 0.0.0.0/0
TransitGatewayAttachmentId: !Ref EgressVpcTgwAttachment
TransitGatewayRouteTableId: !Ref TgwMainRouteDomain
###
### Centralized Egress VPC
###
EgressVpc:
DependsOn: DeleteDefaultVpc
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref EgressVpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: "Egress VPC"
EgressVpcInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "Egress VPC IGW"
EgressVpcInternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref EgressVpc
InternetGatewayId: !Ref EgressVpcInternetGateway
# Public Subnets
EgressVpcPublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref EgressVpcPublicSubnet1Cidr
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ""
VpcId: !Ref EgressVpc
MapPublicIpOnLaunch: true
Tags:
- Key: "Name"
Value: "Egress VPC Public subnet"
EgressVpcPublicSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EgressVpc
Tags:
- Key: "Name"
Value: "Egress VPC Public subnet RTB"
EgressVpcPublicSubnet1Route1:
DependsOn: EgressVpcTgwAttachment
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 10.1.0.0/24
RouteTableId: !Ref EgressVpcPublicSubnet1RouteTable
TransitGatewayId: !Ref TGW
EgressVpcPublicSubnet1Route2:
DependsOn: EgressVpcInternetGateway
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref EgressVpcPublicSubnet1RouteTable
GatewayId: !Ref EgressVpcInternetGateway
EgressVpcPublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref EgressVpcPublicSubnet1RouteTable
SubnetId: !Ref EgressVpcPublicSubnet1
# NAT
EgressVpcNatGwPublicSubnet1:
DependsOn: EgressVpcInternetGatewayAttachment
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !Sub ${EgressVpcNatGwPublicSubnet1EIP.AllocationId}
SubnetId: !Ref EgressVpcPublicSubnet1
Tags:
- Key: "Name"
Value: "Egress VPC NAT Gateway"
EgressVpcNatGwPublicSubnet1EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
# Private Subnets
EgressVpcPrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref EgressVpcPrivateSubnet1Cidr
AvailabilityZone: !Select
- 0
- !GetAZs ''
VpcId: !Ref EgressVpc
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: "Egress VPC Private subnet"
EgressVpcPrivateSubnet1RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EgressVpc
Tags:
- Key: Name
Value: "Egress VPC Private subnet RTB"
EgressVPCPrivateSubnet1Route1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref EgressVpcPrivateSubnet1RouteTable
NatGatewayId: !Ref EgressVpcNatGwPublicSubnet1
EgressVpcPrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref EgressVpcPrivateSubnet1RouteTable
SubnetId: !Ref EgressVpcPrivateSubnet1
EgressVpcTgwAttachment:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
SubnetIds:
- !Ref EgressVpcPrivateSubnet1
Tags:
- Key: Name
Value: "Egress VPC TGW Attachment"
TransitGatewayId: !Ref TGW
VpcId: !Ref EgressVpc
EgressVpcTgWRtbAssociation:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref EgressVpcTgwAttachment
TransitGatewayRouteTableId: !Ref TgwMainRouteDomain
EgressVpcTgWRtbPropagation:
Type: AWS::EC2::TransitGatewayRouteTablePropagation
Properties:
TransitGatewayAttachmentId: !Ref EgressVpcTgwAttachment
TransitGatewayRouteTableId: !Ref TgwMainRouteDomain
###
### Workload VPC 1
###
WorkloadVpc1:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref WorkloadVpc1Cidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: "Workload VPC"
# Public Subnets
WorkloadVpc1PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1PublicSubnet1Cidr
AvailabilityZone: !Select
- 0
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: "Workload VPC Public Subnet 1"
WorkloadVpc1PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1PublicSubnet2Cidr
AvailabilityZone: !Select
- 1
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: "Workload VPC Public Subnet 2"
WorkloadVpc1PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: "Workload VPC Public Subnet RTB"
WorkloadVpcPublicSubnetRoute1:
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref WorkloadVpc1PublicSubnetRouteTable
GatewayId: !Ref WorkloadVpcInternetGateway
WorkloadVpc1PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PublicSubnetRouteTable
SubnetId: !Ref WorkloadVpc1PublicSubnet1
WorkloadVpc1PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PublicSubnetRouteTable
SubnetId: !Ref WorkloadVpc1PublicSubnet2
WorkloadVpc1PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: "Workload VPC Private Subnet RTB"
WorkloadVpc1PrivateSubnetRouteTableEgressRoute:
DependsOn: WorkloadVpc1TgwAttachment
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref WorkloadVpc1PrivateSubnetRouteTable
TransitGatewayId: !Ref TGW
WorkloadVpc1DbSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PrivateSubnetRouteTable
SubnetId: !Ref WorkloadVpc1DbSubnet1
WorkloadVpc1DbSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1DbSubnet1Cidr
AvailabilityZone: !Select
- 0
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "Workload VPC DB Subnet 1"
WorkloadVpc1DbSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PrivateSubnetRouteTable
SubnetId: !Ref WorkloadVpc1DbSubnet2
WorkloadVpc1DbSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1DbSubnet2Cidr
AvailabilityZone: !Select
- 1
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "Workload VPC DB Subnet 2"
WorkloadVpc1PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PrivateSubnetRouteTable
SubnetId: !Ref WorkloadVpc1PrivateSubnet1
WorkloadVpc1PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1PrivateSubnet1Cidr
AvailabilityZone: !Select
- 0
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "Workload VPC Private Subnet 1"
WorkloadVpc1PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref WorkloadVpc1PrivateSubnetRouteTable
SubnetId: !Ref WorkloadVpc1PrivateSubnet2
WorkloadVpc1PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: !Ref WorkloadVpc1PrivateSubnet2Cidr
AvailabilityZone: !Select
- 1
- !GetAZs ''
VpcId: !Ref WorkloadVpc1
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: "Workload VPC Private Subnet 2"
WorkloadVpc1EndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for SSM endpoints
GroupName: Centralized SSM VPC Endpoints SG
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.1.0.0/24
VpcId: !Ref WorkloadVpc1
WorkloadVpc1SsmEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref WorkloadVpc1EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
SubnetIds:
- !Ref WorkloadVpc1PrivateSubnet1
- !Ref WorkloadVpc1PrivateSubnet2
VpcEndpointType: Interface
VpcId: !Ref WorkloadVpc1
WorkloadVpc1SsmMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref WorkloadVpc1EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
SubnetIds:
- !Ref WorkloadVpc1PrivateSubnet1
- !Ref WorkloadVpc1PrivateSubnet2
VpcEndpointType: Interface
VpcId: !Ref WorkloadVpc1
WorkloadVpc1Ec2MessagesVpcEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
PrivateDnsEnabled: false
SecurityGroupIds:
- !Ref WorkloadVpc1EndpointSecurityGroup
ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
SubnetIds:
- !Ref WorkloadVpc1PrivateSubnet1
- !Ref WorkloadVpc1PrivateSubnet2
VpcEndpointType: Interface
VpcId: !Ref WorkloadVpc1
WorkloadVpc1TagSsmEndpoint:
Type: Custom::TagVpcEndpoint
Properties:
ServiceToken: !GetAtt CustomResourceTagger.Arn
VpcEndpointId: !Ref WorkloadVpc1SsmEndpoint
Name: workload-vpc1-ssm-endpoint
WorkloadVpc1TagSsmMessagesEndpoint:
Type: Custom::TagVpcEndpoint
Properties:
ServiceToken: !GetAtt CustomResourceTagger.Arn
VpcEndpointId: !Ref WorkloadVpc1SsmMessagesEndpoint
Name: workload-vpc1-ssmMessages-endpoint
WorkloadVpc1TagEc2MessagesVpcEndpoint:
Type: Custom::TagVpcEndpoint
Properties:
ServiceToken: !GetAtt CustomResourceTagger.Arn
VpcEndpointId: !Ref WorkloadVpc1Ec2MessagesVpcEndpoint
Name: workload-vpc1-ec2messages-endpoint
WorkloadVpc1S3Gateway:
Type: AWS::EC2::VPCEndpoint
Properties:
RouteTableIds:
- !Ref WorkloadVpc1PublicSubnetRouteTable
ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
VpcId: !Ref WorkloadVpc1
WorkloadVpc1TgwAttachment:
Type: AWS::EC2::TransitGatewayAttachment
Properties:
SubnetIds:
- !Ref WorkloadVpc1PrivateSubnet1
- !Ref WorkloadVpc1PrivateSubnet2
Tags:
- Key: Name
Value: "Workload VPC Private Subnet TGW Attachment"
TransitGatewayId: !Ref TGW
VpcId: !Ref WorkloadVpc1
WorkloadVpc1TgWRtbAssociation:
Type: AWS::EC2::TransitGatewayRouteTableAssociation
Properties:
TransitGatewayAttachmentId: !Ref WorkloadVpc1TgwAttachment
TransitGatewayRouteTableId: !Ref TgwMainRouteDomain
WorkloadVpc1TgWRtbPropagation:
Type: AWS::EC2::TransitGatewayRouteTablePropagation
Properties:
TransitGatewayAttachmentId: !Ref WorkloadVpc1TgwAttachment
TransitGatewayRouteTableId: !Ref TgwMainRouteDomain
WorkloadVpcInternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: "Workload VPC IGW"
WorkloadVpcInternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref WorkloadVpc1
InternetGatewayId: !Ref WorkloadVpcInternetGateway
###
### Delete Default VPC
###
DeleteDefaultVpcExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonVPCFullAccess
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
DeleteDefaultVpcLambda:
Type: AWS::Lambda::Function
Properties:
Handler: index.main
Role: !GetAtt DeleteDefaultVpcExecutionRole.Arn
Code:
ZipFile: |
import cfnresponse
import concurrent.futures
import sys
import boto3
from botocore.exceptions import ClientError
import traceback
def delete_igw(ec2, vpc_id):
args = {"Filters": [{"Name": "attachment.vpc-id", "Values": [vpc_id]}]}
igws = ec2.describe_internet_gateways(**args)["InternetGateways"]
for igw in igws:
igw_id = igw["InternetGatewayId"]
ec2.detach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id)
ec2.delete_internet_gateway(InternetGatewayId=igw_id)
def delete_subs(ec2, args):
subs = ec2.describe_subnets(**args)["Subnets"]
for sub in subs:
sub_id = sub["SubnetId"]
ec2.delete_subnet(SubnetId=sub_id)
def delete_rtbs(ec2, args):
rtbs = ec2.describe_route_tables(**args)["RouteTables"]
if rtbs:
for rtb in rtbs:
main = False
for assoc in rtb["Associations"]:
main = assoc["Main"]
if not main:
rtb_id = rtb["RouteTableId"]
ec2.delete_route_table(RouteTableId=rtb_id)
def delete_acls(ec2, args):
acls = ec2.describe_network_acls(**args)["NetworkAcls"]
for acl in acls:
is_default = acl["IsDefault"]
if not is_default:
acl_id = acl["NetworkAclId"]
ec2.delete_network_acl(NetworkAclId=acl_id)
break
def delete_sgps(ec2, args):
sgps = ec2.describe_security_groups(**args)["SecurityGroups"]
non_default_sg_ids = [i["GroupId"] for i in sgps if i["GroupName"] != "default"]
tries = 0
max_tries = len(sgps)
while len(non_default_sg_ids) > 1 and tries < max_tries:
for sg_id in non_default_sg_ids:
try:
ec2.delete_security_group(GroupId=sg_id)
except ClientError as exc:
print(exc, file=sys.stderr)
tries += 1
def delete_vpc(ec2, vpc_id):
ec2.delete_vpc(VpcId=vpc_id)
print("Default VPC "+vpc_id+" has been deleted.")
def process_region():
ec2 = boto3.Session().client("ec2")
try:
attribs = ec2.describe_account_attributes(AttributeNames=["default-vpc"])
except ClientError as e:
raise RuntimeError(
"Unable to query VPCs in {}: {}".format(
region, e.response["Error"]["Message"]
)
) from e
assert 1 == len(attribs["AccountAttributes"])
vpc_id = attribs["AccountAttributes"][0]["AttributeValues"][0]["AttributeValue"]
if vpc_id == "none":
print("Default VPC was not found in the region.")
else:
args = {"Filters": [{"Name": "vpc-id", "Values": [vpc_id]}]}
eni = ec2.describe_network_interfaces(**args)["NetworkInterfaces"]
if eni:
print("Default VPC "+{vpc_id}+" has existing resources. Won't delete.")
else:
print("Deleting default VPC "+vpc_id)
delete_igw(ec2, vpc_id)
delete_subs(ec2, args)
delete_rtbs(ec2, args)
delete_acls(ec2, args)
delete_sgps(ec2, args)
delete_vpc(ec2, vpc_id)
def main(event, context):
session = boto3.Session()
ec2 = session.client("ec2")
responseData = {}
responseStatus = cfnresponse.FAILED
if event["RequestType"] == "Delete":
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
if event["RequestType"] == "Create":
process_region()
#responseData['Data'] = 'DeleteDefaultVpc'
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
if __name__ == "__main__":
main()
Runtime: python3.8
Timeout: 30
DeleteDefaultVpc:
Type: Custom::DeleteDefaultVpc
Properties:
ServiceToken: !GetAtt DeleteDefaultVpcLambda.Arn
# Custom tagger
CustomResourceTaggerLambdasExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- ec2:CreateTags
Resource: '*'
CustomResourceTagger:
Type: AWS::Lambda::Function
Properties:
Handler: index.main
Role: !GetAtt CustomResourceTaggerLambdasExecutionRole.Arn
Code:
ZipFile: !Sub |
import boto3
import cfnresponse
ec2 = boto3.resource('ec2')
def main(event, context):
VpcEndpointId = event['ResourceProperties']['VpcEndpointId']
Name = event['ResourceProperties']['Name']
responseData = {}
responseStatus = cfnresponse.FAILED
if event["RequestType"] == "Delete":
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
if event["RequestType"] == "Create" or event["RequestType"] == "Update":
response = ec2.create_tags(
Resources=[
VpcEndpointId,
],
Tags=[
{
'Key': 'Name',
'Value': Name
},
]
)
responseStatus = cfnresponse.SUCCESS
cfnresponse.send(event, context, responseStatus, responseData)
Runtime: python3.8
Timeout: 30
### App Servers
InstanceRole:
Type: AWS::IAM::Role
Properties:
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- route53:ChangeRecordSet
Resource: '*'
Path: /
InstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref InstanceRole
Vpc1appSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for webapp in VPC1
GroupName: workload-vpc1-webapp-instance-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: workload-vpc1-webapp-instance-sg
Vpc1app:
DependsOn:
- TGW
- WorkloadVpc1TgWRtbPropagation
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
NetworkInterfaces:
- AssociatePublicIpAddress: false
DeviceIndex: '0'
GroupSet:
- !Ref Vpc1appSg
SubnetId: !Ref WorkloadVpc1PrivateSubnet1
UserData: !Base64
Fn::Sub: |
#!/bin/bash -xe
notify() {
echo "UserData was unsuccessful!"
...
# use this function to implement the notification/shutdown behavior
}
trap notify ERR SIGINT SIGTERM
instanceIp=$(curl -sL http://169.254.169.254/latest/meta-data/local-ipv4)
echo "#User rules for ssm-user" > ssm-agent-users
yum update -y
yum install httpd -y
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
nvm install 20
npm install -g loadtest
echo "You successfully connected to the workload-vpc1-webapp-instance in vpc1!" > /var/www/html/index.html
systemctl start httpd
systemctl enable httpd
systemctl restart amazon-ssm-agent
Tags:
- Key: Name
Value: workload-vpc1-webapp-instance
Vpc1Alb:
DependsOn:
- Vpc1app
- WorkloadVpcInternetGateway
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: workload-vpc1-alb
Scheme: internet-facing
Subnets:
- !Ref WorkloadVpc1PublicSubnet1
- !Ref WorkloadVpc1PublicSubnet2
Type: application
SecurityGroups:
- !Ref Vpc1AlbSg
Vpc1AlbSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for ALB in VPC1
GroupName: workload-vpc1-alb-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: workload-vpc1-alb-sg
Vpc1AlbListener:
DependsOn:
- Vpc1Alb
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref Vpc1AlbTargetGroup
LoadBalancerArn: !Ref Vpc1Alb
Port: 80
Protocol: HTTP
Vpc1AlbTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: workload-vpc1-alb-tg
Port: 80
Protocol: HTTP
Targets:
- Id: !Ref Vpc1app
Port: 80
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: workload-vpc1-alb-tg
Vpc1Db:
Type: AWS::RDS::DBInstance
Properties:
AllocatedStorage: 20
DBInstanceClass: db.t3.micro
DBInstanceIdentifier: workload-vpc1-db
Engine: MySQL
MasterUsername: admin
MasterUserPassword: adminPasswordVerySecret123PleaseNoHacky!
VPCSecurityGroups:
- !Ref Vpc1DbSg
DBSubnetGroupName: !Ref DBsubnetGroupName
DBsubnetGroupName:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: DB subnet group for VPC1
SubnetIds:
- !Ref WorkloadVpc1DbSubnet1
- !Ref WorkloadVpc1DbSubnet2
Vpc1DbSg:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for DB in VPC1
GroupName: workload-vpc1-db-sg
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
CidrIp: 10.1.0.0/23
VpcId: !Ref WorkloadVpc1
Tags:
- Key: Name
Value: workload-vpc1-db-sg
Outputs:
VPC1LoadBalancerUrl:
Description: The URL of the VPC1 Load Balancer
Value: !GetAtt Vpc1Alb.DNSName
VPC1DatabaseEndpoint:
Description: "Connection endpoint for the database"
Value: !GetAtt Vpc1Db.Endpoint.Address
終わりに
今回のセッションでは基本的なことでありつつも大事な多層防御で使用されるセキュリティリソースを実際に触ってみました。
実際検証しやすいリソースだとは思いますのでぜひ触ったことない方は実際に構築してみて VPC 内リソースのセキュリティ強化に努めていただければと思います。
この記事がどなたかの役に立つと嬉しいです。
宣伝
6/17(月)19 時よりおそらくオフライン世界最速の re:Cap を開催します。
AWS re:Inforce 2024 に現地参加したメンバーによる振り返りイベントを開催しますのでぜひ足を運んでいただければと思います。
参加登録は以下、Connpass よりよろしくお願いいたします!