【AWS】CloudFormationでセッションマネージャで接続可能なEC2を構築してみた(NATGateway編)

<!-- 発端や概要を記載 -->
# 目次
<!-- タイトルとアンカー名を編集 -->
1. [はじめに](#はじめに)
1. [セッションマネージャーとは](#セッションマネージャーとは)
1. [構築概要図](#構築概要図)
1. [スタックの作成](#スタックの作成)
1. [よくある間違い](#よくある設定ミス)

# はじめに
EC2を使って検証してみよーっと
の前にVPCから構築しなきゃ。。。

ってこと皆さん経験あるでしょうか。
自分はしょっちゅうあります（笑）

１度作成したリソースをそのまま残しておけば検証は楽ですが、思わぬとこでコストがかかってしまったり、多額の請求につながってしまう恐れがあります。

簡単な検証をするためにいちいちサブネット作って、SGを設定して、エンドポイント作成したけど、セッションマネージャでうまく繋がらなかった時ってめちゃくちゃストレスですよね。。。
どこの設定を間違えているかか探す時間がもったいないし

ということでセッションマネージャーで接続可能な検証用EC2を手軽に構築できるCloudFormationテンプレートを作成しました。

<!-- 各チャプター -->

# セッションマネージャーとは 
めっっっっっちゃくちゃ簡単にまとめるとSSH鍵を使用せず、EC2に接続できるサービスです。
プライベートサブネットに配置したEC2に接続しようとした場合、パブリックIPアドレスが振られていないので直接SSHで接続することができず、パブリックサブネットに配置した踏み台サーバ等からSSHで接続する必要があります。
セッションマネージャーを使用するとAWSコンソール画面からプライベートサブネット内のEC2に直接接続でき、踏み台サーバやSSH鍵が不要になります。


# 構築概要図
今回CloudFormationでテンプレート化したNATGatewayを使用した構成の概要図です。
(図には記載がないですが、SGで疎通を制限したり、IAMロールもアタッチします)
セッションマネージャーを使用したEC2への接続方法はいくつか方法があります。

ここではNATGatewayを使用した方法を解説します。
ほかの接続方法についても記事を書こうと思いますので気になる方は読んでいただけたらと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/6766868d-9eac-3895-c283-d506a6183643.png)


<a id="#reference"></a>
# スタックの作成
前置きが長くなってしまいましたが本題のテンプレートです。
各パラメータの説明とかどうやってここまで書き上げたかって話を書いちゃうと
めちゃくちゃ長くて読む気が失せると思うので今回は割愛します。
近いうちに別の記事で紹介できたらなと思ってますので、もしよかったら読んでください。

```session-manager.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: 
  Cfn templete for session-manager
Parameters:
  VPCName:
    Type: String
    Default: SesssionManagerNatGateway
  VPCCidrBlock:  
    Type: String
    Default: 10.0.0.0/16
  PrivateSubnetCidrBlock:
    Type: String
    Default: 10.0.1.0/24
  PublicSubnetCidrBlock:
    Type: String
    Default: 10.0.2.0/24
  #PublicSubnetのAZに指定
  AZ1:
    Type: String
    AllowedValues:
      - ap-northeast-1a
      - ap-northeast-1c
      - ap-northeast-1d
  #PrivateSubnetのAZに指定
  AZ2:
    Type: String
    AllowedValues:
      - ap-northeast-1a
      - ap-northeast-1c
      - ap-northeast-1d
  #起動するEC2のAMIに書き換えて使用
  CustomAMI:
    Type: String
    Default: ami-*******
    AllowedValues:
      -ami-*******
      -ami-*******
      -ami-*******
      -ami-*******
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t3.micro
      - t3.small
      - m1.small
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock
      Tags: 
        - Key: Name
          Value: !Ref VPCName
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref AZ1
      VpcId: !Ref VPC
      CidrBlock: !Ref PrivateSubnetCidrBlock
      MapPublicIpOnLaunch: 'false'
      Tags:
        - Key: Name
          Value: SSMPrvSub
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Ref AZ2
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCidrBlock
      Tags:
        - Key: Name
          Value: SSMPubSub
  NatGateway:
    Type: AWS::EC2::NatGateway
    DependsOn: AttachIGW
    Properties:  
      AllocationId: !GetAtt ElasticIP.AllocationId 
      ConnectivityType: public
      SubnetId: !Ref PublicSubnet
      Tags: 
        - Key: Name
          Value: SSMNatGW
  ElasticIP:
      Type: AWS::EC2::EIP
      Properties:
        Domain: vpc
        Tags:
          - Key: Name
            Value: SSM-NatIP
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: SSM-IGW-cfn
  AttachIGW:
    Type: AWS::EC2::VPCGatewayAttachment
    DependsOn: VPC
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway      
  PublicSubRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: 
      - Key: Name
        Value: SSMPublicRouteTable
  PrivateSubRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: 
      - Key: Name
        Value: SSMPrivateRouteTable
  ToNATRoute:
    DependsOn: NatGateway
    Type: AWS::EC2::Route
    Properties: 
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
      RouteTableId: !Ref PrivateSubRouteTable
  ToIGWRoute:
    DependsOn: InternetGateway
    Type: AWS::EC2::Route
    Properties: 
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
      RouteTableId: !Ref PublicSubRouteTable
  PubAssociateRouteTable:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubRouteTable
      SubnetId: !Ref PublicSubnet
  PriAssociateRouteTable:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubRouteTable
      SubnetId: !Ref PrivateSubnet
  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: SecurityGroup-for-SessionManager
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443  
      Tags:
        - Key: Name
          Value: NatEC2ssessionmanagerSG
  InstanceProfile:
    DependsOn: SessionManagerRole
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: '/'
      Roles:
        - !Ref SessionManagerRole
  SessionManagerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: SessionManager-role-cfn
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  myEC2Instance:
    Type: AWS::EC2::Instance
    DependsOn: PrivateSubnet
    Properties:
      ImageId: !Ref CustomAMI
      InstanceType: !Ref InstanceType
      SubnetId: !Ref PrivateSubnet
      IamInstanceProfile: !Ref InstanceProfile
      SecurityGroupIds:
        - !Ref SecurityGroup
      Tags:
        - Key: Name
          Value: Cfn-nat-ssm
```
まずCloudFormationのコンソール画面を開き、「スタックの作成」の「新しいリソースを使用(標準)」
を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/4912f925-508e-e01e-0896-a0da3b1d0e54.png)
「ファイルの選択」から起動したいテンプレートをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/184d27c0-1b86-662b-3840-7274c6df8a79.png)

今回はテンプレートで定義したデフォルト値で設定します。
AZは好きなところを選んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/3e534653-ce8b-ed57-461c-e3950fae6dc9.png)

必要に応じてタグやスタックポリシー等を設定してください。
作成に時間がかかるリソースが多い場合は「スタック失敗オプション」で「正常にプロビジョニングされたリソースの保持」を選択することで、ロールバックされずに済みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/64389354-2820-7de7-f13a-0fd3a615832c.png)

