SSMセッションマネージャーでコンソールアクセスできるEC2をCloudFormationで自動作成する方法

8 min read読了の目安(約7700字

こんにちは、Masuyama です。

突然ですが SSM の機能の一つであるセッションマネージャーのことをご存知でしょうか。
簡単にいうとブラウザ経由でコンソールにアクセスできる機能のことで、[公式ドキュメント(https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager.html)では次のように説明されています。

Session Manager はフルマネージド型 AWS Systems Manager 機能であり、インタラクティブなワンクリックブラウザベースのシェルや AWS Command Line Interface (AWS CLI) を介して Amazon Elastic Compute Cloud (Amazon EC2) インスタンス、オンプレミスインスタンス、および仮想マシン (VM) を管理できます。Session Manager を使用すると、インバウンドポートを開いたり、踏み台ホストを維持したり、SSH キーを管理したりすることなく、監査可能なインスタンスを安全に管理できます。

ここに書いてある通り、SSH ポートを開いたりする必要もなく、鍵すら必要ないためセキュアにインスタンスを運用することができます。
ただし、実は特定の IAM マネージドポリシーを付与しなければいけなかったり、インスタンスがインターネットに出られる必要があったりと、地味に落とし穴が多かったりします。

今回はタイトルの通りですが、CloudFormation でパブリックサブネット内に EC2 インスタンスを 1 台立ち上げると同時に SSM セッションマネージャーでアクセスするために必要なポリシーを付与する構成を自動で作れるようにしたいと思います。

テンプレートファイルの完成形

あえて先にテンプレートファイルの完成形を載せて、その中でポイントをピックアップしていきたいと思います。
なお、本当にセッションマネージャーでしかアクセスしないならばインバウンドのポートは究極完全封鎖でもいけるのですが、実用性を考慮して HTTP 80 と SSH 25 ぐらいは開けておきます。
ただし、アクセス元のグローバル IP アドレスぐらいは任意で絞れるようにもしておきます。

AWSTemplateFormatVersion: "2010-09-09"
Description: Provision EC2

Parameters:
  EnvironmentName:
    Description: Name which you can specify the environment by this name
    Type: String
    Default: test-environment

  VpcCIDR:
    Type: String
    Default: 10.0.0.0/16

  PublicSubnetCIDR:
    Type: String
    Default: 10.0.0.0/24

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"

  MyIP:
    Description: IP address range which is allowed to access this ECS from it
    Type: String
    Default: 0.0.0.0/0

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

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-InternetGateway

  # InternetGateway をVPCにアタッチ
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: ap-northeast-1a
      VpcId: !Ref VPC
      CidrBlock: !Ref PublicSubnetCIDR
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-PublicSubnet

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

  # PublicSubnet-インターネット間のルーティング
  PublicSubnetToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnetRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  # ルートテーブルをサブネットに関連付け
  AssoPublicSubnetRouteTable:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicSubnetRouteTable

  EC2:
    Type: AWS::EC2::Instance
    Properties:
      # Amazon Linux 2
      ImageId: ami-00d101850e971728d
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces: 
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref EC2SecurityGroup
      # EC2作成時にインスタンスプロファイルを指定
      IamInstanceProfile:
        Ref: SessionManagerIamInstanceProfile
      Tags:
          - Key: Name
            Value: !Sub ${EnvironmentName}-EC2

  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ec2-sg-cf
      GroupDescription: Allow SSH and HTTP access only MyIP
      VpcId: !Ref VPC
      SecurityGroupIngress:
        # http
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref MyIP
        # ssh
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref MyIP

ポイントの抜粋

さて、いくつか注意点とセッションマネージャーに関するところだけ抜粋して紹介していきたいと思います。

指定しているパラメータ

Parameters を使用して、スタック作成時にいくつか入力を求めるようにしています。

...
Parameters:
  EnvironmentName:
    Description: Name which you can specify the environment by this name
    Type: String
    Default: test-environment

  VpcCIDR:
    Type: String
    Default: 10.0.0.0/16

  PublicSubnetCIDR:
    Type: String
    Default: 10.0.0.0/24

  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: "AWS::EC2::KeyPair::KeyName"

  MyIP:
    Description: IP address range which is allowed to access this ECS from it
    Type: String
    Default: 0.0.0.0/0
...

名前や説明文から想像出来る通りですが、以下のように定義しています。

  • EnvironmentName
    • 作る環境を一意に指定するための文字列
    • リソースの Name タグの冒頭に付加されます
  • VpcCIDR
    • CloudFormation で作られる VPC の CIDR
  • PublicSubnetCIDR
    • CloudFormation で作られるパブリックサブネットの CIDR
  • KeyName
    • EC2 に用いるキーペア
    • アカウントに作成されているキーペアを選択させます
  • MyIP
    • EC2 へのアクセスを許可するグローバル IP アドレス

セッションマネージャー用の権限付与

SSM セッションマネージャーを使うためには、EC2 インスタンスが AmazonSSMManagedInstanceCore というマネージドポリシー(最初から作成されているポリシー)を与えられている必要があります。
ここでは上記ポリシーを付与した IAM ロールを作成しています。

...
  SsmSessionManagerIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      RoleName: 'SsmSessionManagerIamRole'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
...

また、インスタンスプロファイルとそのロールを紐付けて、EC2 にインスタンスプロファイルを渡すことで結果的に EC2 インスタンスへ該当のポリシーに与えていることもポイントです。

...
  SessionManagerIamInstanceProfile:
    Type: 'AWS::IAM::InstanceProfile'
    Properties:
      Path: '/'
      Roles:
        - !Ref SsmSessionManagerIamRole
...
  EC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-00d101850e971728d
      KeyName: !Ref KeyName
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref EC2SecurityGroup
      # EC2作成時にインスタンスプロファイルを指定
      IamInstanceProfile:
        Ref: SessionManagerIamInstanceProfile
      Tags:
          - Key: Name
            Value: !Sub ${EnvironmentName}-EC2

スタックの作成

これを使って実際に作成していきます。
入力しているパラメータは一例です。

しばらく待つとスタックの作成が完了しますので、SSM サービス画面からセッションマネージャーを使って EC2 のコンソールにアクセスしてみましょう。

先ほど CloudFormation で作成した EC2 を指定して [セッションの開始] を選択します。

必要な IAM ポリシーを与えられているので、問題なくアクセスすることができました。
インターネットに出られる環境なのでもちろん Ping も外部に通ります。

まとめ

CloudFormation で作成する EC2 へセッションマネージャーでアクセス出来るようにすることが目的でしたが、同時に CloudFormation で EC2 に IAM ロール/ポリシーを付与する方法も理解いただけたかと思います。
インスタンスプロファイルで権限を渡すにあたりテンプレートファイルでの書き方に少し特徴があるので、基本的な使い方として身につけておくと便利です。