EC2へのリモートデスクトップ接続(RDP接続)を便利にするツールを作ってみた

2023.04.25

しばたです。

私は普段から検証などでWindows Server EC2にリモートデスクトップ接続することが多く、これまでは手作業で接続していたのですが「もう少し自動化して楽をしたい」という気持ちになったので簡単なツールを作って公開しました。

作ったもの

作ったツールはこちらになります。

Go言語製シングルバイナリのプログラムです。

EC2およびSSMの機能を使うため両サービスに対する所定の権限とSession Manager Pluginが事前に必要となります。

機能概要

現時点では2つの機能があります。

ec2rdp public コマンド

パブリックに公開されたEC2インスタンスに対してRDP接続するコマンドです。
検証環境などで動的にPublic IPが変わる環境向けに用意しました。

引数で指定されたインスタンスIDからPublic DNS名およびPublic IPを取得、EC2 PasswordDataから管理者パスワードを取得したうえでRemote Desktop Clientを起動してインスタンスへ直接接続を試みます。

Remote Desktop ClientはWindows環境であれば標準のmstsc.exeを使い、macOS環境であればParallels Clientを起動します。

ec2rdp ssm コマンド

SSMのポートフォーワードを経由してRDP接続するコマンドです。
こちらが私が一番作りたかったもので、SSMのポートフォーワード設定の手間を自動化するために用意しました。

引数で指定されたインスタンスの状態を確認したうえで、Session Manager Plugin(session-manager-pluginプロセス)を裏で起動します。
続けてec2rdp publicコマンドと同様にEC2 PasswordDataから管理者パスワードを取得したうえでRemote Desktop Clientを起動しlocalhostを経由して当該インスタンスへ接続します。
localhostの待ち受けポートはTCP 33389を起点に空いているポートを自動で探して決定します。

そしてRemote Desktop Client終了後は自動でSSM Sessionを終了してsession-manager-pluginプロセスを終わらせます。

使い方

対応しているOSはWindowsとmacOSです。
シングルバイナリのツールなのでどのシェル上でも動作します。

予めSession Manager Pluginをインストールしておき、AWS CLIと同様に認証情報ファイル(~/.aws/credentialsおよび~/.aws/config)を設定しておきます。

後はGitHubのリリースページから各OS毎のバイナリをダウンロードしPATHの通っているディレクトリに保存すれば準備完了です。
(今後需要が増える様であればWingetに対応しようと思います)

【追記】
そんなに手間で無かったのでHomebrew Tapsからインストール可能にしました。
macOSの場合は以下のコマンドでもインストールできます。

# macOS only
brew install stknohg/tap/ec2rdp

【追記ここまで】

後は以下の様に使用するプロファイルを設定してec2rdp publicおよびec2rdp ssmコマンドを実行します。

# PowerShellコンソールで実行する場合
$env:AWS_PROFILE='AWSプロファイル名'
ec2rdp public -i "接続対象のインスタンスID" -p "パスワード取得用の秘密鍵のパス"

# PowerShellコンソールで実行する場合
$env:AWS_PROFILE='AWSプロファイル名'
ec2rdp ssm -i "接続対象のインスタンスID" -p "パスワード取得用の秘密鍵のパス"

(裏でsession-manager-pluginプロセスが起動されている様子)

接続情報をカスタマイズする場合は--user--passwordパラメーターも使えます。

# Administrator以外のユーザーで接続する場合は --user パラメーターを使う
# 自分でパスワードを入力する場合は --password パラメーターを使う
ec2rdp public -i "接続対象のインスタンスID" --user shibata --password

また、AWS CLIと同様に--profile--regionパラメーターも用意しています。

# --profile と --region パラメーターも使える
ec2rdp public -i "接続対象のインスタンスID" -p "パスワード取得用の秘密鍵のパス" --profile 'your_profile' --region ap-northeast-1

技術面

やっていることはシンプルですが、めちゃくちゃ久しぶりにGo言語を書いた+はじめてAWS SDK for Go v2を扱ったため実装にはまあまあ苦労しました。
正直非常にダメなコードだと思います...テストも全然ないのでこれは少しずつ改善していきたいですね。

Goのエコシステム

今回CLI FrameworkにCobraを使い、リリース管理にGoReleaserを使いました。
どちらもメジャーなツールですが実際使ってみてその便利さを実感できました。

AWS SDK for Go v2

AWSリソースへのアクセスにはAWS SDK for Go v2を使っています。
はじめて使った感想としては「シンプルなREST APIのラッパーかな?」という印象で、取っ掛かりやすさはあるもののAPIパラメーターやレスポンスの扱いにちょっと手間を感じるところがありました。

あとAWS Profileの扱いがLoadOptionsFunc型の関数をデコレートしていく仕組みの様なんですが、自分の書き方が正しいのか未だに確証を持てません...
このへんはもう少し抽象化された仕組みが欲しいなって思います。

Session Manager Pluginの仕様

前の記事でSession Manager Pluginの仕様を解析できたのでGo言語でも楽に実装できました。
上手いこと引数を作ってやってexec.Commandでプロセスを起動するだけです。
ただ、引数のうち現在利用しているAWS Profile名をいい感じに取得する方法を見つけることができず、ここは無理やり引き渡す様にしています。

Remote Desktop Clientの仕様

Remote Desktop ClientはWindows環境では標準のmstsc.exeを、macOS環境では私が使い慣れている+仕様が明瞭なParallels Clientを使う様にしました。

mstsc.exeはWindowsの資格情報マネージャーに所定の資格情報があればパスワード入力無しにRDP接続を行うことができます。
このためRDP接続時に一時的に資格情報マネージャーに認証情報を保存し、接続が終わったら認証情報を削除する様にしています。
これは昔からいろいろなツールで採用されているベーシックな方法です。

Parallels ClientはURL Schemeが公開されており、接続先情報をURLのパラメーターに渡すことでRDP接続を自動化できます。
パスワードに関するパラメーターに平文のPasswordとハッシュ化されたEncPassの二種類あるのですが、EncPassの仕様が公開されていないのでやむを得ず平文のPasswordパラメーターを使っています。
URLはopenコマンドで開いているのですが、openコマンド自体は直ちに終了するので平文パスワードが見えてしまう時間も僅かで済むだろうという見込みです。

Keychainを使ってどうにかできないか検討もしたのですが上手い方法を見つけることはできませんでした...
いずれはもっとセキュアな仕組みにしたいですね。

最後に

以上となります。

簡単なものではありますが「久しぶりにアプリを書いたぞ!」というお気持ちです。
Go言語の勉強にもなったので私自身は作っただけで非常に満足しております。

このツールにどの程度の需要があるかは正直何とも言えませんが誰かの役に立てばうれしい限りです。