SSMセッションマネージャーのポートフォワーディング機能を利用してみた

AWS System Managerセッションマネージャーにポートフォワーディング機能が追加されました。

AWS System Manager Sessions Manager を使用した新しい機能 – Port Forwarding

この機能を利用すれば、インバウンドポート(SSH/RDP)を許可せずに対象インスタンスへログインすることが出来ます。
実際に本機能を利用してみましょう。
※クライアント機はWindowsを想定しています。

サーバー側(EC2インスタンス)の準備

IAMロール

ログインするEC2インスタンスにIAMロールをアタッチします。
IAMポリシーは AmazonSSMManagedInstanceCore (AWS管理ポリシー) を利用します。

SSMエージェントのインストール

以下を参照ください。Amazon Linux等には既定でインストールされています。

Amazon EC2 Linux インスタンスに SSM エージェント を手動でインストールする

SSMエージェントのアップデート

以下を参照ください。

Run Command を使用して SSM エージェント を更新する

yum update等を利用した場合は最新バージョンに更新されない場合もあるので、Run Command での更新をお勧めします。

クライアント側の準備

AWS CLIのインストール

以下を参照ください。既にインストールされている場合も最新版のインストールをお勧めします。

Windows に AWS CLI をインストールする

セッションマネージャープラグインのインストール

以下を参照ください。既にインストールされている場合も最新版のインストールをお勧めします。

Windows に Session Manager Plugin をインストールする

クライアントで利用するアクセスキーの準備

クライアントにインストールしたAWS CLIを利用する為に、IAMのアクセスキーを準備します。
CloudFormationのテンプレートを用意したのでご利用ください。

AWSTemplateFormatVersion: "2010-09-09"
Description: 
  Sample CloudFormation Template for Use of SSM Session Manager.

Metadata: 
  "AWS::CloudFormation::Interface": 
    ParameterGroups: 
      - Label: 
          default: "EC2 Instance"
        Parameters: 
          - EC2InstanceId
      - Label: 
          default: "IAM Group"
        Parameters: 
          - NameOfIamGroup
          - NameOfIamPolicyForSsmSession
          - NameOfIamPolicyForRotatingAccessKey
      - Label: 
          default: "IAM User 01"
        Parameters: 
          - NameOfIamUser01
      - Label: 
          default: "IAM User 02"
        Parameters: 
          - CreateIamUser02
          - NameOfIamUser02
      - Label: 
          default: "IAM User 03"
        Parameters: 
          - CreateIamUser03
          - NameOfIamUser03
      - Label: 
          default: "IAM User 04"
        Parameters: 
          - CreateIamUser04
          - NameOfIamUser04
      - Label: 
          default: "IAM User 05"
        Parameters: 
          - CreateIamUser05
          - NameOfIamUser05

    ParameterLabels: 
      EC2InstanceId: 
        default: "EC2 Instance ID"
      NameOfIamGroup: 
        default: "Name of IAM Group"
      NameOfIamPolicyForSsmSession: 
        default: "Name of IAM Policy For Executiong SSM Session Manager"
      NameOfIamPolicyForRotatingAccessKey: 
        default: "Name of IAM Policy For Rotating Access Key"
      NameOfIamUser01: 
        default: "User Name"
      CreateIamUser02: 
        default: "User Create"
      NameOfIamUser02: 
        default: "User Name"
      CreateIamUser03: 
        default: "User Create"
      NameOfIamUser03: 
        default: "User Name"
      CreateIamUser04: 
        default: "User Create"
      NameOfIamUser04: 
        default: "User Name"
      CreateIamUser05: 
        default: "User Create"
      NameOfIamUser05: 
        default: "User Name"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------# 
Parameters:
  EC2InstanceId:
    Type: AWS::EC2::Instance::Id

  NameOfIamGroup:
    Type: String
    Default: "SSM-Session-Manager_Executors"

  NameOfIamPolicyForSsmSession:
    Type: String
    Default: "Execute_SSM-Session-Manager_to_Specific-Instance"

  NameOfIamPolicyForRotatingAccessKey:
    Type: String
    Default: "Rotate_Self-AccessKey"

  NameOfIamUser01:
    Type: String
    Default: "IAM-User01"

  CreateIamUser02:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false

  NameOfIamUser02:
    Type: String
    Default: "IAM-User02"

  CreateIamUser03:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false

  NameOfIamUser03:
    Type: String
    Default: "IAM-User03"

  CreateIamUser04:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false

  NameOfIamUser04:
    Type: String
    Default: "IAM-User04"

  CreateIamUser05:
    Type: String
    Default: false
    AllowedValues:
      - true
      - false

  NameOfIamUser05:
    Type: String
    Default: "IAM-User05"

