aws icon
EC2 Instance Connect Endpointを利用してEC2インスタンスにVSCode Remote SSHする
投稿日: 2023年7月29日

みなさん、EC2インスタンスにSSHで接続する際にどのような方法を使っていますか?EC2インスタンスに接続する方法はこれまで

  • EC2にグローバルIPを付与して直接SSH
  • SSMのSession Managerを利用してシェルに接続
  • EC2 Instance Connectを利用して公開鍵を送り込みSSH

といくつか選択肢がありました。従来はSSMのSession ManagerがグローバルIP不要かつインバウンドの穴が完全に不要という点で一歩リードしていた印象でしたが、最近追加されたEC2 Instance Connect Endpointによって完全に状況が変わったと認識しています。今回はEC2 Instance Connect Endpointの紹介と、その活用方法としてVSCodeに接続する方法を紹介したいと思います。

従来のEC2 Instance Connectでは

🔗

まず前提知識としてEC2 Instance Connectを紹介したいと思います。EC2 Instance ConnectはIAM権限を使ってEC2インスタンスにSSHの公開鍵を送り込むことができる機能です。EC2 Instance Connectが接続したいEC2インスタンスにインストールされていることが前提になりますが、最近のAMIから起動されたインスタンスであればプリインストールされていて特に気にしなくても最初から利用できることが多いと思います。

利用方法ですが、

https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-connect-methods.html

に記載されている通り、コンソールでの接続する方法と直接SSHを行う方法があります。直接SSHを行う方法では、接続のために公開鍵を一時的に登録するために下記のコマンドを実行する必要があります。

1aws ec2-instance-connect send-ssh-public-key \
2    --instance-id <インスタンスID> \
3    --instance-os-user ec2-user \
4    --ssh-public-key file://<公開鍵ファイルのパス>

このコマンドを実行すると60秒だけ(!)このSSH鍵を利用してSSH接続ができるようになります。

ここで重要な点なんですが、従来のEC2 Instance ConnectはあくまでSSH鍵をセキュアに登録することができるだけで、SSH鍵を登録した後の実際の疎通はネットワーク的に疎通できる環境が必要となります。なので、EC2インスタンスにグローバルIPを付与した上で、

  • コンソールから接続する場合は特定のIP(コマンドで確認する必要あり)からの接続を許可する必要がある
  • 直接SSHをする場合、接続元のIPからの接続を許可する必要がある

と言った感じでネットワーク的な制約は多く残る形でした。

EC2 Instance Connect Endpointでは

🔗

それでは、EC2 Instance Connect Endpointでは何が変わるのでしょうか。EC2 Instance Connect EndpointではVPC内のプライベートサブネットにインターフェース形のVPC Endpointが作成され、EC2インスタンスはそのエンドポイントを通じて疎通を行う形になります。そのため、接続したいEC2インスタンスがグローバルIPを持つ必要がなくなりますし、セキュリティグループにもエンドポイントに対する穴のみで十分になります。EC2 Instance Connect Endpointの概要図は下記のようになります。(公式ドキュメントから図を引用しています。)

ec2_instance_connect_endpoint

しかし、当然ながらただEC2インスタンスと疎通できるエンドポイントができただけでは自分のIPからインスタンスへの疎通性が確保されるわけではないので、その部分の疎通性を確保するための方法が必要になります。その方法ですが、エンドポイントを介したwebsocketによってインスタンスへの疎通性を確保するopen-tunnelというコマンドがawscliに追加されています。

これにsend-ssh-public-keyによるSSH鍵の登録を組み合わせることで、任意のネットワークからEC2インスタンスに対するSSH接続が実現されます。

実際の接続方法

🔗

awscliではEC2 Instance Connect Endpointによる接続方法として2つのオプションが提供されています。

一つ目は最もシンプルな方法で、sshというサブコマンドが追加されているので、それを使うことです。sshコマンドはsend-ssh-public-keyopen-tunnelをラップしているコマンドと思われるもので、

1aws ec2-instance-connect ssh \
2  --instance-id ${instance_id} \
3  --private-key-file ${path_to_private_key}
4  --os-user ${os_user}

という割とシンプルなコマンドでいきなりSSH接続ができます。ただインスタンスにSSHをしたいだけならこのコマンドを使うだけでも十分便利かつセキュアになると思います。(IAMの権限による認可が行われているため、事前にAssumeRoleなどを行っておく必要がある点はご注意ください)

もう一つの方法は手動でSSH鍵の登録とトンネル形成を行う方法です。

