Session Manager経由のEC2アクセスから、SSHポートフォワードまでのメモ

こんにちは。@masatomix です。
仕事でsshのポートフォワードを整理する機会があって、そのときの作業メモとTIPSです。

開発しているときになんとなく、``~/.ssh/config`` とかにsshの設定を書いてsshアクセスしたり、踏み台サーバを使ってアクセスしたり、 sshの転送設定を書いて踏み台経由でWEBサーバに転送したりすると思いますが、その辺のお話です。

この辺ボンヤリとした知識でやってたんですが、**踏み台のEC2インスタンスがsshアクセスではなく、AWSの Session Manager経由**のところがあり、sshのポートフォワードとかをまあまあ理解しないとって状況になった、という背景です。



## 前提の環境

というわけで、下記のような環境を想定します。

![image-20250426001428513](https://imgur.com/QsiFhyQ.png)

アクセスしたい本丸のサーバは ``dest-ec2 (192.168.1.240)``で、ここにsshしたり、WEBサーバ疎通をするのがゴールとしましょう。
(なので、ユーザPC側に``dest-ec2`` にsshするための秘密鍵は持ってるモノとします)

```console
$ ls -lrt ~/.ssh/kino-KeyPair.pem 
-r-------- 1 sysmgr sysmgr 1679 Apr 23 10:32 /home/sysmgr/.ssh/kino-KeyPair.pem
```

その手前には、いわゆる踏み台サーバ ``bastion-ec2``があるとします。
で、本丸``dest-ec2`` へのアクセスは、踏み台サーバ ``bastion-ec2``からのみ可能としてあります[^1]。

[^1]: 正確には``bastion-ec2`` に設定されたセキュリティグループからのみ接続可能、としてあります。

一方踏み台サーバ``bastion-ec2``はsshアクセスはNGで、**AWSのSession Manager 経由で接続する**ものとします。ちなみに AWSのSession Manager ってのは、いわゆる


![image-20250422184547713](https://imgur.com/dqbsCs3.png)

コレですね。ssh接続に比べて「秘密鍵を管理しなくてよい」「ネットに対してインバウンドのポート(22ポートとかね)を開放しなくてよい」「アクセスの許可をIAMポリシーで管理出来る」などなどの利点がある接続方法です。

最後に、今回接続するPC側でいくつかコマンドを発行しますが、wslを想定しています。



### 必要なツールのインストール

PC側のwslに、AWS CLIはインストールされているものとします(必要に応じて下記を参考にしてください)。

参考: https://qiita.com/masatomix/items/7a1af1f6fc28258927ae

```console
$ aws --version
aws-cli/2.13.17 Python/3.11.5 Linux/5.15.90.1-microsoft-standard-WSL2 exe/x86_64.ubuntu.22 prompt/off

$ session-manager-plugin
session-manager-plugin: command not found (インストールされていない)

$ curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 3816k  100 3816k    0     0   402k      0  0:00:09  0:00:09 --:--:--  796k

$ sudo dpkg -i session-manager-plugin.deb
[sudo] password for sysmgr:
Selecting previously unselected package session-manager-plugin.
(Reading database ... 49341 files and directories currently installed.)
Preparing to unpack session-manager-plugin.deb ...
Preparing for install
Unpacking session-manager-plugin (1.2.707.0-1) ...
Setting up session-manager-plugin (1.2.707.0-1) ...
Creating symbolic link for session-manager-plugin

$ session-manager-plugin
The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
$
```

インストール完了です。



## やってみる

### インスタンスIDを特定して、踏み台サーバへログインする

AWS CLIの Session Managerの機能で、踏み台サーバへログインします。ログインするには踏み台サーバのインスタンスIDが必要なので、まずはそれを調べてみます。

```
$ profile=xxx (プロファイル指定は、必要に応じて)
$ aws ssm describe-instance-information --profile ${profile}
{
    "InstanceInformationList": [
        {
            "InstanceId": "i-0abb65330e171512d",
            "PingStatus": "Online",
            "LastPingDateTime": "2025-04-21T17:45:39.609000+09:00",
            "AgentVersion": "3.1.1927.0",
            "IsLatestVersion": false,
            "PlatformType": "Linux",
            "PlatformName": "Amazon Linux",
            "PlatformVersion": "2023",
            "ResourceType": "EC2Instance",
            "IPAddress": "192.168.xx.10",
            "ComputerName": "ip-192-168-xx-10.ap-northeast-1.compute.internal",
            "SourceId": "i-0abb65330e171512d",
            "SourceType": "AWS::EC2::Instance"
        }
    ]
}
```

踏み台のインスタンスID(``i-0abb65330e171512d``)が特定できました。このインスタンスへのログインにはsshはつかえずSession Manager で接続するので、下記のコマンドを実行します。

```
$ instance_id=i-0abb65330e171512d
$ aws ssm start-session --target ${instance_id} --profile ${profile}

Starting session with SessionId: kino@example.com-vfxbak3l2uj8srn7vlnbu4s688
sh-5.2$
```

入れました。うまくいったようですね！



いちおう、本丸サーバ``dest-ec2``の22番ポートへの疎通も兼ねて、踏み台の先の本丸サーバまでログインしておきましょう。先ほどの手元の秘密鍵を、コピペとかで踏み台サーバにもっていって

``` console
sh-5.2$ ls -lrt ~/.ssh/kino-KeyPair.pem 
-r--------. 1 ssm-user ssm-user 1679 Apr 23 01:39 /home/ssm-user/.ssh/kino-KeyPair.pem
sh-5.2$ 
```

この状態で、sshします[^2]。

```console
sh-5.2$ ssh -l ec2-user 192.168.1.240 -i ~/.ssh/kino-KeyPair.pem 

Run "/usr/bin/dnf check-release-update" for full release and version update info
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
Last login: Wed Apr 23 01:58:21 2025 from 192.168.1.10
[ec2-user@ip-192-168-1-240 ~]$
```

[^2]:  -lオプション ssh -l ec2-user 192.168.1.240 は、ssh ec2-user@192.168.1.240 と等価

Session Managerでログインした踏み台経由で、本丸サーバへsshログインする事が出来ました。



### 踏み台の先にポートフォワードする。

さてこれだけだと、接続に何やらアヤシゲ(？)なAWSのコマンドを使う必要があるし、いわゆる既存のsshポート転送もどう使えばよいか分からないので、もうすこしいろいろやってみます。

実は Session Managerにはポート転送の機能があり、たとえば

- localhost のポート 2222にTCP/IP接続したら
- 踏み台サーバに繋がり
- 踏み台からさらにIPアドレス(192.168.1.240) のポート22 に転送させる

などが出来ます。具体的には以下の通り。

```console
$ aws ssm start-session --target ${instance_id} --profile ${profile} \
 --document-name AWS-StartPortForwardingSessionToRemoteHost \
 --parameters localPortNumber=2222,portNumber=22,host=192.168.1.240
 

Starting session with SessionId: kino@example.com-y87fhnrz2gykocvy4x9uaqi22y
Port 2222 opened for sessionId kino@example.com-y87fhnrz2gykocvy4x9uaqi22y.
Waiting for connections...

Connection accepted for session [kino@example.com-y87fhnrz2gykocvy4x9uaqi22y]
```

なにやら待ち受け状態になりました。
パラメタをみたらだいたい意味は明らかですが、

- AWS-StartPortForwardingSessionToRemoteHostで指定サーバの指定ポートへ転送する機能を使う[^3]
- localPortNumber=2222: localhost の待ち受けポート番号
- host=192.168.1.240 : 転送先のサーバ名/IP
- portNumber=22: 転送先のポート番号

ってことですね。 

[^3]: 自サーバの指定ポートに転送するだけの、``AWS-StartPortForwardingSession`` などもあります。



というわけで、別のwslコンソールから**localhostのポート2222に** SSHで繋いでみます。


```console
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem 

Run "/usr/bin/dnf check-release-update" for full release and version update info
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
Last login: Wed Apr 23 01:58:25 2025 from 192.168.1.10
[ec2-user@ip-192-168-1-240 ~]$ 
```

おお、踏み台サーバの転送機能を経由して、本丸サーバにsshで接続することが出来ました。



### セキュリティグループで開放されていないほかのポート転送はどうする

さて、SSHのポート22はセキュリティグループで開放されていましたが、それ以外のポート番号についてです。例えば、本丸サーバ``dest-ec2``は実はWEBサーバで、ポート8080で待ち受けているとします。さきのポート転送と同じように、

```console
 --parameters localPortNumber=8888,portNumber=8080 host=192.168.1.240
```

ってやれば良さそうですが、先に説明したとおり本丸サーバのポート8080は踏み台からのアクセスが許可されていないため、上記のやり方では接続出来ません。

一応試しておきましょう。本丸サーバにWEBサーバを立ちあげて、

```console
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem
Last login: Fri Apr 25 12:47:11 2025 from 192.168.1.10

[ec2-user@ip-192-168-1-240 ~]$ 
[ec2-user@ip-192-168-1-240 ~]$ sudo yum -y install nc
[ec2-user@ip-192-168-1-240 ~]$ while true; do { echo -e 'HTTP/1.1 200 OK\r\n'; echo 'hello'; } | nc -l 8080; done
(8080へTCP/IPで繋ぐと、応答するヤツをつくりました)
```

別のwslで、AWS SSMの機能で localhost の8888ポートへのアクセスを本丸の8080ポートへ転送させます。

```console
$  aws ssm start-session --target ${instance_id} --profile ${profile} \
 --document-name AWS-StartPortForwardingSessionToRemoteHost \
 --parameters localPortNumber=8888,portNumber=8080,host=192.168.1.240

Starting session with SessionId: kino@example.com-bb4qqviplgjakhp4obcu6gtqi4
Port 8888 opened for sessionId kino@example.com-bb4qqviplgjakhp4obcu6gtqi4.
Waiting for connections...
```

待ち受け状態にはなりましたが、curlしてみると、、、

```console
$ curl localhost:8888
^C
$
```

案の定だめでしたね。



### ようやく SSHポートフォワード

さてようやくSSHポートフォワードのはなしです。いま本丸サーバは

```console
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem
```

でssh接続までは可能なのでした。

AWSのSSMを用いた 8888ポートへの転送はCtrl+Cなどで停止し、代わりにSSHポートフォワードを用いてみます。具体的には下記の通り -L オプションを用います。

```console
Connection accepted for session [kino@example.com-bb4qqviplgjakhp4obcu6gtqi4]
(Ctrl+cします)
^CTerminate signal received, exiting.

Exiting session with sessionId: kino@example.com-bb4qqviplgjakhp4obcu6gtqi4.

$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem -L 8888:192.168.1.240:8080
[ec2-user@ip-192-168-1-240 ~]$
```

なんだかssh接続出来たようですが`` -L 8888:192.168.1.240:8080``の意味は、

- この本丸サーバへ接続しているSSHを用いて、
- localhost のポート8888への接続を
- 192.168.1.240の8080ポートへ転送する 

って意味です。ということで先のwslからcurlで繋いでみると、、



```console
$ curl localhost:8888
hello
$
```

おお、接続出来ましたね[^4]。

```console
ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem -L 8888:192.168.1.240:8080
```

のコマンドだけみると、やたらポート番号が出てきてわけがわからなくなりますが、-L までの指定はsshでログインするためのパラメタ指定、-L以降でポート転送の指定、と考えるとすっきりします。

[^4]: この転送プロセスは、本丸サーバ(192.168.1.240)で動いているため、実際の引数は``-L 8888:localhost:8080``でもOKです。自分のポート8080への転送ってことですね。

おつかれさまでした。

## まとめ

- 踏み台サーバがSession Manager経由だったとしても、
- その先のサーバ群がssh接続可能であれば
- Session Managerのポートフォワード機能でsshを転送して、
- そのトンネルを使って sshのポートフォワード機能を用いれば、他のポートにも着弾可能

ということが分かりました。



## TIPS

### -N オプション、-fオプション

-Nオプションはトンネルを作るだけでコンソールを返さない、-fオプションはバックグラウンドで実行する、となります(やってみるとだいたい分かります[^5]。)

sshで入れてしまってコンソールが返ってくるとなんだかよく分からなくなってしまうため、-Nするのが良さそうです。-fは書いてみたものの、停止がメンドイのであまり使わないかな、、。

[^5]: バックグラウンドにはいっちゃうので、とめるときは``ps -ef |grep ssh``とかしてプロセスID突き止めてkill プロセスIDね)

### -vvv オプション

もはやssh転送関係ないですが、TCP/IP接続のデバッグをしたい場合、この詳細オプションをつけてあげましょう。





### おなじみの ``~/.ssh/config``  を使いたい

さてさて AWS-StartPortForwardingSessionToRemoteHostしたのち、sshログインするために毎回

```console
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem
```

と書いたり、sshポートフォワード するために毎回

```console
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem -L 8888:192.168.1.240:8080
```

って指定するのは煩雑です。``~/.ssh/config``  に以下を書いておきましょう。

```console
$ cat ~/.ssh/config
Host dest-ec2
  HostName localhost
  User ec2-user
  Port 2222
  IdentityFile "/home/sysmgr/.ssh/kino-KeyPair.pem"

Host dest-ec2-web
  HostName localhost
  User ec2-user
  Port 2222
  IdentityFile "/home/sysmgr/.ssh/kino-KeyPair.pem"
  LocalForward 8888 192.168.1.240:8080
$
```

``~/.ssh/config``  の指定もパッと見るとややこしく見えるけど、よくよく分解してみると -Lオプションと同じですね。一応、接続してみましょう。

```console
$ ssh dest-ec2

Run "/usr/bin/dnf check-release-update" for full release and version update info
   ,     #_
   ~\_  ####_        Amazon Linux 2023
  ~~  \_#####\
  ~~     \###|
  ~~       \#/ ___   https://aws.amazon.com/linux/amazon-linux-2023
   ~~       V~' '->
    ~~~         /
      ~~._.   _/
         _/ _/
       _/m/'
Last login: Fri Apr 25 13:50:26 2025 from 192.168.1.10
[ec2-user@ip-192-168-1-240 ~]$
```

お疲れさまでした！



###  VSCodeから踏み台経由でサーバに接続する

VSCodeから踏み台サーバ経由で本丸サーバに接続してみます。この機能はVSCode上でリモートのファイルを開いたりリモートのターミナルを操作できたりなど、超便利な機能です。

VSCodeはデフォルトでは``%userprofile%\.ssh\config`` の設定を参照するようなので、そちらに記載をします。wslでなく、コマンドプロンプトから見てみましょう。


```:%userprofile%\.ssh\config
C:\Users\xxx> type %userprofile%\.ssh\config

Host dest-ec2
  HostName localhost
  User ec2-user
  Port 2222
  IdentityFile "C:/Users/xxx/.ssh/kino-KeyPair.pem"
```

さらにVSCodeやコマンドプロンプトから上記のファイルを読み込んだ場合はwslのディレクトリのファイルを正しく扱えない場合があるみたいなので(当方の環境ではPermission関連で怒られた)、上記のように別の場所に秘密鍵ファイルを配置することにしました。


さて、AWS-StartPortForwardingSessionToRemoteHostしたのち、VSCode左下のアイコンより接続していきます。




![vs01](https://imgur.com/nIHc5ZA.png)

![vs02](https://imgur.com/zhDLCRR.png)


程なくして、接続出来たようですね。

![vs03](https://imgur.com/XUCm7eh.png)






## 関連リンク

- [SSM Session Managerを使ってポートフォワードする](https://dev.classmethod.jp/articles/ssm-session-manage-port-forwarding/)
- [Session Manager + SSHポートフォワーディングでプライベートなインスタンスを踏み台にして２ホスト間を繋ぐ](https://dev.classmethod.jp/articles/access-private-host-in-another-nw-over-ssh-tunnel-and-session-manager/)
- [AWS CDK で Infrastructure as Code する: EC2編
](https://qiita.com/masatomix/items/03dd59929ef1869ea027)