weztermで自動ログイン機能を作る
まえふり
複数のPCを跨った処理を無人で実行したい場合、SSHを利用したいのだけれど、今どきのLinuxはセキリティ問題のためか、パスワード入力に関しては自動化できないようになっている。
こんな時でも、両側のPCの管理者権限を持っているのならば、パスワードを廃止し、鍵ファイルのみで運用することで自動化を達成するべきなんだろうね。
でも、そうはいかない場合は無理矢理やっちゃうか。
基本
動作条件
- OSはLinuxまたはWindows。おそらくはbsdやMacOSでも。
- 接続相手はOpenSSHサーバーで、クライアントもOpenSSHクライアントであること。
(パスワードプロンプトのパターンを利用してハンドシェークしているので、そこを修正して対応するのならば、OpenSSHである必要はない) - ターミナルソフトであるweztermの自動入力機能を使うのでweztermが必要。
- なにかしらのスクリプト言語、ここではRubyを使用する。
シナリオ
- sshクライアントを起動する。
- 並行ジョブでパスワードプロンプトを待って、パスワードを送信する
手抜きコードで動作説明
ほんとに必要最低限、低負荷のLAN内サーバーだと5行かそこらの記述で動く。
手抜きポイントは「sleep 1」で、
確実に1秒待てばパスワードプロンプトが来るならこれで問題ない。
Thread.new() {
sleep 1
open("|wezterm cli send-text", 'w') { |out|
out.print "パスーワード\r"
}
}
system "ssh ユーザー@ホスト"
本番
手抜きを改めてハンドシェークする
手抜きした「sleep」の部分をちゃんとパスワードプロンプトを待つようにするには、
weztermの機能を使ってテキスト形式のスクリーンショットを取って、
末尾に「ユーザーID@ホスト名's password:」が現れるまで待つように書き換える。
スクリーンショットを取るコマンドは「wezterm cli get-text」である。
待ち時間はサーバーの混雑具合によってまちまちで、
軽いと瞬時だけれど混雑サーバーだと数時間かかる場合もあるので、
試行回数が増えるにしたがって動作間隔を開けるようにしておく。
pttn = /#{$USER}@#{$HOST}'s password:$/
secs = 1
begin
sleep secs
secs = (secs < 64) ? (secs * 2) : 64
lines = open("|wezterm cli get-text").read.chomp.split(/\n/).map { |s|
s = s.strip
}
lines.delete('')
end until (lines.size > 0) && (lines[lines.size - 1] =~ pttn)
起動時チェック
weztermの機能で動作するので、GnomeTermialやWindowsTerminalから起動したときにはエラーを出して終了するようにする。
if !ENV['WEZTERM_PANE'] then
puts "This program can only be launched from wezterm."
exit 1
end
一通り機能するツールに仕立てる
wezsshという名前のツールに仕立てる。
usage: wezssh ユーザー名:パスワード@ホスト名[:ポート番号]
例:
$ wezssh myname:mypass@myhost
#coding: ascii
if !ENV['WEZTERM_PANE'] then
puts "This program can only be launched from wezterm."
exit 1
end
$USER = ""
$PASS = ""
$HOST = ""
$PORT = ""
if (ARGV.size == 1) \
&& (m = ARGV[0].match(/([^:]+):([^@]+)@([^:]+)(:.*)?$/)) then
$USER = m[1]
$PASS = m[2]
$HOST = m[3]
$PORT = (m[4] ? m[4].slice(1, m[4].size - 1) : "22")
else
puts "usage:#{File.basename($PROGRAM_NAME, '.rb')} user:pass@host[:port]"
exit 1
end
Thread.new() {
pttn = /#{$USER}@#{$HOST}'s password:$/
secs = 1
begin
sleep secs
secs = (secs < 64) ? (secs * 2) : 64
lines = open("|wezterm cli get-text").read.chomp.split(/\n/).map { |s|
s = s.strip
}
lines.delete('')
end until (lines.size > 0) && (lines[lines.size - 1] =~ pttn)
open("|wezterm cli send-text", 'w') { |out|
out.print "#{$PASS}\r"
}
}
system "ssh -p #{$PORT} #{$USER}@#{$HOST}"