AWS Hands-on for Beginners Network編#1 をCloudFormationでやってみる

AWS 初心者向けハンズオン
「Network編#1 AWS上にセキュアなプライベートネットワーク空間を作成する」をCloudFormationで実施してみました。

https://aws.amazon.com/jp/events/aws-event-resource/hands-on/

https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-Network1-2022-reg-event.html?trk=aws_introduction_page

:::note info
目的
- AWS CLIとCloudFormationの実装方法を比較
- スタックの作成、削除はAWS CLIから実施
:::
完成系
![hands-on-beginers_1.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3297641/68fb5704-c3d1-08d8-2b3c-d5985d4084da.png)

AWS CLI編はこちら

https://qiita.com/sincere_networker/items/01d506305048d355d64c

# スタックの作成方法
※TEMPLATE_FILE名, STACK_NAMEは適宜変更
※IAM リソースを含む場合`--capabilities CAPABILITY_NAMED_IAM`オプションが必要
```
TEMPLATE_FILE='~/template/handson_nat.yml'
STACK_NAME=handson

aws cloudformation deploy \
  --template-file ${TEMPLATE_FILE} \
  --stack-name ${STACK_NAME} \
  --capabilities CAPABILITY_NAMED_IAM
```
# スタックの削除方法
※STACK_NAMEは適宜変更
```
STACK_NAME=handson

aws cloudformation delete-stack \
  --stack-name ${STACK_NAME} 
```
# テンプレート(NAT編)
![hands-on-beginers-Step2.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3297641/621b9d79-c5e5-076b-4c8f-8d8a6228f8c3.png)
```:handson_nat.yml
AWSTemplateFormatVersion: 2010-09-09
Description: ---

Parameters: 
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.11.0/24

  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
    Type: String
    Default: 10.0.12.0/24

Resources: 
  ### VPC
  handsonVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: 'true'
      Tags:
      - Key: Name
        Value: handson

  ### Subnet_Public
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnet1CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Subnet - a"

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnet2CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Subnet - c"

  ### Subnet_Private
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PrivateSubnet1CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private subnet - a"

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnet2CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private subnet - c"

  ### Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: handson-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref handsonVPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Route Table"

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private Route Table"

  ### RouteTable_Association
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

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

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

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet2
  
  ### SecurityGroup
  SecurityGroupPublic:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: "beginers-handson SecurityGroup."
        GroupName: "beginers-handson-sg"
        VpcId: !Ref handsonVPC
        SecurityGroupIngress:
            - IpProtocol: tcp
              FromPort: 80
              ToPort: 80
              CidrIp: 0.0.0.0/0
  
  SecurityGroupPrivate:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: "ssm"
        GroupName: "ssm"
        VpcId: !Ref handsonVPC
        SecurityGroupIngress:
            - IpProtocol: tcp
              FromPort: 443
              ToPort: 443
              CidrIp: !Ref VpcCIDR

  ### EC2用ロール
  ec2Role:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'handson-ssm'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonSSMFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
  
  ### Create IAM Profile
  ec2InstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      InstanceProfileName: handson-ssm
      Path: "/"
      Roles:
        - !Ref ec2Role

  ### Create Public EC2
  PublicEC2:
    Type: "AWS::EC2::Instance"
    Properties:
      AvailabilityZone: ap-northeast-1a
      IamInstanceProfile: !Ref ec2InstanceProfile
      ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}"
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet1
          GroupSet:
           - !Ref SecurityGroupPublic
      UserData:
        Fn::Base64: |
          #!/bin/bash

          yum -y update
          yum -y install httpd
          systemctl enable httpd.service
          systemctl start httpd.service
      Tags:
      - Key: Name
        Value: "Web"

  ### Create Private EC2
  PrivateEC2:
    Type: "AWS::EC2::Instance"
    Properties:
      AvailabilityZone: ap-northeast-1c
      IamInstanceProfile: !Ref ec2InstanceProfile
      ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}"
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "false"
          DeviceIndex: "0"
          SubnetId: !Ref PrivateSubnet2
          GroupSet:
           - !Ref SecurityGroupPublic
      Tags:
      - Key: Name
        Value: "Internal"

  ### NatGateway
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
        AllocationId: !GetAtt NatGatewayEIP.AllocationId
        SubnetId: !Ref PublicSubnet2
        Tags:
        - Key: Name
          Value: handson-natgateway

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
        Domain: vpc

  DefaultPrivateRoute:
    Type: AWS::EC2::Route
    Properties:
        RouteTableId: !Ref PrivateRouteTable
        DestinationCidrBlock: '0.0.0.0/0'
        NatGatewayId: !Ref NatGateway

```
# テンプレート(エンドポイント編)
![hands-on-beginers-Step3.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3297641/e10962f4-be56-38f0-5ab2-f5fa9bcb87b6.png)

![hands-on-beginers-Step4.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3297641/25a846e5-6bf8-5fad-49f0-c72fa3f1dd71.png)

