VS Code Remote-SSHで、ポート22を開けずにプライベートサブネットのEC2に接続する(SSM Session Manager編)
はじめに
こんにちは!コンサルティング部のヒスです。
開発用のEC2インスタンスに、普段使い慣れたVS Codeでそのままログインして作業したいけれど、セキュリティ要件的にEC2にパブリックIPは付与したくないし、インバウンドの22番ポートも一切開けたくない、という場面は珍しくありません。
実はこの2つ、両立できます。AWS Systems Manager(SSM)のSession Managerが提供する「SSHセッション」機能を使うと、EC2側のインバウンドルールを一切変更せずに、VS Codeの「Remote - SSH」拡張機能からプライベートサブネット内のEC2へ接続できるんです。
今回は検証用のAWS環境で、実際にこの接続方式を構築して動作確認までしてみたので、途中で実際にハマったポイントも含めて紹介します。
全体像
まず、今回検証した構成の全体像です。EC2はパブリックIPを持たないプライベートサブネットに配置し、SSM関連の通信はすべてVPCエンドポイント経由で完結させています。
ポイントは次の3つです。
- EC2にはパブリックIPを付与しない。インバウンドルールにも22番ポートの許可を追加しない
- EC2にアタッチしたIAMロールに
AmazonSSMManagedInstanceCoreポリシーを付与し、SSMマネージドインスタンスとして登録する - VPCにSSM関連のインターフェース型VPCエンドポイント(
ssm/ssmmessages/ec2messages)を作成し、EC2⇔SSMサービス間の通信をインターネットを経由せず閉じた経路にする
EC2コンソールのインスタンス詳細画面。「パブリックIPv4アドレス」欄が空欄(-)になっている

同インスタンスのセキュリティグループのインバウンドルール一覧。22番ポートの許可ルールが存在しない

なぜVS Codeがこれで繋がるのか
VS Codeの「Remote - SSH」拡張機能は、あくまで標準的なSSHプロトコルしか話せません。SSMを直接扱う機能は持っていません。
一方でAWS CLIには、aws ssm start-sessionに--document-name AWS-StartSSHSessionを指定すると、「対話シェルを渡す」のではなく「SSHプロトコルのバイト列をそのまま素通しするトンネルを作る」という特殊なモードがあります。
この2つをProxyCommandでつなぐことで、VS Code(SSHクライアント)から見ると「通常のSSH接続と同じに見えるが、実際の通信経路はSSMのトンネルを通っている」状態を作れます。
従来のSSH接続方式との比較
「結局、パブリックIP+22番ポート開放という一般的なSSH接続と何が違うのか」を整理すると以下の通りです(この方式自体、ローカルPCからVPNやDirect Connectなしにそのまま繋げる手軽な方法であり、決して悪い方法ではありません。あくまで前提条件が異なる、という比較です)。
| 項目 | 通常のSSH(パブリックIP+22番ポート開放) | SSM Session Manager経由 |
|---|---|---|
| 接続先の指定方法 | IPアドレス | インスタンスID |
| EC2のパブリックIP | 必要 | 不要 |
| インバウンドの22番ポート | 必要(送信元IPで制限するのが一般的) | 不要(一切開けない) |
| クライアント側のネットワーク到達性 | インターネット経由で直接到達可(VPN等は不要) | インターネット経由でAWSのSSM APIに届けばよい(こちらもVPN等は不要) |
| 自分のIPアドレスが変わった場合 | セキュリティグループの許可ルールをその都度更新する必要がある | 特に対応不要(IAM認証情報があれば良い) |
| アクセス制御の単位 | セキュリティグループ(送信元IPというネットワーク単位) | IAMポリシー(タグ条件などで柔軟に制御可能) |
| インターネットからの見え方 | パブリックIP・ポートは存在する(アクセス元を絞っていても「露出」自体はしている) | パブリックIPなし・開いているポートなし。EC2がインターネットから完全に不可視 |
| 接続の監査ログ | 標準では別途仕組みが必要 | CloudTrail+Session Managerのログ機能で標準対応 |
どちらの方式もVPNや踏み台サーバーを別途用意する必要はなく、ローカルPCから直接接続できる点は同じです。違いが出るのは「EC2側に何を持たせるか」で、SSM方式はパブリックIPも開いているポートも一切持たせずに済む分、EC2自体をインターネットから完全に不可視にできること、そして自分の作業環境のIPアドレスが変わってもセキュリティグループを触らずに済むことが実質的なメリットです。
事前準備
| ツール | 用途 |
|---|---|
| AWS CLI v2 | aws ssmコマンドの実行 |
| Session Manager Plugin | aws ssm start-sessionを実際に動かすためのプラグイン |
| VS Code拡張機能「Remote - SSH」(発行元: Microsoft) | IDEへのSSH接続に必須 |
EC2側の要件は以下の通りです。
- IAMロールに
AmazonSSMManagedInstanceCoreポリシーをアタッチ - SSM用インターフェース型VPCエンドポイント(
ssm/ssmmessages/ec2messages)をVPC内に作成し、EC2のセキュリティグループから443番ポートで到達可能にする - SSH公開鍵認証用のキーペアをEC2に設定(
KeyName)
VS Codeの拡張機能タブで「Remote - SSH」がインストール済みであること