次の画面で「AWS CloudFoamationリソースがカスタム名で作成される場合があることを承認します」にチェックを入れて、「スタックの作成」を押下すると作成が始まります。
EC2にセッションマネージャで接続するための権限(AmazonSSMManagedInstanceCore)を付与したロールをこのテンプレートで作成するので、チェックは必ず入れてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/21ff1bfe-402e-edf1-015f-ac5188f19dcb.png)
# 稼働確認
テンプレートからスタックを作成したら作成完了まで待ちます。
スタック名に指定した論理IDのステータスが「CREATE_COMPLETE」になれば成功です。
今回は「Test-Stack-SessionManager」というスタック名で作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/442b9df9-413c-8923-5437-2faa011d63f1.png)
VPCを確認すると指定したCIDRで作成されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/ecebc001-1a85-8dd2-dfcd-e419c58b4e67.png)

EC2のコンソール画面で接続したいインスタンスを選択し、「接続」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/1805f235-f5f9-9ae7-e81c-62f73cf26914.png)
セッションマネージャの接続が可能な状態になって入れば「接続」の文字がオレンジになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/202443a3-8f44-039d-550a-2b94a6c18ca5.png)

接続完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/db664fea-aa5b-cd51-9465-fc0d66295037.png)
# よくある設定ミス
AWSの勉強を始めたばかりのころに自分も経験したのですが、セッションマネージャーで接続するためにはVPCにサブネットを作成し、EC2を起動するだけでは接続できません。
セッションマネージャーで接続できない場合によくある設定ミスをいくつか挙げておきます。
> + 接続先EC2にアタッチしたロールに「AmazonSSMManagedInstanceCore」ポリシーがアタッチされていない
> + 接続対象インスタンスにSSmエージェントがインストールされていない
> + SGのアウトバウンド:443が許可されていない(SSMエージェントが使用するポート)
> + NACLでインバウンド・アウトバウンドが許可されていない、もしくは拒否されている
 (NACLはステートレスなのでインバウンド・アウトバウンドで明示的な許可が必要です)
# さいごに
初めて記事を書いたのでまだまだ読みづらい箇所が多いかと思いますが、たくさん書いて読みやすい記事を書けるように頑張ります！