```:handson_endpoint.yml
AWSTemplateFormatVersion: 2010-09-09
Description: ---

Parameters: 
  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: "10.0.0.0/16"

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24

  PublicSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone
    Type: String
    Default: 10.0.2.0/24

  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.11.0/24

  PrivateSubnet2CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone
    Type: String
    Default: 10.0.12.0/24

Resources: 
  ### VPC
  handsonVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: 'true'
      Tags:
      - Key: Name
        Value: handson

  ### Subnet_Public
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnet1CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Subnet - a"

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnet2CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Subnet - c"

  ### Subnet_Private
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PrivateSubnet1CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private subnet - a"

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnet2CIDR
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private subnet - c"

  ### Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: handson-igw

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref handsonVPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Public Route Table"

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref handsonVPC
      Tags:
      - Key: Name
        Value: "Private Route Table"

  ### RouteTable_Association
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1

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

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

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet2
  
  ### SecurityGroup
  SecurityGroupPublic:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: "beginers-handson SecurityGroup."
        GroupName: "beginers-handson-sg"
        VpcId: !Ref handsonVPC
        SecurityGroupIngress:
            - IpProtocol: tcp
              FromPort: 80
              ToPort: 80
              CidrIp: 0.0.0.0/0
  
  SecurityGroupPrivate:
    Type: AWS::EC2::SecurityGroup
    Properties:
        GroupDescription: "ssm"
        GroupName: "ssm"
        VpcId: !Ref handsonVPC
        SecurityGroupIngress:
            - IpProtocol: tcp
              FromPort: 443
              ToPort: 443
              CidrIp: !Ref VpcCIDR

  ### EC2用ロール
  ec2Role:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'handson-ssm'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AmazonSSMFullAccess'
        - 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'
  
  ### Create IAM Profile
  ec2InstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      InstanceProfileName: handson-ssm
      Path: "/"
      Roles:
        - !Ref ec2Role

  ### Create Public EC2
  PublicEC2:
    Type: "AWS::EC2::Instance"
    Properties:
      AvailabilityZone: ap-northeast-1a
      IamInstanceProfile: !Ref ec2InstanceProfile
      ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}"
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet1
          GroupSet:
           - !Ref SecurityGroupPublic
      UserData:
        Fn::Base64: |
          #!/bin/bash

          yum -y update
          yum -y install httpd
          systemctl enable httpd.service
          systemctl start httpd.service
      Tags:
      - Key: Name
        Value: "Web"

  ### Create Private EC2
  PrivateEC2:
    Type: "AWS::EC2::Instance"
    Properties:
      AvailabilityZone: ap-northeast-1c
      IamInstanceProfile: !Ref ec2InstanceProfile
      ImageId: "{{resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64}}"
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "false"
          DeviceIndex: "0"
          SubnetId: !Ref PrivateSubnet2
          GroupSet:
           - !Ref SecurityGroupPublic
      Tags:
      - Key: Name
        Value: "Internal"

  ### NatGateway
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
        AllocationId: !GetAtt NatGatewayEIP.AllocationId
        SubnetId: !Ref PublicSubnet2
        Tags:
        - Key: Name
          Value: handson-natgateway

  NatGatewayEIP:
    Type: AWS::EC2::EIP
    Properties:
        Domain: vpc

  # DefaultPrivateRoute:
  #   Type: AWS::EC2::Route
  #   Properties:
  #       RouteTableId: !Ref PrivateRouteTable
  #       DestinationCidrBlock: '0.0.0.0/0'
  #       NatGatewayId: !Ref NatGateway

  SSMEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: "com.amazonaws.ap-northeast-1.ssm"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: !Ref handsonVPC
      SubnetIds:
        - !Ref PublicSubnet2
      SecurityGroupIds:
        - !Ref SecurityGroupPrivate

  SSMAgentEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.ap-northeast-1.ssmmessages"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: !Ref handsonVPC
      SubnetIds:
        - !Ref PublicSubnet2
      SecurityGroupIds:
        - !Ref SecurityGroupPrivate

  EC2MessageEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.ap-northeast-1.ec2messages"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: !Ref handsonVPC
      SubnetIds:
        - !Ref PublicSubnet2
      SecurityGroupIds:
        - !Ref SecurityGroupPrivate

  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: hands-on-beginners-bucket-20240218

  s3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.ap-northeast-1.s3"
      VpcEndpointType: Gateway
      VpcId: !Ref handsonVPC
      RouteTableIds:
        - !Ref PublicRouteTable
        - !Ref PrivateRouteTable
```
# まとめ
## ハンズオンをやった上でのCloudFormationで実装のメリット(AWS CLIでの実装と比較)
- テンプレートを作成すれば、環境の作成/削除が楽
- パラメータの値を変えることでIPアドレス等を変えた別環境が簡単に作成できる
- リソースIDの紐づけが簡単

## ハンズオンをやった上でのCloudFormationで実装方法、不明点
- エンドポイントのタグ名のつけ方が不明