[レポート] Amazon VPCのための多層ネットワークセキュリティの構築 #NIS373 #AWSreInforce
こんにちは、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 よりよろしくお願いいたします!