ããã¯ããªã«ãããããŠæžãããã®ïŒ
æè¿äœ¿ãããšãæžã£ãŠããæ°ãããSSHã¯ã©ã€ã¢ã³ããªã©ã§ãããJavaã§æ±ãããšãããšJSchãæåããšæããŸãã
ãã®ãããã®äºæ
ãæè¿èŠçŽããšã¡ãã£ãšå€ãã£ãŠããæããããã®ãšãä»åã¯Apache MINA SSHDã§SSHãSFTPã®
ã¯ã©ã€ã¢ã³ããæ±ã£ãŠã¿ãããšæããŸãã
Javaã®SSHã¯ã©ã€ã¢ã³ãã©ã€ãã©ãªãŒ
Javaã§äœ¿ããSSHã¯ã©ã€ã¢ã³ãã©ã€ãã©ãªãŒã¯ã以åã«ãŸãšããããšããããŸãããšãã£ãŠã13幎åã§ããâŠã
ä»èª¿ã¹çŽããšããããªæãã«ãªã£ãŠããŸããã
JSch
Javaã§ãã䜿ãããSSHã¯ã©ã€ã¢ã³ãã©ã€ãã©ãªãŒãšããã°JSchã§ãããçŸåšã¯ã¡ã³ããã³ã¹ãæ¢ãŸã£ãŠããŸãã
JSch download | SourceForge.net
GitHub - is/jsch: Mirror of JSch from JCraft.
æåŸã®ãªãªãŒã¹ã¯ã2018幎ã§ããããã
ãªãããã©ãŒã¯ãååšããŸããçŸåšã¯JSchãšããã°ãã¡ããäœ¿ãæ¹ãããããã§ãã
GitHub - mwiede/jsch: fork of the popular jsch library
Apache MINA SSHD
Apache MINA SSHDã¯ãSSHã¯ã©ã€ã¢ã³ãã»ãµãŒããŒã®äž¡æ¹ãæ±ããã©ã€ãã©ãªãŒã§ãã
以åïŒããã10幎ãããåã§ããâŠïŒã«SSHãµãŒããŒãšããŠè©ŠããããšããããŸãã
çŸåšã§ãã¡ã³ããã³ã¹ãã¢ã¯ãã£ãã«ç¶ããããŠããŸãã
SSHJ
JSchãšåãããããªãåããããSSHã©ã€ãã©ãªãŒã§ãã
GitHub - hierynomus/sshj: ssh, scp and sftp for java
çŸåšã§ãã¡ã³ããã³ã¹ãç¶ããããŠããŸãããŸãæäœããããããããã§ãã
Ganymed SSH-2 for Java
ãã¡ããJSchãšåãããããªãåããããSSHã©ã€ãã©ãªãŒã§ãã
ãã¡ããã¡ã³ããã³ã¹ã¯ããã»ã©ã¢ã¯ãã£ãã§ã¯ãªãããã§ããæçµãªãªãŒã¹ã¯2020幎ã§ãã
Apache MINA SSHDã䜿ã
JSchã®æŽæ°ãæ¢ãŸã£ãŠããããšãç¥ããªãã£ãã®ã§ãã¡ãã£ãšé©ããŸããã
ã§ã¯ä»ã¯ã©ãã䜿ãããŠããã®ããªãšæã£ãŠèª¿ã¹ãŠã¿ããšãããApache MINA SSHDãå€ããã§ãã
ããšãã°Spring Integrationã®SFTP Adaptersã¯ãJSchããApache MINA SSHDã«ç§»è¡ããããã§ãã
Starting with version 6.0, an outdated JCraft JSch client has been replaced with modern Apache MINA SSHD framework. This caused a lot of breaking changes in the framework components.
SFTP Adapters :: Spring Integration
ãã£ãšããå©çšè ã«ã¯ãã®å€æŽãããŸãããããªãããã«ãªã£ãŠããã¿ããã§ããã
However, in most cases, such a migration is hidden behind Spring Integration API. The most drastic changed has happened with a DefaultSftpSessionFactory which is based now on the org.apache.sshd.client.SshClient and exposes some if its configuration properties.
ã¡ãªã¿ã«ã¡ãã£ãšé©ããããšãšããŠã¯ãApache MINA SSHDåŽã«Spring Integrationäºæã®ã¢ãžã¥ãŒã«ããã£ããããŸãã
https://github.com/apache/mina-sshd/tree/sshd-2.15.0/sshd-spring-sftp
Spring Integrationèªèº«ã¯ãã®ã¢ãžã¥ãŒã«ã¯äœ¿ã£ãŠããŸããã
ãšããããã§ãä»åã¯Apache MINA SSHDãèŠãŠããããšæããŸãã
ããã¥ã¡ã³ãã¯GitHubãªããžããªãŒå ã«ãããŸãã
https://github.com/apache/mina-sshd/tree/sshd-2.15.0/docs
ãµããŒãããŠããæ©èœã¯ãã¡ãã
https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/standards.md
åºæ¬çã«README.mdããããã¥ã¡ã³ããèªã¿é²ããŠããã°ããã§ãããã
https://github.com/apache/mina-sshd/tree/sshd-2.15.0
ãšã¯ãããããã¥ã¡ã³ããã¡ãã£ãšèªã¿è§£ãã¥ããã®ã§ãä»åã¯ã¡ã¢ãå ŒããŠSSHãšSFTPã䜿ã£ãŠã¿ãããšæããŸãã
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ java --version openjdk 21.0.6 2025-01-21 OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1) OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-55-generic", arch: "amd64", family: "unix"
ãŸãæ¥ç¶å ã®SSHãµãŒããŒã¯ãUbuntu Linux 24.04 LTSãšããŸãã
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.2 LTS Release: 24.04 Codename: noble $ uname -srvmpio Linux 6.8.0-55-generic #57-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 12 23:42:21 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux $ sshd -V OpenSSH_9.6p1 Ubuntu-3ubuntu13.8, OpenSSL 3.0.13 30 Jan 2024
IPã¢ãã¬ã¹ã¯192.168.0.6ãããŒãã¯2022ãšããŸãã
æºå
SSHãµãŒããŒåŽã®æºåãããŸãã
ãŠãŒã¶ãŒãäœæã
$ sudo adduser ssh-testuser
SSHãµãŒããŒã«ã¯ããã¹ã¯ãŒããšå
¬ééµèªèšŒã®äž¡æ¹ã§ãã°ã€ã³ã§ããããã«ããŠãããŸãããµã€ãã¯ãã£ããšããŠã
åŸè
ã ãã ãšæããŸããã確èªãšããæå³ã§âŠã
$ sudo su - ssh-testuser
ãã®ãŠãŒã¶ãŒã®ããŒã ãã£ã¬ã¯ããªãŒã«ãã¡ã€ã«ããã£ã¬ã¯ããªãŒãäœæããŠãããŸãã
$ echo 'Hello from Server' > hello-server.txt $ mkdir remote-dir $ echo 'Hello from Server in remote-dir' > remote-dir/hello-server-in-dir.txt $ mkdir remote-dest-dir
ãã°ã€ã³çšã®å ¬ééµãäœæããŸããããããã¯ã¯ã©ã€ã¢ã³ãåŽã§äœæããŸããã¢ã«ãŽãªãºã ã¯ed25519ã§ãã
$ mkdir ssh-keys $ ssh-keygen -f ssh-keys/testkey Generating public/private ed25519 key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in ssh-keys/testkey Your public key has been saved in ssh-keys/testkey.pub The key fingerprint is: SHA256:2I5UU/izS7EWXpXW+fTWf8yV8gPIswp67eJE+1su08E test@client The key's randomart image is: +--[ED25519 256]--+ | .. o.| | .. +.o| | o.. .o .=| | + .=+.o .*| | o.S..Bo ++o| | ..+. *E o=| | oo+ooo. o| | ..o.=+. | | o.o++. | +----[SHA256]-----+
SSHããŒãã§ããŸããããã¹ãã¬ãŒãºã¯èšå®ããã«ããŸããã
$ ll ssh-keys åèš 16 drwxrwxr-x 2 xxxxx xxxxx 4096 3æ 8 16:40 ./ drwxrwxr-x 5 xxxxx xxxxx 4096 3æ 8 16:39 ../ -rw------- 1 xxxxx xxxxx 464 3æ 8 16:40 testkey -rw-r--r-- 1 xxxxx xxxxx 93 3æ 8 16:40 testkey.pub
å ¬ééµããµãŒããŒåŽã«çœ®ããŸãã
$ mkdir $HOME/.ssh $ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeWtk8ud7ehRVLWEzaZIjcFHiMavsTJzf7HB6sfkIii test@client' > $HOME/.ssh/authorized_keys $ chmod 700 $HOME/.ssh $ chmod 600 $HOME/.ssh/authorized_keys
ããšã¯Apache MINA SSHDãäœ¿ãæºåãããŠãããŸãããã
MavenäŸåé¢ä¿ãªã©ã
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.apache.sshd</groupId> <artifactId>sshd-sftp</artifactId> <version>2.15.0</version> </dependency> <dependency> <groupId>net.i2p.crypto</groupId> <artifactId>eddsa</artifactId> <version>0.3.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.12.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.36</version> <scope>test</scope> </dependency> </dependencies>
SFTPãŸã§äœ¿ãã®ã§sshd-sftpãäŸåé¢ä¿ã«å«ããŠããŸãããSSHã¯ã©ã€ã¢ã³ãã®ã¿äœ¿ãå Žåã¯sshd-coreãããã°OKã§ãã
<dependency> <groupId>org.apache.sshd</groupId> <artifactId>sshd-core</artifactId> <version>2.15.0</version> </dependency>
Apache MINA SSHDã¯ããã©ã«ãã§OpenSSHãã©ãŒãããã®éµãèªãããšãã§ããã®ã§ãããã¢ã«ãŽãªãºã ã«ed25519ã
䜿ã£ãŠããå Žåã«ã¯ä»¥äžã®äŸåé¢ä¿ãå¿
èŠã§ãã
<dependency> <groupId>net.i2p.crypto</groupId> <artifactId>eddsa</artifactId> <version>0.3.0</version> </dependency>
ãŸãããªããBouncy Castleã远å ããŠãèªããããã«ãªããŸãã
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpg-jdk18on</artifactId> <version>1.80</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> <version>1.80</version> </dependency>
ä»åã¯eddsaã䜿ããŸããã
確èªã¯ãã¹ãã³ãŒãã§è¡ãããšã«ããŸãã
ããã§æºåå®äºã§ãã
Apache MINA SSHDã§ãSSHïŒSFTPã¯ã©ã€ã¢ã³ãã䜿ã
ã§ã¯ãApache MINA SSHDã§SSHïŒSFTPã¯ã©ã€ã¢ã³ãã䜿ã£ãŠãããŸãã
以äžã®çµã¿åããã§ããããæžããŠãããŸãã
- ãã¹ã¯ãŒãèªèšŒã§SSHæ¥ç¶ããŠãSSHã§ã³ãã³ãå®è¡
- å ¬ééµèªèšŒã§SSHæ¥ç¶ããŠãSSHã§ã³ãã³ãå®è¡
- ãã¹ã¯ãŒãèªèšŒã§SSHæ¥ç¶ããŠãSFTPæäœãå®è¡
- å ¬ééµèªèšŒã§SSHæ¥ç¶ããŠãSFTPæäœãå®è¡
ã¯ã©ã€ã¢ã³ããµã€ãã®å Žåãæåã«èŠãŠããããã¥ã¡ã³ãã¯ãã¡ãã§ããããã
https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/security-providers.md
çšæãããã¹ãã³ãŒãã®é圢ã¯ãã¡ãã
src/test/java/org/littlewings/mina/sshd/SshClientTest.java
package org.littlewings.mina.sshd; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Collection; import java.util.EnumSet; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.auth.password.PasswordIdentityProvider; import org.apache.sshd.client.channel.ClientChannel; import org.apache.sshd.client.channel.ClientChannelEvent; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.keyprovider.KeyIdentityProvider; import org.apache.sshd.common.util.security.SecurityUtils; import org.apache.sshd.sftp.client.SftpClient; import org.apache.sshd.sftp.client.SftpClientFactory; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class SshClientTest { private static final String SSH_HOST = "192.168.0.6"; private static final int SSH_PORT = 2022; private static final String SSH_USERNAME = "ssh-testuser"; private static final String SSH_PASSWORD = "password"; private static final String SSH_PRIVATE_KEY = "ssh-keys/testkey"; private static final String SSH_KEY_PASSPHRASE = "keypassword"; // ããã«ããã¹ããæžãïŒ }
ããããã¯ãããããã®ããªãšãŒã·ã§ã³ãæžããŠãããŸãã
ãã¹ã¯ãŒãèªèšŒã§SSHæ¥ç¶ããŠãSSHã§ã³ãã³ãå®è¡
æåã¯1çªåçŽãªïŒïŒïŒããã¹ã¯ãŒãèªèšŒã§SSHæ¥ç¶ããŠããã³ãã³ãå®è¡ããŸãã
åèã«ããããã¥ã¡ã³ãã¯ãã¡ãã§ãã
https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/client-setup.md
äœæãããã¹ãã³ãŒãã¯ãã¡ãã
@Test void sshClientAuthenticatePassword() throws IOException { try (SshClient sshClient = SshClient.setUpDefaultClient()) { sshClient.start(); try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã¹ã¯ãŒã clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD)); // ãã°ã€ã³ clientSession.auth().verify(); try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) { // æšæºåºåïŒãšã©ãŒåºåã®èšå® ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); clientChannel.setOut(outBaos); clientChannel.setErr(errBaos); // ã³ãã³ãå®è¡ clientChannel.open().verify(); // ã³ãã³ãçµäºåŸ ã¡ clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); System.out.println(outBaos.toString(StandardCharsets.UTF_8)); assertThat(outBaos.toString(StandardCharsets.UTF_8)) .contains("hello-server.txt", "remote-dir", "remote-dest-dir"); assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty(); } try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l remote-dir")) { ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); clientChannel.setOut(outBaos); clientChannel.setErr(errBaos); clientChannel.open().verify(); clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); System.out.println(outBaos.toString(StandardCharsets.UTF_8)); assertThat(outBaos.toString(StandardCharsets.UTF_8)) .contains("hello-server-in-dir.txt"); assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty(); } } } }
é ã远ã£ãŠèª¬æããŠãããŸãã
æåã«SshClientã®ã€ã³ã¹ã¿ã³ã¹ãäœæã
try (SshClient sshClient = SshClient.setUpDefaultClient()) {
sshClient.start();
æ¥ç¶å
ããŠãŒã¶ãŒåãæå®ããŠãClientSessionãäœæããŸãã
try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã¹ã¯ãŒã clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD)); // ãã°ã€ã³ clientSession.auth().verify();
ãã¹ã¯ãŒãã®èšå®ã¿ã€ãã³ã°ã¯ClientSessionã®ååŸåŸã§ãæåŸã«ClientSession#authããverifyãè¡ãããšã§ãã°ã€ã³ã§ããŸãã
ããšã¯ã³ãã³ãå®è¡ã§ãããClientChannelçµç±ã§è¡ããŸãã
try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) {
æšæºåºåãæšæºãšã©ãŒåºåãèšå®ã§ããã®ã§ãä»åã¯ByteArrayOutputStreamã«æºã蟌ãã§çµæã確èªã§ããããã«ããŸããã
// æšæºåºåïŒãšã©ãŒåºåã®èšå® ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); clientChannel.setOut(outBaos); clientChannel.setErr(errBaos); // ã³ãã³ãå®è¡ clientChannel.open().verify(); // ã³ãã³ãçµäºåŸ ã¡ clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); System.out.println(outBaos.toString(StandardCharsets.UTF_8)); assertThat(outBaos.toString(StandardCharsets.UTF_8)) .contains("hello-server.txt", "remote-dir", "remote-dest-dir"); assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();
ã³ãã³ãå®è¡ã®åŸã«
// ã³ãã³ãå®è¡
clientChannel.open().verify();
å®è¡çµäºãåŸ ã€ãšããããã€ã³ãã§ããããå ¥ããªãã£ããçµæã®ååŸãäžå®å®ã«ãªã£ãŠãšãŠãããããŸããâŠã
// ã³ãã³ãçµäºåŸ ã¡ clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);
ãããªãšããã§ããã
ã¡ãªã¿ã«ãæšæºåºåã«æžãåºãããçµæãSystem.out.printlnããŠããç®æããããŸããããã®çµæã¯ãã¡ãã§ãã
åèš 12 -rw-rw-r-- 1 ssh-testuser ssh-testuser 18 3æ 8 16:34 hello-server.txt drwxrwxr-x 2 ssh-testuser ssh-testuser 4096 3æ 8 18:19 remote-dest-dir drwxrwxr-x 2 ssh-testuser ssh-testuser 4096 3æ 8 16:34 remote-dir åèš 4 -rw-rw-r-- 1 ssh-testuser ssh-testuser 32 3æ 8 18:18 hello-server-in-dir.txt
å ¬ééµèªèšŒã§SSHæ¥ç¶ããŠãSSHã§ã³ãã³ãå®è¡
次ã¯å ¬ééµèªèšŒã䜿ã£ãŠæ¥ç¶ããŸãã
çµæã¯ãã¡ãã
@Test void sshClientAuthenticateKey() throws IOException, GeneralSecurityException { try (SshClient sshClient = SshClient.setUpDefaultClient()) { // SSHéµã®èšå® Collection<KeyPair> keys = SecurityUtils .getKeyPairResourceParser() .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8)); sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys)); sshClient.start(); try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã°ã€ã³ clientSession.auth().verify(); try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) { // æšæºåºåïŒãšã©ãŒåºåã®èšå® ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); clientChannel.setOut(outBaos); clientChannel.setErr(errBaos); // ã³ãã³ãå®è¡ clientChannel.open().verify(); // ã³ãã³ãçµäºåŸ ã¡ clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); System.out.println(outBaos.toString(StandardCharsets.UTF_8)); assertThat(outBaos.toString(StandardCharsets.UTF_8)) .contains("hello-server.txt", "remote-dir", "remote-dest-dir"); assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty(); } try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l remote-dir")) { ByteArrayOutputStream outBaos = new ByteArrayOutputStream(); ByteArrayOutputStream errBaos = new ByteArrayOutputStream(); clientChannel.setOut(outBaos); clientChannel.setErr(errBaos); clientChannel.open().verify(); clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L); System.out.println(outBaos.toString(StandardCharsets.UTF_8)); assertThat(outBaos.toString(StandardCharsets.UTF_8)) .contains("hello-server-in-dir.txt"); assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty(); } } } }
ãã¹ã¯ãŒãã®èšå®ã¯ãªããªããŸããã
try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã°ã€ã³ clientSession.auth().verify();
代ããã«ãSSHã®ç§å¯éµãèšå®ããŠããŸãã
// SSHéµã®èšå® Collection<KeyPair> keys = SecurityUtils .getKeyPairResourceParser() .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8)); sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys)); sshClient.start();
ä»åã¯SshClientå
šäœã«èšå®ããŠããŸãããClientSessionåäœãšããããšãã§ããããã§ãã
Set up an SSH client in 5 minutes / ClientIdentityLoader/KeyPairProvider
ããšã®éšåã¯åãã§ããã
ã¡ãªã¿ã«ä»¥äžã®äŸåé¢ä¿ãå ¥ã£ãŠããªãã£ãå Žåã¯
<dependency> <groupId>net.i2p.crypto</groupId> <artifactId>eddsa</artifactId> <version>0.3.0</version> </dependency>
ãããªäŸå€ãã¹ããŒãããããšã«ãªããŸãïŒã¢ã«ãŽãªãºã ãed25519ã«ããŠããå ŽåïŒã
java.security.NoSuchAlgorithmException: Unsupported key type (ssh-ed25519) in null
at org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser.readPublicKey(OpenSSHKeyPairResourceParser.java:221)
at org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser.extractKeyPairs(OpenSSHKeyPairResourceParser.java:133)
at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.extractKeyPairs(AbstractKeyPairResourceParser.java:198)
at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.extractKeyPairs(AbstractKeyPairResourceParser.java:167)
at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.loadKeyPairs(AbstractKeyPairResourceParser.java:117)
at org.apache.sshd.common.config.keys.loader.KeyPairResourceParser$2.loadKeyPairs(KeyPairResourceParser.java:166)
at org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader.loadKeyPairs(KeyPairResourceLoader.java:157)
ãã¹ã¯ãŒãèªèšŒã§SSHæ¥ç¶ããŠãSFTPæäœãå®è¡
次ã¯SFTPã§ããåç §ããããã¥ã¡ã³ãã¯ãã¡ãã
https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/sftp.md
ãŸãã¯ãã¹ã¯ãŒãèªèšŒã§ããå®ã¯ãéäžãŸã§ã¯å ã»ã©ãŸã§ãšåãã§ãã
@Test void sftpClientAuthenticatePassword() throws IOException { try (SshClient sshClient = SshClient.setUpDefaultClient()) { sshClient.start(); try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã¹ã¯ãŒã clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD)); // ãã°ã€ã³ clientSession.auth().verify(); try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) { // get try (InputStream is = sftpClient.read("hello-server.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n"); } try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n"); } // put sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml"); // get try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).contains( "<groupId>org.apache.sshd</groupId>", "<artifactId>sshd-sftp</artifactId>" ); } } } } }
SftpClientãäœæããã®ã«ãå
ã»ã©ãŸã§äœ¿ã£ãŠããClientSessionãå¿
èŠã«ãªããŸãã
try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) {
ãã¡ã€ã«åä¿¡ãéä¿¡ãªã©ã
// get try (InputStream is = sftpClient.read("hello-server.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n"); } try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n"); } // put sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml"); // get try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).contains( "<groupId>org.apache.sshd</groupId>", "<artifactId>sshd-sftp</artifactId>" ); }
éä¿¡ãããã¡ã€ã«ã¯ãpom.xmlã«ããŸããã
SftpClientãååŸããåŸã¯ããããŸã§æäœã«è¿·ãããšã¯ãªããšæããŸãã
å ¬ééµèªèšŒã§SSHæ¥ç¶ããŠãSFTPæäœãå®è¡
æåŸã¯å ¬ééµèªèšŒã䜿ã£ãŠæ¥ç¶ããåŸã«ãSFTPæäœãè¡ããã¿ãŒã³ã
@Test void sftpClientAuthenticateKey() throws IOException, GeneralSecurityException { try (SshClient sshClient = SshClient.setUpDefaultClient()) { // SSHéµã®èšå® Collection<KeyPair> keys = SecurityUtils .getKeyPairResourceParser() .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8)); sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys)); sshClient.start(); try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) { // ãã°ã€ã³ clientSession.auth().verify(); try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) { // get try (InputStream is = sftpClient.read("hello-server.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n"); } try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n"); } // put sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml"); try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); is.transferTo(baos); assertThat(baos.toString(StandardCharsets.UTF_8)).contains( "<groupId>org.apache.sshd</groupId>", "<artifactId>sshd-sftp</artifactId>" ); } } } } }
ãããŸã§ãããšãæ°ããèŠçŽ ã¯ãããŸããã
ãããªãšããã§ããããã
ãããã«
Apache MINA SSHDã§SSHïŒSFTPã¯ã©ã€ã¢ã³ãã䜿ã£ãŠã¿ãŸããã
ããã¥ã¡ã³ãã®ã³ãŒãäŸã«çç¥ç®æãå€ãã£ãã®ã§ããŸãã€ã¡ãŒãžãæŽãã詊ããŠã¿ãã®ã§ããããŸãããããããããŸããã
ãŸãããªããšããªããŸããã
ããã«ããŠããApache MINAèªäœãããšããšãããã¯ãŒã¯ç³»ã®ã©ã€ãã©ãªãŒã ããããå©çšåŽãããããšããäœã¬ã€ã€ãŒãª
æããããŸããããå°ãç°¡åã«æžãããå Žåã¯SSHJã®æ¹ã䟿å©ããã§ããã