# ------------------------------------------------------------#
#  Conditions
# ------------------------------------------------------------#
Conditions:
# Create or Not: IAM Users
  CreateIamUser02True: !Equals [ !Ref CreateIamUser02, true ]
  CreateIamUser03True: !Equals [ !Ref CreateIamUser03, true ]
  CreateIamUser04True: !Equals [ !Ref CreateIamUser04, true ]
  CreateIamUser05True: !Equals [ !Ref CreateIamUser05, true ]

Resources: 
# ------------------------------------------------------------#
# IAM Group
# ------------------------------------------------------------#
# IAM Group
  IamGroup: 
    Type: AWS::IAM::Group
    Properties:
      GroupName: !Ref NameOfIamGroup

# IAM Policy For Executing SSM Session Manager
  IamPolicyForSsmSession:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Ref NameOfIamPolicyForSsmSession
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - ssm:StartSession
          Resource: 
              - !Sub "arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/${EC2InstanceId}"
              - !Sub "arn:aws:ssm:${AWS::Region}::document/AWS-StartPortForwardingSession"
        - Effect: Allow
          Action:
          - ssm:TerminateSession
          Resource: arn:aws:ssm:*:*:session/${aws:username}-*
      Groups:
      - !Ref IamGroup

# IAM Policy For Rotating Access Key
  IamPolicyForRotatingAccessKey:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Ref NameOfIamPolicyForRotatingAccessKey
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
          - iam:CreateAccessKey
          - iam:DeleteAccessKey
          Resource: !Join
            - ''
            - - !Sub "arn:aws:iam::${AWS::AccountId}:user/"
              - ${aws:username}
      Groups:
      - !Ref IamGroup

# ------------------------------------------------------------#
# IAM Users
# ------------------------------------------------------------#
# IAM User 01
  IamUser01: 
    Type: AWS::IAM::User
    Properties: 
      UserName: !Ref NameOfIamUser01
      Groups:
        - !Ref IamGroup

# Access Key 01
  AccessKey01: 
    Type: AWS::IAM::AccessKey
    Properties: 
      Status: Active
      UserName: !Ref IamUser01

# IAM User 02
  IamUser02: 
    Type: AWS::IAM::User
    Condition: CreateIamUser02True
    Properties: 
      UserName: !Ref NameOfIamUser02
      Groups:
        - !Ref IamGroup

# Access Key 02
  AccessKey02: 
    Type: AWS::IAM::AccessKey
    Condition: CreateIamUser02True
    Properties: 
      Status: Active
      UserName: !Ref IamUser02

# IAM User 03
  IamUser03: 
    Type: AWS::IAM::User
    Condition: CreateIamUser03True
    Properties: 
      UserName: !Ref NameOfIamUser03
      Groups:
        - !Ref IamGroup

# Access Key 03
  AccessKey03: 
    Type: AWS::IAM::AccessKey
    Condition: CreateIamUser03True
    Properties: 
      Status: Active
      UserName: !Ref IamUser03

# IAM User 04
  IamUser04: 
    Type: AWS::IAM::User
    Condition: CreateIamUser04True
    Properties: 
      UserName: !Ref NameOfIamUser04
      Groups:
        - !Ref IamGroup

# Access Key 04
  AccessKey04: 
    Type: AWS::IAM::AccessKey
    Condition: CreateIamUser04True
    Properties: 
      Status: Active
      UserName: !Ref IamUser04

# IAM User 05
  IamUser05: 
    Type: AWS::IAM::User
    Condition: CreateIamUser05True
    Properties: 
      UserName: !Ref NameOfIamUser05
      Groups:
        - !Ref IamGroup

# Access Key 05
  AccessKey05: 
    Type: AWS::IAM::AccessKey
    Condition: CreateIamUser05True
    Properties: 
      Status: Active
      UserName: !Ref IamUser05

