CLOVER🍀

That was when it all began.

Apache MINAで簡単SSHD

先ほど、Apache MINAのFTPサーバをご紹介しました。

Apache MINAで簡単FTPD
http://d.hatena.ne.jp/Kazuhira/20140308/1394254984

今度は、SSHサーバです。

SSHD
http://mina.apache.org/sshd-project/

FTPサーバは組み込みのライブラリとして使うか、スタンドアロンで使うかを選べたみたいですが、こちらはプログラムで組むことが前提みたいです。

では、使ってみましょう。

こちらのチュートリアルを参考に。

Embedding SSHD in 5 minutes
http://mina.apache.org/sshd-project/embedding_ssh.html

プログラムは、Groovyで書きます。

まずは、import文。

@Grab('org.apache.sshd:sshd-core:0.10.1')
@Grab('org.apache.sshd:sshd-pam:0.10.1')
@Grab('org.apache.sshd:sshd-sftp:0.10.1')
import org.apache.sshd.SshServer
import org.apache.sshd.server.PasswordAuthenticator
import org.apache.sshd.server.command.ScpCommandFactory
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
import org.apache.sshd.server.session.ServerSession
import org.apache.sshd.server.sftp.SftpSubsystem
import org.apache.sshd.server.shell.ProcessShellFactory

ホントは、Grapeでこう書きたいんですけど

@Grab('org.apache.sshd:sshd:0.10.1')

これが直接引っ張ってくるのは、JARではなくpom.xmlなのでうまくいかない様子。

MavenやGradleなら大丈夫なのかな?

最初に、インスタンスの作成。

// SshServerのインスタンス作成
def sshd = SshServer.setUpDefaultServer()

ポートとKeyProviderの設定。

// ポートとKeyProviderの指定
sshd.port = 10022
sshd.keyPairProvider = new SimpleGeneratorHostKeyProvider("hostkey.ser")

ここで書いている「hostkey.ser」というファイルは、SSHDが勝手に作成するので、起動時になくても大丈夫です。

ちなみに、Groovyで書いているのでここを含めて以降、Javaで

sshd.setPort(10022);

と書くべきところを全部setter経由の呼び出しを端折ってます。

続いて、認証方式の設定。

// 認証方式の設定
sshd.passwordAuthenticator = new MyPasswordAuthenticator()

実は、この認証方式の設定は先ほどのチュートリアルの中には書かれていなくて、別ドキュメントになります。

Configuring Security
http://mina.apache.org/sshd-project/configuring_security.html

公開鍵認証方式とパスワード認証方式がありますが、ここではパスワード認証方式を選択しました。

今回作成した、PasswordAuthenticatorインターフェースの実装は、こんな感じ。

// 実質、誰でも認証
class MyPasswordAuthenticator implements PasswordAuthenticator {
    @Override
    public boolean authenticate(String username, String password, ServerSession session) {
        println("user[$username], password[$password]")
        true
    }
}

実際は、ちゃんと認証してくださいと…。

続いて、シェルの設定。

// シェルの設定
sshd.shellFactory = new ProcessShellFactory(["/bin/bash", "-i", "-l"].toArray(new String[0]),
                                            EnumSet.of(ProcessShellFactory.TtyOptions.ONlCr))

ドキュメント通りに「/bin/sh」と書くと、Ubuntuはデフォルトがdashなのでうまく動きませんでした…。

また、ProcessShellFactory.TtyOptionsを使わないとsshコマンドでリモートログインした時に、改行とか空白が乱れてえらいことになったのですが、Twitterでヒントをいただいて修正することができました。ありがとうございます!

続いて、scpとsftpを有効化します。

// scpの有効化
sshd.commandFactory = new ScpCommandFactory()

// sftpの有効化
sshd.subsystemFactories = [new SftpSubsystem.Factory()]

サーバの起動。

// サーバ起動
sshd.start()

println("[${new Date()}] Startup. Simple-SSHD")

終了する時は、stopです。

// 終了(コメントアウトを解除すると、終了します)
// sshd.stop()

今回は、そのまま通しちゃうと終了してしまうので、コメントアウトしています。

では、動かしてみましょう!実行!

$ groovy simple-sshd.groovy 
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[Sat Mar 08 16:28:45 JST 2014] Startup. Simple-SSHD