Systems Manager「フリートマネージャー」画面。対象EC2が「マネージド」ステータスで表示されている

~/.ssh/configの設定
ローカルPC側で必要な設定は、実質これだけです。
Host my-ec2-via-ssm
HostName <EC2インスタンスID>
User ec2-user
IdentityFile ~/.ssh/my-ec2-key.pem
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
ProxyCommand sh -c "aws ssm start-session --region ap-northeast-1 --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"
HostNameには(IPアドレスではなく)EC2インスタンスIDを直接指定します。SSMはインスタンスIDでターゲットを識別するためですIdentityFileは、EC2に設定した公開鍵に対応する秘密鍵StrictHostKeyChecking no/UserKnownHostsFile /dev/nullは必須ではありませんが、後述の理由で設定を強く推奨します
これで、VS Codeのコマンドパレットから「Remote-SSH: Connect to Host」→ xxx-poc-xxx を選ぶだけで接続できます。EC2のインバウンドルールは最初から最後まで一切変更していません。


接続完了後、VS Codeの左下ステータスバーに「SSH:XXX-poc-XXX」が表示されています



ハマったポイント2選
以下は参考までに、実際に手を動かしていて遭遇した2つのつまずきです。片方は単純な設定ミス、もう片方は知らないと誰でも引っかかりやすい仕様のクセなので、こんなこともあったという参考程度に紹介します。
その1: 「Opening Remote...」から何分経っても進まない
VS Codeで接続を試みると、Opening Remote...という表示のまま、体感5分以上待っても一向に進まない現象に遭遇しました。
最初はNAT Gatewayの設定漏れやインスタンスサイズ不足を疑いましたが、原因は無関係でした。SSH初回接続時に表示される「このホストを信頼しますか?(yes/no)」という確認プロンプトに、誰も応答していなかっただけです。
このプロンプトは通常のターミナルなら目立ちますが、VS Code経由だと画面の目立たない場所に小さく表示されるため、気づかずに固まっていると誤認しやすいです。~/.ssh/configに以下を追加することで、このプロンプト自体をスキップできます。
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
(本番運用でホストキー検証を無効化する場合は、なりすまし対策の要否を別途検討してください。個人検証環境では実用上の割り切りとして無効化しています。)
その2: Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
複数台のEC2をCloudFormationで構築していたところ、1台だけこのエラーで接続を拒否されました。
原因を調べると、単純な設定漏れでした。そのEC2インスタンスのリソース定義にKeyName(キーペアの指定)を書き忘れていたため、EC2側に有効な公開鍵がそもそも登録されていなかったのです。他のインスタンスには正しく設定していたため、なおさら気づきにくいミスでした。
# NG: KeyNameの指定漏れ
MyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref PrivateSubnet
# KeyNameがない!
# OK
MyInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref LatestAmiId
InstanceType: t3.micro
SubnetId: !Ref PrivateSubnet
KeyName: !Ref MyKeyPair
Permission denied系のエラーが出た場合、SSM/VPCエンドポイント側の設定を疑う前に、まず「そもそもそのEC2に正しい公開鍵が登録されているか」を確認するのが近道です。
まとめ
今回のポイントは、SSMのAWS-StartSSHSessionをProxyCommand経由で挟むだけで、EC2側にパブリックIPもインバウンド22番ポートも要らなくなるという点です。接続が固まって見えたらまずホストキー確認プロンプトを見落としていないか、Permission deniedが出たらSSM側より先にEC2の鍵登録(KeyNameなど)を疑ってみてください。
インバウンドSSHを開けられない環境でも、普段使っているエディタでそのまま開発できるのはやっぱり楽です。同じような制約で困っている方の参考になれば幸いです。







