やりたいこと
・リモートのMySQLにインターネット経由で接続したい。
・安全にSSHトンネル経由で接続したい。
実現方法
JSchというものを使うとうまくいった。
maven
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
java
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import java.sql.*;
public class TunnelTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException, JSchException {
JSch jsch = new JSch();
jsch.addIdentity("/path/to/.ssh/id_rsa");
jsch.setKnownHosts("/path/to/.ssh/known_hosts");
Session session = jsch.getSession("{SSH_USERNAME}", "{SSH_SERVER}", 22);
session.connect();
try {
// 左がトンネル入口で右がSSHサーバから見たDB接続先。"localhost"は省略可。
session.setPortForwardingL("localhost", 3307, "{MYSQL_SERVER}", 3306);
Class.forName("com.mysql.cj.jdbc.Driver");
Connection con = DriverManager.getConnection(
// 自ホストのトンネル入口を指定してDBに接続
"jdbc:mysql://localhost:3307/",
"{DB_USERNAME}",
"{DB_PASSWORD}"
);
// 適当にクエリを投げてみる
try (Statement st = con.createStatement()) {
try (ResultSet resultSet = st.executeQuery("show databases")) {
while (resultSet.next()) {
System.out.println(resultSet.getString(1));
}
}
}
} finally {
session.disconnect();
}
}
}
knownhostsはファイルパスではなくファイルの中身の情報をInputStreamで与えてもよい。
jsch.setKnownHosts(new ByteArrayInputStream("[example.com]:22 ssh-rsa ABC...==".getBytes()));
つまづいたポイント
invalid privatekeyエラー
こんなエラーが出た。
Exception in thread "main" com.jcraft.jsch.JSchException: invalid privatekey: [B@4459eb14
at com.jcraft.jsch.KeyPair.load(KeyPair.java:664)
at com.jcraft.jsch.KeyPair.load(KeyPair.java:561)
at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:40)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:406)
at com.jcraft.jsch.JSch.addIdentity(JSch.java:366)
at TunnelTest.main(TunnelTest.java:13)
Process finished with exit code 1
原因は秘密鍵の仕様変更
ググるとstack overflowに情報があった。秘密鍵のフォーマットが新しいものだとだめらしい。
The root cause was discovered to be the ssh private key mismatch. The exception only happened for users with key of newer kind ed25519, which outputs this key header:
-----BEGIN OPENSSH PRIVATE KEY-----
instead of kind RSA:
-----BEGIN RSA PRIVATE KEY-----
regenerating an RSA key (ssh-keygen -t rsa), made the exception go away.
Edit following comments: If you have OpenSSH 7.8 and above you might need to add -m PEM to the generation command: ssh-keygen -t rsa -m PEM

"Invalid privatekey" when using JSch
I'm using the following code to work with Git in a Java application.
I have a valid key (use it all the time), and this specific code has work for me before wi...
日本語ではこちらで丁寧に解説されていた。
【OpenSSH 7.8】秘密鍵を生成する形式が変更になった件について | DevelopersIO
こんにちは。DI部の春田です。 皆さん、日頃からssh-keygenのコマンドはよく使われるかと思います。 先日、GoのSSHライブラリに関する記事をあげましたが、秘密鍵のヘッダが変更されている件について、もう少し詳しく …
手元の秘密鍵を見てみる。
% head -n 1 ~/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
まさしくこれが原因だ。
以前の形式で秘密鍵を作って解決
鍵を新しく作ってみる。
% ssh-keygen -t rsa -m PEM -f {新しい秘密鍵のファイルパス}
% head -n 1 {新しい秘密鍵のファイルパス}
-----BEGIN RSA PRIVATE KEY-----
この鍵を使うと、うまく動いた。
以上
参考サイト

Connect to remote MySQL database through SSH using Java
How can I connect to remote MySQL database through SSH from java application? Small code example is helpful for me and I'd appreciate this.
JSch - Java Secure Channel