# ------------------------------------------------------------#
# OUTPUTS
# ------------------------------------------------------------#
Outputs:
  01AccessKey:
    Description: Access Key of IAM User 01
    Value: !Ref AccessKey01

  01SecretAccessKey:
    Description: Secret Access Key of IAM User 01
    Value: !GetAtt AccessKey01.SecretAccessKey

  02AccessKey:
    Description: Access Key of IAM User 02
    Value: !Ref AccessKey02
    Condition: CreateIamUser02True

  02SecretAccessKey:
    Description: Secret Access Key of IAM User 02
    Value: !GetAtt AccessKey02.SecretAccessKey
    Condition: CreateIamUser02True

  03AccessKey:
    Description: Access Key of IAM User 03
    Value: !Ref AccessKey03
    Condition: CreateIamUser03True

  03SecretAccessKey:
    Description: Secret Access Key of IAM User 03
    Value: !GetAtt AccessKey03.SecretAccessKey
    Condition: CreateIamUser03True

  04AccessKey:
    Description: Access Key of IAM User 04
    Value: !Ref AccessKey04
    Condition: CreateIamUser04True

  04SecretAccessKey:
    Description: Secret Access Key of IAM User 04
    Value: !GetAtt AccessKey04.SecretAccessKey
    Condition: CreateIamUser04True

  05AccessKey:
    Description: Access Key of IAM User 05
    Value: !Ref AccessKey05
    Condition: CreateIamUser05True

  05SecretAccessKey:
    Description: Secret Access Key of IAM User 05
    Value: !GetAtt AccessKey05.SecretAccessKey
    Condition: CreateIamUser05True

CloudFormationの出力に表示されたアクセスキーとシークレットアクセスキーを控えておいてください。

ポートフォワーディング機能を利用してログインしてみる

コマンドプロンプトで、AWS CLI用のアクセスキーを設定します。

C:\>set AWS_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxxxxx

C:\>set AWS_SECRET_ACCESS_KEY=+Xd0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

必要に応じてデフォルトリージョンと出力形式も設定します。

C:\>set AWS_DEFAULT_REGION=ap-northeast-1

C:\>set AWS_DEFAULT_OUTPUT=table

インスタンスIDとポート番号(今回はEC2インスタンスがLinuxなのでSSH=22)を指定してポートフォワーディング機能を呼び出します。

C:\>aws ssm start-session --target i-xxxxxxxxxxxxxxxxx ^
--document-name AWS-StartPortForwardingSession ^
--parameters "{\"portNumber\":[\"22\"]}"

start-session コマンド実行後、ランダムに割り当てられたローカル側のポート番号が出力されます。

Starting session with SessionId: IAM-User01-xxxxxxxxxxxxxxxxx
Port 55394 opened for sessionId IAM-User01-xxxxxxxxxxxxxxxxx.
Connection accepted for session IAM-User01-xxxxxxxxxxxxxxxxx.

各クライアントで 127.0.0.1:[出力されたポート番号] を接続先に指定し、ログインします。

ログイン出来ました。

ちなみに、ローカルのポート番号を指定したい場合は以下のようにします。

C:\>aws ssm start-session --target i-xxxxxxxxxxxxxxxxx ^
--document-name AWS-StartPortForwardingSession ^
--parameters "{\"portNumber\":[\"22\"],\"localPortNumber\":[\"9999\"]}"

RDPの場合

C:\>aws ssm start-session --target i-xxxxxxxxxxxxxxxxx ^
--document-name AWS-StartPortForwardingSession ^
--parameters "{\"portNumber\":[\"3389\"]}"

アクセスキーのローテーション

話がそれますが、CloudFormationで作成したIAMユーザには自身のアクセスキーを作成・削除できる権限があります。
適宜アクセスキーをローテートしましょう。

# 現在利用しているアクセスキーを確認
C:\>echo %AWS_ACCESS_KEY_ID%
AKIAxxxxxxxxxxxxxxxx

# 新しいアクセスキーを作成
C:\>aws iam create-access-key
-------------------------------------------------------------------
|                         CreateAccessKey                         |
+-----------------------------------------------------------------+
||                           AccessKey                           ||
|+------------------+--------------------------------------------+|
||  AccessKeyId     |  AKIAxxxxxxxxxxxxxxxx                      ||
||  CreateDate      |  2019-10-02T07:24:17Z                      ||
||  SecretAccessKey |  iOTbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  ||
||  Status          |  Active                                    ||
||  UserName        |  IAM-User01                                ||
|+------------------+--------------------------------------------+|

# 新しいアクセスキーをCLIにセット
C:\>set AWS_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxxxxx

C:\>set AWS_SECRET_ACCESS_KEY=iOTbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 古いアクセスキーの削除
C:\>aws iam delete-access-key --access-key-id AKIAxxxxxxxxxxxxxxxx