AWS Tools for PowerShellでSSM Session Manager Pluginを使える様にしてみた
しばたです。
AWS CLIと連携してSSM Sessionを介した機能を実現するSession Manager Pluginですが、現時点ではAWS Tools for PowerShellと連携することは出来ません。
この点に関して過去に上の記事を書いたりもしていたのですが、今回、AWS CLIやSession Manager Pluginの仕様を解析して無理やりAWS Tools for PowerShellと連携させることができたのでその内容を共用したいと思います。
現在の仕様
Session Manager Pluginを使うコマンドaws ssm start-sessionと同等のPowerShellコマンドレットはStart-SSMSessionになるのですが、現時点では、
Amazon Web Services CLI usage: start-session is an interactive command that requires the Session Manager plugin to be installed on the client machine making the call. For information, see Install the Session Manager plugin for the Amazon Web Services CLI in the Amazon Web Services Systems Manager User Guide.
Amazon Web Services Tools for PowerShell usage: Start-SSMSession isn't currently supported by Amazon Web Services Tools for PowerShell on Windows local machines.
とドキュメントにもある様に非サポートとなっています。
AWS CLIでaws ssm start-session
を実行するとAWS環境上でSSM Sessionを生成し、そのセッション情報を元にSession Manager Plugin(session-manager-plugin
)プロセスを起動するのですが、AWS Tools for PowerShellのStart-SSMSession
コマンドではセッション情報を返すことしかできません。
# AWS Tools for PowerShellではSession Manager Pluginと連動せずセッション情報を返すだけ
PS C:\> Start-SSMSession -Target i-0123456789abc
SessionId StreamUrl
--------- ---------
aws-dotnet-sdk-session-xxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxx wss://ssmmessages.ap-northeast-1.amazonaws.com/v1/data-cha…
これはREST APIのStartSessionをシンプルに実行しているだけだからです。
AWS CLIではStartSession
APIの実行に加えてsession-manager-plugin
プロセスを実行する様に独自のカスタマイズが加えられており過去記事でも説明したとおりです。
session-manager-plugin の引数
そんなわけで、原理的にはどうにかしてsession-manager-plugin
プロセスを起動してやればAWS Tools for PowerShellでも連携可能なはずです。
以前はこの点を解析しきれなかったのですが、現在はSession Manager Plugin自体がオープンソースとなりGitHub上でソースが公開されています。
このおかげでだいぶ解析がはかどりました。
Session Manger Pluginの引数は以下の様に6つ定義されています。
// ValidateInputAndStartSession validates input sent from AWS CLI and starts a session if validation is successful.
// AWS CLI sends input in the order of
// args[0] will be path of executable (ignored)
// args[1] is session response
// args[2] is client region
// args[3] is operation name
// args[4] is profile name from aws credentials/config files
// args[5] is parameters input to aws cli for StartSession api
// args[6] is endpoint for ssm service
各引数の仕様について簡単に説明していきます。
第1引数 : Sesson Response
第1引数にはStartSession
APIを実行した結果のレスポンス(JSON)をそのまま全部渡してやります。
厳密にはレスポンス内にある
- SSM Session ID :
SessionId
- Web Socket URL :
StreamUrl
- Session Token :
TokenValue
さえあれば良い様です。
第2引数 : クライアントリージョン
第2引数はリージョン名(ap-northeast-1
など)を渡してやります。
第3引数 : オペレーション名
第3引数にはREST APIのオペレーション名を渡してやります。
現状StartSession
固定と考えて良いでしょう。
第4引数 : プロファイル名
第4引数には共有認証情報ファイル(AWS CLIで使う~/.aws/credentials
)のプロファイル名を渡してやります。
Session Manager PluginはGo言語製のため.NETの認証情報は使えません。
幸いにもAWS Tools for PowerShellは.NETの認証情報だけでなく認証情報ファイルも使えるため、共有認証情報ファイルを使う設定であればそのプロファイル名を渡してやればOKです。
第5引数 : StartSession API パラメーター
第5引数にはStartSession
APIを実行した際のパラメーターをJSON形式で渡してやります。
第3引数との関係を考えると「本来はREST API実行時パラメーター向けに汎用的なものを意図したのかな?」という感じですが、現在の実装はStartSession
API専用となっておりTarget
の値だけが実際に使われている感じです。
例えば最初に例示したStart-SSMSession -Target i-0123456789abc
の場合であれば、
{"Target":"i-0123456789abc"}
といった感じの値を渡してやります。
第6引数 : SSMエンドポイント
最後の第6引数にはSSMサービスのエンドポイントURLを設定します。
例えば東京リージョンであれば以下の値を設定します。
https://ssm.ap-northeast-1.amazonaws.com
作った関数
ここまでの内容を踏まえて今回新たにStart-SSMSessionEx
という関数を作りました。
従来のStart-SSMSession
を拡張し、セッション情報取得後にsession-manager-plugin
プロセスを起動しているだけです。
前提条件としては
- Windows環境専用
- 非Windowsでの動作確認をしてないため。とはいえ少しの改修で対応できる気はします。
- AWS Tools for PowerShellインストール済み
- 最低限
AWS.Tools.SimpleSystemsManagement
モジュールが必要です。
- 最低限
- Session Manager Pluginインストール済み
- プロファイルを共有認証情報ファイルに記載すること
- .NETの認証情報は使えません
となります。
function Start-SSMSessionEx () {
[CmdletBinding()]
param (
[Parameter(Mandatory = $false, Position = 1)]
[string]$DocumentName,
[Parameter(Mandatory = $false)]
[hashtable]$Parameter,
[Parameter(Mandatory = $false)]
[string]$Reason,
[Parameter(Mandatory = $true)]
[string]$Target,
[Parameter(Mandatory = $false)]
[switch]$PassThru,
[Parameter(Mandatory = $false)]
[string]$ProfileName,
[Parameter(Mandatory = $false)]
[object]$Region
)
if (-not (Get-Command 'session-manager-plugin' -Type Application -ErrorAction Ignore)) {
$message = @'
SessionManagerPlugin (session-manager-plugin) is not found.
Please refer to SessionManager Documentation here:
https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html
'@
Write-Error $message
return
}
$session = $null
try {
$params = @{
Parameter = $Parameter
Target = $Target
ProfileName = $ProfileName
Region = $Region
}
if (-not [string]::IsNullOrEmpty($DocumentName)) {
$params['DocumentName'] = $DocumentName
}
if (-not [string]::IsNullOrEmpty($Reason)) {
$params['Reason'] = $Reason
}
$session = Start-SSMSession @params
} catch {
Write-Error $_
return
}
if ($null -eq $session) {
Write-Error 'Failed to get SSM session.'
return
}
# Start SSM Session manager plugin
if ([string]::IsNullOrEmpty($ProfileName)) {
$ProfileName = $StoredAWSCredentials
}
if ([string]::IsNullOrEmpty($Region)) {
$Region = (Get-DefaultAWSRegion).Region
}
# Setup arguments
$arguments = @()
# arg1 : session json
$arguments += "`"$((($session | ConvertTo-Json -Compress) -replace '"', '\"'))`""
# arg2 : region name
$arguments += $Region
# arg3 : StartSession
$arguments += 'StartSession'
# arg4 : shared credentials file profile nam
$arguments += $ProfileName
# arg5 : parameter json
$arg5hash = @{ Target = $Target; }
if (-not [string]::IsNullOrEmpty($DocumentName)) {
$arg5hash['DocumentName'] = $DocumentName
}
if ($null -ne $Parameter) {
$arg5hash['Parameter'] = $Parameter
}
if (-not [string]::IsNullOrEmpty($Reason)) {
$arg5hash['Reason'] = $Reason
}
$arguments += "`"$(($arg5hash | ConvertTo-Json -Compress) -replace '"', '\"')`""
# arg 6 : SSM endpoint URL
$arguments += "https://ssm.$($region).amazonaws.com"
Write-Verbose 'session-manager-plugin arguments'
for ($i = 0; $i -lt $arguments.Count; $i++) {
Write-Verbose " arg$($i + 1) : $($arguments[$i])"
}
# start session-manager-plugin
if ($PassThru) {
# PassThru
$proc = Start-Process -FilePath 'session-manager-plugin' -ArgumentList $arguments -PassThru -WindowStyle Hidden
Write-Host ('Starting session with SessionId: {0}' -f $session.SessionId)
Write-Host ('Start session-manager-plugin process ({0})' -f $proc.Id)
return [PSCustomObject]@{
Session = $session
Process = $proc
}
} else {
# Wait
Start-Process -FilePath 'session-manager-plugin' -ArgumentList $arguments -Wait -NoNewWindow
}
}
大体の処理は見たまんまという感じではありますが、Windows環境だと引数に渡すJSONファイルのダブルクオートをエスケープする("
を\"
にする)必要がある点に結構ハマりました。
その他にはsession-manager-plugin
の起動で終了待ちをするか否かで結構悩み、結局は-PassThru
パラメーターを付けて選択できる様にしました。
デフォルトでは終了待ちする様にしましたが、終了待ちをせずにセッションIDだけ受け取り非同期にSSMセッションを終了することもできます。
ちなみにSession Manager Pluginの仕様としてはSSMセッションの終了を検知すると自動でプロセスも終了する様になっています。
実行例 : ポートフォワード
例として以下の様なパラメーターでこの関数を実行してやるとRDPのポートフォワードが可能になります。
# 33389 → 3389 ポートへのフォーワード
$params = @{
Target = 'i-01234567890abcdef'
DocumentName = 'AWS-StartPortForwardingSession'
Parameter = @{portNumber = @('3389'); localPortNumber = @('33389') }
}
Start-SSMSessionEx @params
SSMセッション取得後にSession Manger Pluginを起動してポートフォワードの待ち受けをしてくれるので、あとはlocalhost
へRDP接続してやるだけです。
おまけ : PSEC2RDPモジュール
本記事を書くきっかけとして、自分用にPSEC2RDPという名前のPowerShellモジュールを作って公開したのでおまけとして紹介します。
このモジュールではEC2インスタンスへのRDP接続を便利にするために以下の2つの関数を公開してます。
- 公開インスタンスに対してAdministratorパスワードの取得とRDP接続を自動で行う
Start-EC2RDPClient
関数 - 非公開インスタンスに対してAdministratorパスワードを取得、SSMのポートフォワードを裏で実行してRDP接続を自動で行う
Start-SSMRDPClient
関数
特に後者の「SSMのポートフォワードを裏で実行」を実現するためにSession Manager Pluginの仕様を改めて調べ直しました。
私個人としてはこのモジュールを便利に使えているのですが、「Session Manager Pluginの仕様を把握できた今ならGo言語かC#あたりで処理を書き直した方がより汎用的なツールを作れるな。」という気持ちになってしまったのでPowerShell Galleryでの公開まではしていません。
気になる方はリポジトリをクローンして試してみてください。
最後に
以上となります。
約三年半越しにリベンジできたので個人的には非常に嬉しいです。
万人向けの内容ではありませんが本記事の内容が誰かの役に立てば幸いです。