Systems Manager Session Managerを使ってEC2からキーペアを根絶する。

# はじめに
今までEC2インスタンスを運用するときにはEC2インスタンス作成時に`SSH`キーペアを作成して`SSH`で接続、データ転送する際には`SCP`等で転送するようなことをやっておりましたが、`Systems Manager`の機能を使うことでキーペアを使わなくて済むため、今回は`Systems Manager`サービスの1機能となる`Session Manager`を使ってキーペアを根絶してみようと思います。

- 【次】[Systems Managerのデフォルトのホスト管理設定を使ってSession Managerログインする。](https://qiita.com/sakai00kou/items/c94ae0d10a42ccb9967d)

# なぜキーペアを根絶したいのか
そもそもキーペアとは何かというと、`EC2`に`SSH`接続する際に必要となる鍵情報となります。

キーペア自身には鍵情報を定期的に更新したりする機能は無いので、意図的に更新作業など行わなければ、同じキーペアファイルで永続的に対象の`EC2`にログインすることができるものとなります。

キーペアで`EC2`に接続するためには、作業を行うクライアントやサーバにキーペアを置く必要があるので、キーペアを運用していかないといけないリスクや、キーペアが誤って公開されてしまったときのリスクと向かい合わなければならず、持たなくて済むなら持ちたくないものです。

# AWS Systems Manager Session Managerについて
`AWS Systems Manager`の`Session Manager`機能を使えばユーザのアクセス制御はIAMで行うことができ、ログインしてCLI操作を行うだけであればキーペアも不要でセキュリティグループで`SSH`を許可する必要も無いことから、`AWS`で`EC2`に接続する際にはぜひとも使ったほうが良い機能だと思います。

ただ、`Session Manager`は`SSH`とは当然異なるため、`SSH`を使うことを前提とするサービス（例えば`Ansible`）等を利用したりサーバ間通信で`SCP`等を利用している場合は、単純に`SSH`を`Session Manager`に置き換えると問題が発生してしまうので、今回の記事を参照してよく検討するようにしてください。

`Ansible`に限って言えば、`Run Command`で用意されている`Ansible`用ドキュメントを使うことでキーペアを持つことを回避することができるのでキーペア根絶の参考にしてください。

- [AnsibleをAWS Systems Managerから実行するCI/CDを構築する。（その１：Systems ManagerからのAnsible実行）](https://qiita.com/sakai00kou/items/5604b94dd2c87922538f)
- [AWS Systems ManagerでAnsible実行を行う前提でのPlaybook構成検討](https://qiita.com/sakai00kou/items/e5868597b874a73f8109)

また、今回は`EC2`の説明に限りますが、`Session Manager`と連携するための`SSM`エージェントを`Linux`サーバ等にインストールすることで、オンプレサーバへの接続も管理することが可能です。

# Session Managerによる接続手順
以下より大きく3つの項目に区切って`Session Manager`を使った`EC2`への接続について紹介していこうと思います。

最初に`Session Manager`を使った接続手順を説明する「**接続編**」、`EC2`のネットワーク構成ごとの接続手順を説明する「**構成編**」、`Session Manager`を使った`SSH`プロキシ等の手順を説明する「**応用編**」を説明していきます。

- [接続編](#接続編)
- [構成編](#構成編)
- [応用編](#応用編)

## 接続編
`EC2`が`Session Manager`と連携できるようになっていれば、AWSマネジメントコンソールもしくはCLIから接続できます。

- [事前作業](#事前作業)
    - [SSMエージェントの準備](#ssmエージェントの準備)
    - [SSMエージェントの設定](#ssmエージェントの設定)
    - [IAMロールの準備](#iamロールの準備)
- [AWSマネジメントコンソールからのEC2接続](#awsマネジメントコンソールからのec2接続)
- [AWS CLIからのEC2接続](#aws-cliからのec2接続)

### 事前作業
`Session Manager`経由で`EC2`接続するためにはSSMエージェントの準備と`EC2`から`Session Manager`への通信を許可するIAMロールの設定が必要となります。

以下よりSSMエージェントインストールの方法とIAMロールの作成を行っていきます。

#### SSMエージェントの準備
`Session Manager`経由での`EC2`接続の仕組みとして、`Session Manager`と連携するためのSSMエージェントというサービスを使ってSSMエージェントから`Session Manager`に接続を行うことで、`Session Manager`側から`EC2`を管理・接続できるようになるという仕組みとなります。

SSMエージェントは`AWS`が提供している比較的最近のマシンイメージ（`AMI`）を使用しているようであれば、すでに実装済みとなるため特に作業は不要ですが、独自のAMIを使用したり、古いAMIを使用したい場合は`EC2`にSSMエージェントを個別にインストールする必要があります。

以下、`x86_64`の`Amazon Linux 2023`に対してSSMエージェントをインストール＆サービス起動する場合の例。

```bash:x86_64のAmazon Linux 2023へのSSMエージェントインストール
sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
```

```bash:SSMエージェントサービス起動
sudo systemctl enable --now amazon-ssm-agent
```

その他OSへのインストール方法は以下参照。

- [SSM Agent の使用](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ssm-agent.html)

#### SSMエージェントの設定
`Session Manager`で`EC2`に接続を行うと、デフォルトでは「`/var/lib/amazon/ssm/[インスタンスID]/document/orchestration`」に作業ログや一時ファイルが置かれ、ログアウトしてもそのまま残り続けます。

`EC2`へログインしてのコマンド実行程度であればすぐに枯渇するようなことも無いかと思いますが、SSMエージェントは`Run Command`等でも使われ、ログだけではなく実行ファイルも置かれることから、特に`Run Command`で`Ansible`を実行する場合、`Playbook`のファイルが毎回まるごとダウンロードされるため、何度も実行しているとそれだけでディスク容量やiノードが逼迫してしまいます。

ディスクやiノード枯渇を防ぐため、以下の作業を行い、作業ログや一時ファイルがサーバ上に残らないようにしましょう。

- [SSM Agentの実行スクリプトを削除する方法](https://qiita.com/sakai00kou/items/f6e04760783b805d55d5)

#### IAMロールの準備
`EC2`から`Session Manager`へ通信することから、`EC2`にIAMロールを付与し、`EC2`から`Session Manager`への接続を許可する必要があります。

あらかじめIAMポリシーとして作成されている、`AmazonSSMManagedInstanceCore`に、`Session Manager`に必要な権限が付与されているため、こちらのポリシーを使ってIAMロールを作成します。

IAMダッシュボードより、「**ロール**」→「**ロールを作成**」で以下のようにIAMロールを作成します。

|項目|設定|備考
|:--|:--|:--|
|信頼されたエンティティタイプ|AWSのサービス||
|ユースケース|EC2||
|許可ポリシー|AmazonSSMManagedInstanceCore||
|ロール名|ec2instance-role|任意の名前を入力|
|説明|デフォルトのまま|任意の説明を入力|
|タグ|空欄|今回は設定せず|

作成したらEC2ダッシュボードより「**インスタンス**」からIAMロールを付与するEC2を選択し、「**アクション**」→「**セキュリティ**」→「**IAMロールを変更**」で作成したIAMロールを選択し、「**IAMロールの更新**」でIAMロールを付与します。

![Monosnap_20231014_204630.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/3dc1fa84-a3d4-a580-02cb-6d6dd41466a8.png)

### AWSマネジメントコンソールからのEC2接続
`EC2`のネットワーク構成的に`Session Manager`に接続できる状態になっている前提で進めます。

EC2ダッシュボードより「**インスタンス**」から接続を行いたい`EC2`を選択し、「**接続**」→「**セッションマネージャー**」タブの「**接続**」を押下します。

![Monosnap_20231014_205517.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/9a3842b1-08ed-037e-0dc6-90e385788b9d.png)

正常に接続できれば、`SSH`接続したときと同じように操作ができる画面が表示されます。

![Monosnap_20231014_205844.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/aa50bbd5-9529-4780-be49-ad4c0a1e7058.png)

また、別の方法として、`Systems Manager`ダッシュボード画面の「**セッションマネージャー**」→「**セッション**」タブから「**セッションの開始**」で接続したいEC2を選択し、「**Start session**」を押下しても接続することができます。

![Monosnap_20231014_210518.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/b3e7c522-d4dd-59c9-38a9-7e096ff705c9.png)

### AWS CLIからのEC2接続
クライアントPCから`Session Manager`で接続するためには`aws ssm start-session`コマンドで接続したい`EC2`のインスタンスIDを指定して実行することで`Session Manager`経由で`EC2`に接続することができます。

以下、`i-07e03a71e09e5921b`のインスタンスIDを持つ`EC2`に接続した場合の表示例。

```bash:AWS CLIからのEC2接続
aws ssm start-session --target i-07e03a71e09e5921b

Starting session with SessionId: xxxxx-xxxxxxxxxxxxxxxxx
sh-5.2$ exec /bin/bash
[ec2-user@ip-10-1-1-144 bin]$ cd ~/
[ec2-user@ip-10-1-1-144 ~]$ timestamp=$(date '+%Y-%m-%dT%H:%M:%SZ')
[ec2-user@ip-10-1-1-144 ~]$ echo $timestamp
2023-10-14T12:11:18Z
[ec2-user@ip-10-1-1-144 ~]$
```

前準備は必要ですが、前述の通り、セキュリティグループで`SSH`ポートを許可する必要もなく、キーペアも作成する必要が無いため、簡単にセキュアな接続構成を準備することができます。

以下からは`EC2`のネットワーク構成ごとの`Session Manager`接続方法を説明します。

## 構成編
`Session Manager`はVPC外のサービスとなるため、実際にクライアントPCから`Session Manager`経由で`EC2`に接続する場合の構成としては以下のような構成となります。

```plantuml
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/Region.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/ManagementGovernance/SystemsManagerSessionManager.puml
!include AWSPuml/General/User.puml
!include AWSPuml/AWSSimplified.puml

left to right direction
title Session Managerの構成イメージ

skinparam shadowing false
hide stereotype
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

rectangle "$UserIMG()\nユーザー" as user
AWSCloudGroup(cloud){
  RegionGroup(region, "リージョン") {
    rectangle "$SystemsManagerSessionManagerIMG()\nSession Manager" as ssm
    VPCGroup(vpc, "VPC") {
      rectangle "$EC2IMG()\nEC2" as ec2
    }
  }
}

user --> ssm : Session Manager接続
ec2 ..> ssm : Session Managerとの連携
ssm --> ec2 : Session Manager経由での接続
@enduml
```

`Session Manager`は大雑把に言うと`EC2`への接続をプロキシしてくれるようなサービスとなるため、`EC2`自身も`Session Manager`サービスと連携できるようにしないと`Session Manager`経由で接続することができないので、上述のIAMロールの設定で`EC2`から`Session Manager`へ接続できるように権限を付与する必要がありました。

`EC2`のネットワーク構成としては以下のようなパターンが考えられるかと思いますので、それぞれのパターンごとに説明していきます。

- [パブリックサブネットに所属したEC2にSession Manager接続する場合](#パブリックサブネットに所属したec2にsession-manager接続する場合)
- [プライベートサブネットに所属したEC2にSession Manager接続する場合（NATゲートウェイあり）](#プライベートサブネットに所属したec2にsession-manager接続する場合natゲートウェイあり)
- [プライベートサブネットに所属したEC2にSession Manager接続する場合（NATゲートウェイなし）](#プライベートサブネットに所属したec2にsession-manager接続する場合natゲートウェイなし)

### パブリックサブネットに所属したEC2にSession Manager接続する場合
`EC2`が直接グローバルIPアドレスを持っているパブリックサブネットに所属している場合、`EC2`はインターネット経由（厳密には`AWS`のグローバルIPネットワーク網内での通信となるためインターネット網とは異なりますが）で`Session Manager`と連携できるため、`EC2`で`SSM`エージェントが動作していれば何もせずとも`Session Manager`経由で接続することができます。

```plantuml
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/Region.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/ManagementGovernance/SystemsManagerSessionManager.puml
!include AWSPuml/General/User.puml
!include AWSPuml/Groups/PublicSubnet.puml
!include AWSPuml/NetworkingContentDelivery/VPCInternetGateway.puml
!include AWSPuml/AWSSimplified.puml

left to right direction
title パブリックサブネットでのSession Manager接続

skinparam shadowing false
hide stereotype
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

rectangle "$UserIMG()\nユーザー" as user
AWSCloudGroup(cloud){
  RegionGroup(region, "リージョン") {
    rectangle "$SystemsManagerSessionManagerIMG()\nSession Manager" as ssm
    VPCGroup(vpc, "VPC") {
      rectangle "$VPCInternetGatewayIMG()\nInternet Gateway" as igw
      PublicSubnetGroup(public, "Public Subnet") {
        rectangle "$EC2IMG()\nEC2" as ec2 #Transparent
      }
    }
  }
}

user --> ssm : Session Manager接続
ec2 ..> igw : Session Managerとの連携
igw ..> ssm : Session Managerとの連携
ssm --> igw : Session Manager接続
igw --> ec2 : Session Manager接続
@enduml
```

### プライベートサブネットに所属したEC2にSession Manager接続する場合（NATゲートウェイあり）
グローバルネットワークとNATゲートウェイ経由で接続している`EC2`インスタンスの場合、NATゲートウェイ経由で`EC2`から`Session Manager`に対して接続できることから、パブリックサブネット所属の`EC2`の時と同様、`EC2`で`SSM`エージェントが動作していれば`Session Manager`経由で接続することができます。

```plantuml
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/Region.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/ManagementGovernance/SystemsManagerSessionManager.puml
!include AWSPuml/General/User.puml
!include AWSPuml/Groups/PrivateSubnet.puml
!include AWSPuml/NetworkingContentDelivery/VPCInternetGateway.puml
!include AWSPuml/NetworkingContentDelivery/VPCNATGateway.puml
!include AWSPuml/AWSSimplified.puml

left to right direction
title プライベートサブネットでのSession Manager接続（NAT Gatewayあり）

skinparam shadowing false
hide stereotype
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

rectangle "$UserIMG()\nユーザー" as user
AWSCloudGroup(cloud){
  RegionGroup(region, "リージョン") {
    rectangle "$SystemsManagerSessionManagerIMG()\nSession Manager" as ssm
    VPCGroup(vpc, "VPC") {
      rectangle "$VPCInternetGatewayIMG()\nInternet Gateway" as igw
      rectangle "$VPCNATGatewayIMG()\nNAT Gateway" as ngw
      PrivateSubnetGroup(private, "Private Subnet") {
        rectangle "$EC2IMG()\nEC2" as ec2 #Transparent
      }
    }
  }
}

user --> ssm : Session Manager接続
ec2 ..> ngw : Session Managerとの連携
ngw ..> igw : Session Managerとの連携
igw ..> ssm : Session Managerとの連携
ssm --> igw : Session Manager接続
igw --> ngw : Session Manager接続
ngw --> ec2 : Session Manager接続
@enduml
```

### プライベートサブネットに所属したEC2にSession Manager接続する場合（NATゲートウェイなし）
グローバルネットワークと全く繋がっていないセグメントに所属する`EC2`インスタンスに接続する場合、そのままでは`EC2`から`Session Manager`へ接続するための経路がなく、`Session Manager`と連携することができないため、`Session Manager`と裏から接続するための`VPC`エンドポイントを作ってあげる必要があります。

`VPC`エンドポイントを作成することで、通常、`AWS`のグローバルネットワーク経由で繋ぐ必要がある`Session Manager`との接続を内部のプライベートネットワークから直接`Session Manager`へ接続することができるようになります。

```plantuml
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/Region.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/ManagementGovernance/SystemsManagerSessionManager.puml
!include AWSPuml/General/User.puml
!include AWSPuml/Groups/PrivateSubnet.puml
!include AWSPuml/NetworkingContentDelivery/VPCEndpoints.puml
!include AWSPuml/NetworkingContentDelivery/VPCElasticNetworkInterface.puml
!include AWSPuml/AWSSimplified.puml

left to right direction
title プライベートサブネットでのSession Manager接続（NAT Gatewayなし）

skinparam shadowing false
hide stereotype
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

rectangle "$UserIMG()\nユーザー" as user
AWSCloudGroup(cloud){
  RegionGroup(region, "リージョン") {
    rectangle "$SystemsManagerSessionManagerIMG()\nSession Manager" as ssm
    VPCGroup(vpc, "VPC") {
      rectangle "$VPCEndpointsIMG()\nVPC Endpoint" as endpoint
      PrivateSubnetGroup(private, "Private Subnet") {
        rectangle "$VPCElasticNetworkInterfaceIMG()\nENI" as eni #Transparent
        rectangle "$EC2IMG()\nEC2" as ec2 #Transparent
      }
    }
  }
}

user --> ssm : Session Manager接続
ec2 ..> eni : Session Managerとの連携
eni ..> endpoint : Session Managerとの連携
endpoint ..> ssm : Session Managerとの連携
ssm --> endpoint : Session Manager接続
endpoint --> eni : Session Manager接続
eni --> ec2 : Session Manager接続
@enduml
```

以下より`VPC`エンドポイントの作成方法を説明します。

#### VPCエンドポイントとは
`EC2`から内部のプライベートネットワークで`Session Manager`と接続するには、`PrivateLink`といった機能を使用して`VPC`エンドポイントを設定する必要があります。

ざっくり言うと、該当のプライベートネットワークに`Session Manager`サービスと直接接続している仮想ネットワークインタフェース（`ENI`）を作成することで、裏から`Session Manager`に繋げられるようにします。

もう少し`PrivateLink`について説明すると、例えば`Session Manager`というパブリックに公開されているサーバがあったとして、内部のサーバと通信するためにネットワークインタフェースを増設してプライベートセグメントにも接続できるようにするような機能となります。

- [ステップ 6: (オプション) AWS PrivateLink を使用して Session Manager の VPC エンドポイントを設定する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-getting-started-privatelink.html)

#### セキュリティグループの作成
`VPC`エンドポイントを作成する前に、`VPC`エンドポイントを設定する際に一緒に作成されるネットワークインタフェース（`ENI`）に付与するセキュリティグループを作成しておきます。

`Session Manager`との通信は443ポートで行われるため、インバウンドの443ポートを許可する設定を行います。

なぜ`EC2`から`Session Manager`への通信に対するセキュリティグループ設定なのにアウトバウンドの443ポートではなくインバウンドの443ポートを指定するのかと言うと、セキュリティグループを付与する対象は`ENI`となり、`ENI`から見た場合は、`EC2`から`ENI`に入ってくる通信（インバウンド）となるため、インバウンドの許可となります。

以下に`ENI`から見た際のセキュリティグループのイメージ。

```plantuml
@startuml
!define AWSPuml https://raw.githubusercontent.com/awslabs/aws-icons-for-plantuml/v14.0/dist
!include AWSPuml/AWSCommon.puml
!include AWSPuml/Groups/AWSCloud.puml
!include AWSPuml/Groups/VPC.puml
!include AWSPuml/Groups/Region.puml
!include AWSPuml/Compute/EC2.puml
!include AWSPuml/ManagementGovernance/SystemsManagerSessionManager.puml
!include AWSPuml/Groups/SecurityGroup.puml
!include AWSPuml/NetworkingContentDelivery/VPCEndpoints.puml
!include AWSPuml/NetworkingContentDelivery/VPCElasticNetworkInterface.puml
!include AWSPuml/AWSSimplified.puml

left to right direction
title ENIから見た場合のセキュリティグループのイメージ

skinparam shadowing false
hide stereotype
skinparam rectangle {
    BackgroundColor AWS_BG_COLOR
    BorderColor transparent
}

AWSCloudGroup(cloud){
  rectangle "$SystemsManagerSessionManagerIMG()\nSession Manager" as ssm
    rectangle "$VPCElasticNetworkInterfaceIMG()\nENI" as eni #Transparent
  rectangle "$EC2IMG()\nEC2" as ec2 #Transparent
}

ssm <-- eni
eni <-- ec2 : インバウンド通信

@enduml
```

#### VPCエンドポイントの作成
VPCダッシュボードより「**Endpoints**」→「**エンドポイントを作成**」から各エンドポイントを作成していきます。

サービスとしては以下のサービスを指定する必要があり、まとめて選択するようなことはできないことから、最大で以下6つ分の`VPC`エンドポイントを作成します。

|サービス名|必須有無|説明|
|:--|:--|:--|
|com.amazonaws.*region*.ec2messages|必須|
|com.amazonaws.*region*.ssm|必須|
|com.amazonaws.*region*.ssmmessages|必須|
|com.amazonaws.*region*.kms|オプション|暗号化オプションを使う場合に必要|
|com.amazonaws.*region*.logs|オプション|CloudWatch Logsへログ転送する場合に必要|
|com.amazonaws.*region*.s3|オプション|S3へログ転送する場合に必要|

オプションと記載した3つについては、`Session Manager`の設定によって追加で設定する必要があり、もし`Session Manager`の設定で有効にしているのにVPCエンドポイントの設定が行われていないと接続できないので注意してください。

なお、`Session Manager`の設定は`Systems Manager`ダッシュボードより、「**セッションマネージャー**」→「**設定**」タブの「**編集**」から設定できます。

![Monosnap_20231015_092908.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/d726c39b-1a55-928b-52bc-09322d8fe2d3.png)

`VPC`エンドポイントの設定は以下のように設定します。

|項目|設定|備考|
|:--|:--|:--|
|名前タグ|※任意|任意の名前を指定|
|サービスカテゴリ|AWSのサービス||
|サービス|※上記のサービスを指定||
|VPC|※VPCエンドポイントを作成するVPCを指定||
|DNS名を有効化|チェック|DNSが有効でないと作成に失敗するため、指定する|
|DNSレコードのIPタイプ|IPv4||
|サブネット|※EC2が存在するプライベートサブネットを指定||
|IPアドレスタイプ|IPv4||
|セキュリティグループ|session_manager_sg|先程作成したセキュリティグループを指定|
|ポリシー|フルアクセス|今回はフルアクセスで指定|
|タグ|空欄|今回はタグは作成しない|

上記`VPC`エンドポイントを作成することで、`EC2`から`Session Manager`へ接続できるようになります。

`EC2`から`Session Manager`へ接続する際には`DNS`で内部に作られたENIのアドレスを問い合わせて`VPC`エンドポイント経由で接続することになるため、特にルーティング等は不要です。

## 応用編
「**[接続編](#接続編)**」、「**[構成編](#構成編)**」までの手順で、キーペアを使わずに様々なネットワーク構成の`EC2`に接続することができるようになったので、一般的な使い方をする分にはここまでの手順で支障がないかと思います。

ここからは応用として、`Session Manager`を使ったポートフォワードを行ってみようと思います。

- [Session Managerを使ったポートフォワード](#session-managerを使ったポートフォワード)
- [Session Manager経由でのSSH接続](#session-manager経由でのssh接続)

### Session Managerを使ったポートフォワード
`SSH`にはクライアントとサーバ間で接続した`SSH`のセッション（`SSH`トンネル）を使って、任意のポートの通信をフォワーディングする`SSH`ポートフォワード機能がありますが、`Session Manager`にも同じようにポートフォワードする機能があります。

- [Session Manager接続するEC2へのポートフォワード](#session-manager接続するec2へのポートフォワード)
- [Session Manager接続するEC2経由で他ホストへのポートフォワード](#session-manager接続するec2経由で他ホストへのポートフォワード)

#### Session Manager接続するEC2へのポートフォワード
`Session Manager`を使って接続する`EC2`の他のポートにフォワーディングする場合、`EC2`に以下のコマンドで接続することでフォワーディングすることができます。

```bash:Session Managerを使ったポートフォワード
aws ssm start-session \
    --target [インスタンスID] \
    --document-name AWS-StartPortForwardingSession \
    --parameters '{"portNumber":["[リモートポート番号]"], "localPortNumber":["[ローカルポート番号]"]}'
```

上記コマンドを実行することで待ち受け状態となるので、例えばリモートポートをポート80、ローカルポートをポート8880とした場合、別のターミナルやブラウザを開いて`curl`コマンドやWebブラウザから`http://localhost:8880/`のようにしてクライアント自身にアクセスすると、`Session Manager`で接続している`EC2`上で動作しているWebサービスに接続できる仕組みとなります。

```bash:ポートフォワードコマンドを実行した場合の出力例
Starting session with SessionId: xxxxx-xxxxxxxxxxxxxxxxx
Port 8880 opened for sessionId xxxxx-xxxxxxxxxxxxxxxxx.
Waiting for connections...
```

![SessionManagerForwarding1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/323508ff-9891-764b-a594-26f176bf6d43.png)

なお、`Session Manager`で接続した`EC2`がプロキシのような役割になってポート80にアクセスするようなイメージとなるため、クライアントからのポート80への通信許可をセキュリティグループで行う必要はありません。

以下参考にしたURL

- [New – Port Forwarding Using AWS System Manager Session Manager](https://aws.amazon.com/jp/blogs/aws/new-port-forwarding-using-aws-system-manager-sessions-manager/)

#### Session Manager接続するEC2経由で他ホストへのポートフォワード
先程は`Session Manager`で接続している`EC2`へのポートフォワードでしたが、以下のコマンドで接続することで`Session Manager`で接続した`EC2`から更に先に存在するサービスにアクセスすることができるようになります。

```bash:Session Managerで接続したEC2経由での他ホストへのポートフォワード
aws ssm start-session \
    --target [インスタンスID] \
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters '{"host":["[ホスト名またはIPアドレス]"],"portNumber":["[リモートポート番号]"], "localPortNumber":["[ローカルポート番号]"]}'
```

指定している`document-name`が異なるのと、`parameters`に`host`の指定が加わったこと以外は変わりません。

ちなみに、ホスト指定を`localhost`にすれば`Session Manager`で接続している`EC2`自身を指定することになるので、先程の`AWS-StartPortForwardingSession`のドキュメントを使った場合と同じ動作となります。

そのため、わざわざ２つのポートフォワード用コマンドを使い分けるのではなく、`AWS-StartPortForwardingSessionToRemoteHost`のコマンドだけ使うようにしても良いかと思います。

![SessionManagerForwarding2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/14edea91-8111-8ae8-6bcc-06e1c477d291.png)

上記Webサービスへのアクセスは、`Session Manager`で接続している`EC2`を送信元ホストとしてWebサービスにアクセスすることになるため、セキュリティグループで`EC2`からアクセスできるように許可を行ってください。

### Session Manager経由でのSSH接続
先程の`Session Manager`を使ったポートフォワードの応用となりますが、ポートフォワードで`EC2`の`SSH`ポートに接続することで、セキュリティグループで`SSH`を許可しなくても`SSH`接続できるようになります。

ポートは許可する必要はありませんが、当然`SSH`接続するためのキーペアもしくはパスワードは必要となるので今回の記事としては本末転倒ですが、`SCP`や`SFTP`でデータ転送したいといった場合の参考にしてください。

また、クライアント側の`~/.ssh/config`に以下のようなプロキシコマンドを登録することで、`SSH`コマンドを実行した際に、裏で`Session Manager`の接続をした上で`Session Manager`トンネル経由で`SSH`できるようになります。

```bash:~/.ssh/configに登録するプロキシコマンド
# SSH over Session Manager
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
```

以下、プロキシコマンド登録したクライアントから`SSH`で`EC2`に接続した場合のコマンド例。

```bash:SSH接続
ssh ec2-user@[インスタンスID] -i [キーペアファイル]
```

- [ステップ 8: (オプション) Session Manager を通して SSH 接続のアクセス許可を有効にして制御する](https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html)

# おわりに
キーペアを根絶するためにはいくつか課題はあるため、システムによっては根絶ができない場合もあるかと思いますが、そのような場合でも`Session Manager`を組み合わせることで`SSH`のリスクを軽減できることがわかるかと思います。

運用で回避可能であれば、例えば`EC2`へのデータ転送は`S3`経由で行ったり、`Ansible`は`Run Command`で実行するように変更したりすれば、キーペアの根絶も可能かと思うので、なるべくキーペアを使わない運用にシフトできれば幸せになれるかと思います。