[小ネタ]Session ManagerでアクセスできるEC2を1発で作成するCloudFormationテンプレート作ってみた

2020.10.06

こんにちはオンジーこと恩塚です!

パパッと検証でEC2を起動してセッションマネージャーでコマンドを試したいっていうときに今までマネジメントコンソールからポチポチやってたのですが、一発で構築したくなってCloudFormationテンプレートを作ってみました。

テンプレート(yamlファイル)

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  EnvironmentName:
    Type: String
    Default: xxxxxxxx-env

  VPCCIDR:
    Type: String
    Default: 10.192.0.0/16

  PrivateSubnetCIDR:
    Type: String
    Default: 10.192.0.0/24

  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

  Ec2InstanceType:
    Type: String
    Default: t3.nano

  KeyPair:
    Type: String
    Default: xxxxx-key

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-VPC

  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ""]
      CidrBlock: !Ref PrivateSubnetCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-PrivateSubnet

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-PrivateRouteTable

  PrivateSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable

  EndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EndpointSecurityGroup
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EndpointSecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !Ref VPCCIDR

  EndpointSSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup
      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 EndpointSecurityGroup
      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 EndpointSecurityGroup
      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

  EC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${EnvironmentName}-SSM-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - Ref: EC2IAMRole
      InstanceProfileName: !Sub ${EnvironmentName}-EC2InstanceProfile

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EC2SecurityGroup
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EC2SecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0

  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref Ec2InstanceType
      SubnetId: !Ref PrivateSubnet
      ImageId: !Ref Ec2ImageId
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 100
            VolumeType: gp2
      EbsOptimized: true
      SourceDestCheck: true
      KeyName: !Ref KeyPair
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EC2Instance

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub ${EnvironmentName}-VPC

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub ${EnvironmentName}-VPCCIDR

  PrivateSubnet:
    Value: !Ref PrivateSubnet
    Export:
      Name: !Sub ${EnvironmentName}-PrivateSubnet

  PrivateRouteTable:
    Value: !Ref PrivateRouteTable
    Export:
      Name: !Sub ${EnvironmentName}-PrivateRouteTable

  SecurityGroup:
    Value: !Ref EndpointSecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-EndpointSecurityGroup

  EndpointSSM:
    Value: !Ref EndpointSSM
    Export:
      Name: !Sub ${EnvironmentName}-EndpointSSM

  EndpointSSMMessages:
    Value: !Ref EndpointSSMMessages
    Export:
      Name: !Sub ${EnvironmentName}-EndpointSSMMessages

  EndpointEC2Messages:
    Value: !Ref EndpointEC2Messages
    Export:
      Name: !Sub ${EnvironmentName}-EndpointEC2Messages

  EndpointS3:
    Value: !Ref EndpointS3
    Export:
      Name: !Sub ${EnvironmentName}-EndpointS3

  EC2SecurityGroup:
    Value: !Ref EC2SecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-EC2SecurityGroup

  EC2Instance:
    Value: !Ref EC2Instance
    Export:
      Name: !Sub ${EnvironmentName}-EC2Instance

ざっくり解説

キーペアは作成済みのものを指定しています。

またスタックの作成方法については他記事でたくさん紹介されているので割愛します。

このテンプレートで作成しているのは以下の通りです。

  • VPC
  • サブネット
  • ルートテーブル
  • VPCエンドポイント
  • IAMロール
  • EC2インスタンス

大事なところだけ抜き出して紹介します!

  EndpointSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EndpointSecurityGroup
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-EndpointSecurityGroup
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: !Ref VPCCIDR

エンドポイントに設定するセキュリティグループはインバウンドルールでVPCからの443ポートへのHTTPS通信を許可する必要があります。

  EndpointSSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref EndpointSecurityGroup
      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 EndpointSecurityGroup
      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 EndpointSecurityGroup
      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

プライベートサブネットにあるEC2インスタンスにセッションマネージャーで繋ぐには全部で4つのVPCエンドポイントが必要になるのでこの部分で作成しています。

  • com.amazonaws.region.ssm
  • com.amazonaws.region.ec2messages
  • com.amazonaws.region.ssmmessages
  • com.amazonaws.region.s3
  EC2IAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${EnvironmentName}-SSM-role
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

セッションマネージャーでアクセスするためにはIAMポリシーのAmazonSSMManagedInstanceCoreがアタッチされているIAMロールがEC2インスタンスに設定されている必要があります。

作成したIAMロールをEC2のインスタンスプロファイルで指定します。

以上になります!

参考