別のコンソールから接続。先のパスワード認証方式の実装通り、パスワードは何でも通ります。

$ ssh -p 10022 user@localhost
The authenticity of host '[localhost]:10022 ([127.0.0.1]:10022)' can't be established.
DSA key fingerprint is d6:c1:fc:dc:43:4b:5d:f9:18:4a:ef:68:f1:54:a1:f8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:10022' (DSA) to the list of known hosts.
Password authentication
Password: 
$

無事、接続完了です。

この後、普通に「ls」とかコマンドを実行することができます。

終了。

$ exit
ログアウト
Connection to localhost closed.

scp。クライアントからサーバへコピー。

$ scp -P 10022 scp-client.txt  user@localhost:
Password authentication
Password: 
scp-client.txt                                                                        100%   16     0.0KB/s   00:00

サーバからクライアントへ。

$ scp -P 10022 user@localhost:/path/to/scp-server.txt ./.
Password authentication
Password: 
scp-server.txt                                                                        100%   16     0.0KB/s   00:00

なんか、Fromがサーバだと、フルパスじゃないと動かなかった??

sftp。

$ sftp -P 10022 user@localhost
Password authentication
Password: 
Connected to localhost.
sftp> 

get。

sftp> get sftp-server.txt
Fetching /path/to/sftp-server.txt to sftp-server.txt
/path/to/sftp-ser 100%   17     0.0KB/s   00:00

put。

sftp> put sftp-client.txt
Uploading sftp-client.txt to /path/to/sftp-client.txt
sftp-client.txt                                                                       100%   17     0.0KB/s   00:00

終了。

sftp> bye

ちなみに、lsとかはキレイに見れるっていうね。

sftp> ls -l
-rw-rw-r--   1 xxxxx xxxxx     1201 Mar  8 16:12 hostkey.ser
-rw-rw-r--   1 xxxxx xxxxx       16 Mar  8 16:35 scp-client.txt
-rw-rw-r--   1 xxxxx xxxxx       16 Mar  8 16:26 scp-server.txt
-rw-rw-r--   1 xxxxx xxxxx       17 Mar  8 16:37 sftp-client.txt
-rw-rw-r--   1 xxxxx xxxxx       17 Mar  8 16:26 sftp-server.txt
-rw-rw-r--   1 xxxxx xxxxx     1579 Mar  8 16:23 simple-sshd.groovy

なんかscpのサーバからのコピー時が??な気がしますが、使えそうな感じ。

一応、今回書いたコードを載せておきます。
simple-sshd.groovy

// @Grab('org.apache.sshd:sshd:0.10.1') // この形式は指定できない?
@Grab('org.apache.sshd:sshd-core:0.10.1')
@Grab('org.apache.sshd:sshd-pam:0.10.1')
@Grab('org.apache.sshd:sshd-sftp:0.10.1')
import org.apache.sshd.SshServer
import org.apache.sshd.server.PasswordAuthenticator
import org.apache.sshd.server.command.ScpCommandFactory
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
import org.apache.sshd.server.session.ServerSession
import org.apache.sshd.server.sftp.SftpSubsystem
import org.apache.sshd.server.shell.ProcessShellFactory

// SshServerのインスタンス作成
def sshd = SshServer.setUpDefaultServer()

// ポートとKeyProviderの指定
sshd.port = 10022
sshd.keyPairProvider = new SimpleGeneratorHostKeyProvider("hostkey.ser")

// 認証方式の設定
sshd.passwordAuthenticator = new MyPasswordAuthenticator()

// シェルの設定
sshd.shellFactory = new ProcessShellFactory(["/bin/bash", "-i", "-l"].toArray(new String[0]),
                                            EnumSet.of(ProcessShellFactory.TtyOptions.ONlCr))

// scpの有効化
sshd.commandFactory = new ScpCommandFactory()

// sftpの有効化
sshd.subsystemFactories = [new SftpSubsystem.Factory()]

// サーバ起動
sshd.start()

println("[${new Date()}] Startup. Simple-SSHD")

// 終了(コメントアウトを解除すると、終了します)
// sshd.stop()

// 実質、誰でも認証
class MyPasswordAuthenticator implements PasswordAuthenticator {
    @Override
    public boolean authenticate(String username, String password, ServerSession session) {
        println("user[$username], password[$password]")
        true
    }
}