1# 公開鍵の登録
2aws ec2-instance-connect send-ssh-public-key \
3    --instance-id ${instance_id} \
4    --instance-os-user ${os_user} \
5    --ssh-public-key file://${path_to_public_key}
6# ProxyCommandを利用してトンネルを経由したSSHを実行
7ssh -i ${path_to_private_key} ${os_user}@${instance_id} \
8  -o ProxyCommand='aws ec2-instance-connect open-tunnel --instance-id %h'

というコマンドを実行することで、生のsshコマンドでSSH接続を確立することができます。この方法の便利なところは、標準のsshに収めることができているため、scpなどのSSHを利用したコマンドを利用することができる点です。また、接続先がIPアドレスでなくインスタンスIDで指定できる点にも注目です。EC2インスタンスでは再起動を行う度にIPアドレスが変わってしまうため、その度にssh_configを書き換えるのが非常に面倒でしたが、インスタンスIDは再起動されても変わらないため、一度設定を記載してしまえば何度再起動しても設定を変える必要がなくなります。ssh_configに

1Host instance1
2  HostName <インスタンスID>
3  User <OSユーザ名>
4  IdentityFile <秘密鍵のパス>
5  ProxyCommand aws ec2-instance-connect open-tunnel --instance-id %h

のように設定を追加してあげると、単に

1ssh instance1

だけで接続できて大変お手軽かつセキュアになります。(直前にsend-ssh-public-keyを実行する必要があるので、忘れないようにご注意ください!)

これRemote SSHにも使えるんじゃね?

🔗

前置きが長くなってしまったのですが、本題に移りたいと思います。前述の通り

この方法の便利なところは、標準のsshに収めることができているため、scpなどのSSHを利用したコマンドを利用することができる点です。

なんですが、SSHを使った便利なものといえばRemote SSH(偏見)なので、Remote SSHに使えるようにしたら死ぬほど便利なのでは?と思ったので、実際にできるようにしてみました。

Remote SSHではssh_configを参照して接続先を指定することができるので、先ほど設定したconfigを使えば問題なくRemote SSHが起動してくれそうな感じがしますが、下記の2点の問題がありこのままでは動きません。

  • 事前にSSH鍵を登録するsend-ssh-public-keyを実行する必要がある
  • EC2 Instance Connect Endpointの仕組み自体がIAMによる認可に依存しているため、ProxyCommandで実行しているopen-tunnelが権限エラーになってしまう

そのため、これらの問題を突破するために、シェルスクリプトを作成します。シェルスクリプトとは言っても、単純に必要な処理を羅列しただけの簡単なものです。

~/ec2_instance_connect.sh
1#!/bin/bash
2
3# なんらかの方法でAssumeRoleをする。今回はawsumeというツールを使う
4. awsume <プロファイル名>
5
6# os_userなどをスクリプトにハードコードしたくないので、引数で受け取る
7aws ec2-instance-connect send-ssh-public-key \
8    --instance-id $1 \
9    --instance-os-user $2 \
10    --ssh-public-key file://$3
11aws ec2-instance-connect open-tunnel --instance-id $1

その上で、ssh_configを書き換えます。

1Host instance1
2  HostName <インスタンスID>
3  User <OSユーザ名>
4  IdentityFile <秘密鍵のパス>
5  ProxyCommand ~/ec2_instance_connect.sh %h <OSユーザ名> <公開鍵のパス>

これで接続できるようになりました! 基本的に上記の設定で問題ないんですが、VSCode Remote SSHは接続時にインタラクティブなコマンドを実行できない点には注意が必要です。インタラクティブなコマンドが必要になるケースとしては、Assume Roleの際のMFAの入力などが挙げられると思います。私もMFAが必要な環境で作業を行っているのですが、こちらだけは筋のよい解決策が見つかってはいないのですが、緩和する方法は見つかっているので最後にそれを共有させてもらえればと思います。

MFAを乗り越える

🔗

AWSでAssumeRoleを管理するツールは数多くありますが、私はawsumeというツールを利用しています(先ほどのシェルスクリプトでもシレッと使ってたりします)。このツールは

  • python製のツールでpipで簡単にインストールできる
  • MFAの扱いが楽
  • .aws/configや.aws/credentialsを読んでくれる

といった部分が便利になっています。今回特に重要なのが、「MFAの扱い」の部分で、一度MFAを突破すると有効期限中はawsumeでAssumeRoleを行ってもMFAが再度要求されなくなります。なので、awsumeを使っていれば、事前に一度だけMFAを通して置きさえすれば、MFAが必要な環境でもRemote SSHが利用できるようになります。

今回は以上です。非常に便利な機能なので、ぜひみなさんも利用していただけたらと思います。