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

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

# はじめに
CloudFormationでセッションマネージャ接続可能な検証用EC2を構築したい！ってことで第2弾です。
第1弾のNatGatewayを使用したCloudFormationテンプレートはこちらになります。
+ [【AWS】CloudFormationでセッションマネージャで接続可能なEC2を構築してみた(NATGateway編)](https://qiita.com/itkz1016/items/1c39f024a00b020c5bd7)

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

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

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

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

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

# 構築概要図
今回CloudFormationでテンプレート化したVPCエンドポイントを使用した構成の概要図です。
(図には記載がないですが、SGで疎通を制限したり、IAMロールもアタッチします)
第1弾でもご紹介しましたがセッションマネージャーを使用したEC2への接続方法はいくつか方法があります。
前回はNATGatewayを使用したCloudFormationテンプレートを紹介しましたが、今回はVPCエンドポイントを使用した接続です。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/d76136c6-99c9-092d-321b-22962a347af8.png)
# VPCエンドポイントとは
まず初めにVPCエンドポイントって何？って方に向けて簡単にですが説明します。
VPCエンドポイントを使用するとVPC内のサービスとVPC外のAWSサービスをインターネットを経由せずに(プライベートに)接続することができます。
具体例だとEC2とS3間をインターネットを経由せずに接続することが可能になります。

エンドポイントには「ゲートウェイエンドポイント」、「インターフェースエンドポイント」、「GatewayLoadBalancerエンドポイント」の3種類があり、今回使用するは「インターフェースエンドポイント」と「ゲートウェイエンドポイント」になります。

それぞれのエンドポイントの違いについて説明するとそれだけでいくつか記事が書けそうなので割愛させていただきますが、よくわからない方はとりあえず、AWSリソース同士をプライベートに接続するために機能なんだなぁってくらいで考えておいてください。
インターフェースエンドポイント同士は接続可能だったり、ゲートウェイエンドポイント同士は接続不可(名前解決できず、ルートテーブルを使用した接続しかできないため)とか詳しく書き出すとすさまじい量になりそうなので。。。


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

```session-manager.yaml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  VPCName:
    Type: String
    Default: SesssionManagerVPCEndpoint
  VPCCidrBlock:  
    Type: String
    Default: 10.0.0.0/16
  PrivateSubnetCidrBlock:
    Type: String
    Default: 10.0.1.0/24
  AZ1:
    Type: String
    AllowedValues:
      - ap-northeast-1a
      - ap-northeast-1c
      - ap-northeast-1d
  CustomAMI:
    Type: String
    #インスタンスに使用するAMIを指定
    Default: ami-*****************
    AllowedValues:
      - 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
      EnableDnsHostnames: 'True'
      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: SessionManagerPrvSub  
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags: 
      - Key: Name
        Value: PrivateRouteTable
  PriAssociateRouteTable:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet
  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: SecurityGroup-for-EC2-SessionManager
      SecurityGroupEgress: 
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443  
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 25
          ToPort: 25
      Tags:
        - Key: Name
          Value: EC2ssessionmanagerSG
  VPCEndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: SecurityGroup-for-VPCEndpoint
      SecurityGroupIngress:
        - SourceSecurityGroupId: !Ref EC2SecurityGroup
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443  
      Tags:
        - Key: Name
          Value: VPCEndpoint-SG
  EndpointSSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: Interface
      VpcId: !Ref VPC
  EndpointSSMMessages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: Interface
      VpcId: !Ref VPC 
  EndpointEC2Messages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VPCEndpointSecurityGroup
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      SubnetIds:
        - !Ref PrivateSubnet
      VpcEndpointType: Interface
      VpcId: !Ref VPC
  EndpointS3:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      RouteTableIds:
        - !Ref PrivateRouteTable
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref VPC
  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: vpcendpoint-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 EC2SecurityGroup
      Tags:
        - Key: Name
          Value: Cfn-sessionmanager-instance
```

スタックの起動方法につきましては、「【AWS】CloudFormationでセッションマネージャで接続可能なEC2を構築してみた(NATGateway編)」で紹介していますのでわからない方はこちらを参考にしてください。（よかったら拡散も！)
https://qiita.com/itkz1016/items/1c39f024a00b020c5bd7
# 稼働確認
テンプレートからスタックを作成したら作成完了まで待ちます。
スタック名に指定した論理IDのステータスが「CREATE_COMPLETE」になれば成功です。
今回は「VPCEndopoint-SessionManager」というスタック名で作成しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/af97de0f-ec07-3751-9a8a-df7a62171269.png)
VPCを確認すると指定したCIDRで作成されていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/3eed0ee7-25b9-43a9-5ecf-cde6346cc13b.png)
VPCEndpointを確認してみます。
今回Nameタグの指定をしなかったので名前タグいていませんが4つ作成されていることが確認できます。
この4種(もしくはcom.amazonaws.ap-northeast-1.s3以外の3つ)はEC2とセッションマネージャを行う際に必要なエンドポイントとなります。
各エンドポイントの詳細は以下の公式ドキュメントをご参照ください。
https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-create-vpc.html#sysman-setting-up-vpc-create
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/da25b420-ec00-66e5-4a45-7a8e22af3f0d.png)
次にエンドポイント用とEC2用にそれぞれ作成したセキュリティグループを見てみましょう。
まずはエンドポイント用セキュリティグループです。
EC2にアタッチしたSGを送信元ソースに指定し、HTTPS:443のインバウンドを許可しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/1a313c06-894c-c6bd-4c52-055dac6828ff.png)
次にEC2用セキュリティグループです。
VPCエンドポイントにアタッチしたセキュリティグループを送信先ソースとして指定し、HTTPS:443のアウトバウンドを許可しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615865/ddfb17ea-0d40-e2e6-0040-18eeb7813c3a.png)
大まかな設定を確認したので、いよいよ接続していきます。
EC2のコンソール画面で接続したいインスタンスを選択し、「接続」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/3ee60329-5612-203e-d78b-09a9f3472787.png)
セッションマネージャの接続が可能な状態になって入れば「接続」の文字がオレンジになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633099/7241fd7d-102b-6eed-301a-9c87f70d1976.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はステートレスなのでインバウンド・アウトバウンドで明示的な許可が必要です)
> + VPCエンドポイントが足りない
# さいごに
セッションマネージャー第2弾ということで書いてみましたが、NatGatewayを使用する構成よりは、VPCエンドポイントのほうが少し安いのかな？ってことでよく使ってます。(エンドポイントが大量に必要な場合はコスト面ではNatGatewayの使用をお勧めします。)
ただ、手作業で設定しようとするとVPCエンドポイントへのルートテーブルの設定だったり、エンドポイントとEC2それぞれに作成するセキュリティグループの設定が必要でちょっとだけ大変な気がします。
今回載せたテンプレートの解説はあえて割愛したのでCloudFormationのことがよくわからない方はなぜ動いているのか疑問に思っているかと思います。テンプレートを書く際のコツとか、何を考えて、何を見ながら書いているのかって話もどこかのタイミングで記事にできたらなと思います。
もしよかったらTwitterでの拡散もお願いします！