Java用 SSH クライアントライブラリ - ganymed-ssh2, sshj, JSch, Apache SSHD

主要な Java 用の SSH クライアントライブラリを使って簡単なサンプルを作成してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140814/

はじめに

Vagrant で実行中のゲスト OS に対して SSHls -al を実行し、その結果を出力するだけの単純な処理を Groovy で実装してみる事にします。

今回の実装のポイントとなる点は下記のようなところだと思います。

  • (1) Vagrant秘密鍵ファイル insecure_private_key で認証
  • (2) 未知のホスト鍵をチェックしないようにする (ssh における StrictHostKeyChecking=no と同等の処理)

Vagrant秘密鍵ファイル insecure_private_keyスクリプトファイルと同じディレクトリへコピーしておく事とします。

また、本件のサンプルコードではエラー処理や文字コード等を配慮していない点にご注意ください。

Ganymed SSH-2

元のオリジナルはかなり以前にメンテナンスが停止されていたり、Orion SSH や Trilead SSH のような同系統のライブラリが他にあったりと、どれを使えばよいのか分かり難い印象がありますが、とりあえず https://code.google.com/p/ganymed-ssh-2/ でメンテナンス継続されているものを使えば良さそうです。

ホスト鍵のチェックは connect メソッドで制御できるようです。(チェックしたければ ServerHostKeyVerifier を渡す)

ganymed_sample.groovy
@Grab('ch.ethz.ganymed:ganymed-ssh2:262')
import ch.ethz.ssh2.Connection

def con = new Connection('127.0.0.1', 2222)
con.connect()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
if (con.authenticateWithPublicKey('vagrant', new File('insecure_private_key'), null)) {
    def session = con.openSession()

    session.execCommand('ls -al')

    println session.stdout.text

    session.close()
}

con.close()
実行結果
> groovy ganymed_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

sshj - SSHv2 library for Java

authPublickeyconnect の後に実行する必要があります。

また、未知のホスト鍵をチェックしないようにするには、addHostKeyVerifierPromiscuousVerifier を設定します。 (デフォルトではチェックするようになっています)

sshj_sample.groovy
@Grab('net.schmizz:sshj:0.10.0')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.common.IOUtils
import net.schmizz.sshj.transport.verification.PromiscuousVerifier

def client = new SSHClient()
// (2) 未知のホスト鍵をチェックしない
client.addHostKeyVerifier(new PromiscuousVerifier())

client.connect('127.0.0.1', 2222)
// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
client.authPublickey('vagrant', client.loadKeys('insecure_private_key'))

def session = client.startSession()

def cmd = session.exec('ls -al')

println IOUtils.readFully(cmd.inputStream)

session.close()
client.disconnect()
実行結果
> groovy sshj_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

JSch - Java Secure Channel

未知のホスト鍵をチェックしないようにするには setConfigStrictHostKeyCheckingno にします。

jsch_sample.groovy
@Grab('com.jcraft:jsch:0.1.51')
import com.jcraft.jsch.JSch

def jsch = new JSch()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
jsch.addIdentity('insecure_private_key')
// (2) 未知のホスト鍵をチェックしない
jsch.setConfig('StrictHostKeyChecking', 'no')

def session = jsch.getSession('vagrant', '127.0.0.1', 2222)

session.connect()

def ch = session.openChannel('exec')
ch.setCommand('ls -al')

ch.connect()

println ch.inputStream.text

ch.disconnect()
session.disconnect()
実行結果
> groovy jsch_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

Apache SSHD

Apache MINA をベースに SSH サーバーとクライアントの両方の API を備えたライブラリです。 他のライブラリと比べると多少面倒なように思います。

close メソッドに false を指定すると graceful にクローズします。

sshd_sample.groovy
@Grab('org.apache.sshd:sshd-core:0.12.0')
@Grab('org.apache.mina:mina-core:3.0.0-M2')
@Grab('org.bouncycastle:bcpkix-jdk15on:1.51')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import org.apache.sshd.ClientChannel
import org.apache.sshd.SshClient
import org.apache.sshd.common.keyprovider.FileKeyPairProvider

def client = SshClient.setUpDefaultClient()
client.start()

def session = client.connect('vagrant', '127.0.0.1', 2222).await().getSession()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
def keyProvider = new FileKeyPairProvider(['insecure_private_key'] as String[])
session.addPublicKeyIdentity(keyProvider.loadKeys().iterator().next())

def auth = session.auth()
// verify の実行が必要
auth.verify()

if (auth.isSuccess()) {
    def ch = session.createExecChannel('ls -al')
    def baos = new ByteArrayOutputStream()

    ch.setOut(baos)

    ch.open()
    // コマンド完了まで待機
    ch.waitFor(ClientChannel.CLOSED, 0)
    ch.close(false)

    println baos.toString()
}

session.close(false)
client.stop()
実行結果
> groovy sshd_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo