こんにちは。@masatomix です。
仕事でsshのポートフォワードを整理する機会があって、そのときの作業メモとTIPSです。
開発しているときになんとなく、~/.ssh/config
とかにsshの設定を書いてsshアクセスしたり、踏み台サーバを使ってアクセスしたり、 sshの転送設定を書いて踏み台経由でWEBサーバに転送したりすると思いますが、その辺のお話です。
この辺ボンヤリとした知識でやってたんですが、踏み台のEC2インスタンスがsshアクセスではなく、AWSの Session Manager経由のところがあり、sshのポートフォワードとかをまあまあ理解しないとって状況になった、という背景です。
前提の環境
というわけで、下記のような環境を想定します。
アクセスしたい本丸のサーバは dest-ec2 (192.168.1.240)
で、ここにsshしたり、WEBサーバ疎通をするのがゴールとしましょう。
(なので、ユーザPC側にdest-ec2
にsshするための秘密鍵は持ってるモノとします)
$ 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。
一方踏み台サーバbastion-ec2
はsshアクセスはNGで、AWSのSession Manager 経由で接続するものとします。ちなみに AWSのSession Manager ってのは、いわゆる
コレですね。ssh接続に比べて「秘密鍵を管理しなくてよい」「ネットに対してインバウンドのポート(22ポートとかね)を開放しなくてよい」「アクセスの許可をIAMポリシーで管理出来る」などなどの利点がある接続方法です。
最後に、今回接続するPC側でいくつかコマンドを発行しますが、wslを想定しています。
必要なツールのインストール
PC側のwslに、AWS CLIはインストールされているものとします(必要に応じて下記を参考にしてください)。
参考: https://qiita.com/masatomix/items/7a1af1f6fc28258927ae
$ 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番ポートへの疎通も兼ねて、踏み台の先の本丸サーバまでログインしておきましょう。先ほどの手元の秘密鍵を、コピペとかで踏み台サーバにもっていって
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。
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 ~]$
Session Managerでログインした踏み台経由で、本丸サーバへsshログインする事が出来ました。
踏み台の先にポートフォワードする。
さてこれだけだと、接続に何やらアヤシゲ(?)なAWSのコマンドを使う必要があるし、いわゆる既存のsshポート転送もどう使えばよいか分からないので、もうすこしいろいろやってみます。
実は Session Managerにはポート転送の機能があり、たとえば
- localhost のポート 2222にTCP/IP接続したら
- 踏み台サーバに繋がり
- 踏み台からさらにIPアドレス(192.168.1.240) のポート22 に転送させる
などが出来ます。具体的には以下の通り。
$ 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: 転送先のポート番号
ってことですね。
というわけで、別のwslコンソールからlocalhostのポート2222に SSHで繋いでみます。
$ 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で待ち受けているとします。さきのポート転送と同じように、
--parameters localPortNumber=8888,portNumber=8080 host=192.168.1.240
ってやれば良さそうですが、先に説明したとおり本丸サーバのポート8080は踏み台からのアクセスが許可されていないため、上記のやり方では接続出来ません。
一応試しておきましょう。本丸サーバにWEBサーバを立ちあげて、
$ 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ポートへ転送させます。
$ 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してみると、、、
$ curl localhost:8888
^C
$
案の定だめでしたね。
ようやく SSHポートフォワード
さてようやくSSHポートフォワードのはなしです。いま本丸サーバは
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem
でssh接続までは可能なのでした。
AWSのSSMを用いた 8888ポートへの転送はCtrl+Cなどで停止し、代わりにSSHポートフォワードを用いてみます。具体的には下記の通り -L オプションを用います。
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で繋いでみると、、
$ curl localhost:8888
hello
$
おお、接続出来ましたね4。
ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem -L 8888:192.168.1.240:8080
のコマンドだけみると、やたらポート番号が出てきてわけがわからなくなりますが、-L までの指定はsshでログインするためのパラメタ指定、-L以降でポート転送の指定、と考えるとすっきりします。
おつかれさまでした。
まとめ
- 踏み台サーバがSession Manager経由だったとしても、
- その先のサーバ群がssh接続可能であれば
- Session Managerのポートフォワード機能でsshを転送して、
- そのトンネルを使って sshのポートフォワード機能を用いれば、他のポートにも着弾可能
ということが分かりました。
TIPS
-N オプション、-fオプション
-Nオプションはトンネルを作るだけでコンソールを返さない、-fオプションはバックグラウンドで実行する、となります(やってみるとだいたい分かります5。)
sshで入れてしまってコンソールが返ってくるとなんだかよく分からなくなってしまうため、-Nするのが良さそうです。-fは書いてみたものの、停止がメンドイのであまり使わないかな、、。
-vvv オプション
もはやssh転送関係ないですが、TCP/IP接続のデバッグをしたい場合、この詳細オプションをつけてあげましょう。
おなじみの ~/.ssh/config
を使いたい
さてさて AWS-StartPortForwardingSessionToRemoteHostしたのち、sshログインするために毎回
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem
と書いたり、sshポートフォワード するために毎回
$ ssh -l ec2-user localhost -p 2222 -i ~/.ssh/kino-KeyPair.pem -L 8888:192.168.1.240:8080
って指定するのは煩雑です。~/.ssh/config
に以下を書いておきましょう。
$ 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オプションと同じですね。一応、接続してみましょう。
$ 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でなく、コマンドプロンプトから見てみましょう。
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左下のアイコンより接続していきます。
程なくして、接続出来たようですね。
関連リンク
- SSM Session Managerを使ってポートフォワードする
- Session Manager + SSHポートフォワーディングでプライベートなインスタンスを踏み台にして2ホスト間を繋ぐ
- AWS CDK で Infrastructure as Code する: EC2編
-
正確には
bastion-ec2
に設定されたセキュリティグループからのみ接続可能、としてあります。 ↩ -
-lオプション ssh -l ec2-user 192.168.1.240 は、ssh ec2-user@192.168.1.240 と等価 ↩
-
自サーバの指定ポートに転送するだけの、
AWS-StartPortForwardingSession
などもあります。 ↩ -
この転送プロセスは、本丸サーバ(192.168.1.240)で動いているため、実際の引数は
-L 8888:localhost:8080
でもOKです。自分のポート8080への転送ってことですね。 ↩ -
バックグラウンドにはいっちゃうので、とめるときは
ps -ef |grep ssh
とかしてプロセスID突き止めてkill プロセスIDね) ↩