RubyでSSHサーバーを立ててみます。
使うのは、hrr_rb_ssh Gem(現時点で、バージョンは0.4.0)です。
使いどころ
- SSH接続を利用するツール等のテストに
- ネットワーク機器等のシミュレーターとして
- SSH Subsystemの実装に(NETCONF等)
- ユーザー権限で動かすオレオレSSHサーバーとして
インストール
Gemは、次のようにインストールします。
Bundlerを使う場合、
Gemfile
gem 'hrr_rb_ssh'
gem 'hrr_rb_ssh-ed25519' # ed25519を使う場合
というファイルを作り、
$ bundle install
します。もしくは、直接
$ gem install hrr_rb_ssh
$ gem install hrr_rb_ssh-ed25519 # ed25519を使う場合
を実行します。
使ってみる
まずは、実行ユーザーのシェルにログインできるSSHサーバーを立ててみます。
ちょっと長いですが、やっていることは、
- ライブラリを読み込む
- 認証(publickey、password)情報の定義
- SSH内部処理のハンドラの定義(実際には、デフォルト実装を使用)
- TCPserverを立てて、接続を待ち受ける
- SSHサーバーサービスを開始する
です。
require 'logger'
require 'socket'
require 'hrr_rb_ssh'
require 'hrr_rb_ssh/ed25519' # ed25519を使う場合
logger = Logger.new STDOUT
logger.level = Logger::INFO
# 公開鍵認証に、OpenSSHの~/.ssh/authorized_keysを使う
auth_publickey = HrrRbSsh::Authentication::Authenticator.new { |context|
username = ENV['USER']
homedir = Dir.home
authorized_keys = HrrRbSsh::Compat::OpenSSH::AuthorizedKeys.new(File.read(File.join(homedir, '.ssh', 'authorized_keys')))
authorized_keys.any?{ |public_key|
context.verify username, public_key.algorithm_name, public_key.to_pem
}
}
# 指定のパスワードで認証する
auth_password = HrrRbSsh::Authentication::Authenticator.new { |context|
username = ENV['USER']
password = '接続時に使うパスワード'
context.verify username, password
}
options = {}
# 定義した認証を利用する
options['authentication_publickey_authenticator'] = auth_publickey
options['authentication_password_authenticator'] = auth_password
# SSH接続の内部処理を、デフォルト実装のハンドラで処理する
options['connection_channel_request_pty_req'] = HrrRbSsh::Connection::RequestHandler::ReferencePtyReqRequestHandler.new
options['connection_channel_request_env'] = HrrRbSsh::Connection::RequestHandler::ReferenceEnvRequestHandler.new
options['connection_channel_request_shell'] = HrrRbSsh::Connection::RequestHandler::ReferenceShellRequestHandler.new
options['connection_channel_request_exec'] = HrrRbSsh::Connection::RequestHandler::ReferenceExecRequestHandler.new
options['connection_channel_request_window_change'] = HrrRbSsh::Connection::RequestHandler::ReferenceWindowChangeRequestHandler.new
# ポート10022番で接続を待ち受ける
server = TCPServer.new 10022
io = server.accept
# SSHサーバーを起動する
server = HrrRbSsh::Server.new options, logger: logger
server.start io
独自サービスを提供する
使ってみる では、デフォルトのハンドラを利用し、ユーザーのログインシェルをサービスとして提供しました。
ここでは、ログインシェルの代わりに、独自サービスとして、echoサーバー(処理は何も行わず、ただ文字をエコーバックするだけ)を実装してみます。
コードは、次のようになります。
require 'logger'
require 'socket'
require 'hrr_rb_ssh'
require 'hrr_rb_ssh/ed25519' # ed25519を使う場合
logger = Logger.new STDOUT
logger.level = Logger::INFO
# 指定のパスワードで認証する
auth_password = HrrRbSsh::Authentication::Authenticator.new { |context|
username = ENV['USER']
password = '接続時に使うパスワード'
context.verify username, password
}
# echoサーバーの処理を実装
conn_echo = HrrRbSsh::Connection::RequestHandler.new { |context|
context.chain_proc { |chain|
# SSHクライアントに、最初のメッセージを出力(context.io[1].write)する
context.io[1].write "Start echo service\r\n"
loop do
# SSHクライアントからの入力(context.io[0].read)を受けて
buf = context.io[0].readpartial(10240)
# Ctrl+Dが入力された場合、抜ける
break if buf.include?(0x04.chr)
# SSHクライアントに出力(context.io[1].write)する
context.io[1].write buf
end
exitstatus = 0
exitstatus
}
}
options = {}
# 定義した認証を利用する
options['authentication_publickey_authenticator'] = auth_publickey
options['authentication_password_authenticator'] = auth_password
# shellのリクエストに対するハンドラを、echoサーバーの実装に置き換える
options['connection_channel_request_shell'] = conn_echo
# ポート10022番で接続を待ち受ける
server = TCPServer.new 10022
io = server.accept
# SSHサーバーを起動する
server = HrrRbSsh::Server.new options, logger: logger
server.start io
SSHクライアントからアクセスし、例えば foobarbaz...
と入力すると、
$ ssh localhost -p 10022
Password: 接続時に使うパスワード
Start echo service
foobarbaz...
のように、入力がエコーバックされます。
このように、HrrRbSsh::Connection::RequestHandler.new {|context| ...}
を定義することで、独自のサービスを提供することが可能になります。