How to SSH to a server behind another SSH server using JSch? - java

I need to be able to ssh from a Java program into a remote server, and from there SSH to another server. I have credentials for both servers on my client.
The commands will be passed automatically from within the app as regular strings (no user input). I need to be able to run those custom commands on the second server and be able to decide what commands to issue during runtime, based on the output and some simple logic.
Can I use JSch to do that and if yes, where should I start look into? (Examples, info)
=============================================================
ADDED:
Exception in thread "main" com.jcraft.jsch.JSchException:
UnknownHostKey: host.net. RSA key fingerprint is 'blahblahblah'
as till now, I am solving this problem by modifying the known_hosts file and adding host manually in there.
Can I bypass this little problem by settings an option somewhere telling the JSch to press YES automatically when this YES-NO question is asked?

To connect to a second server behind a firewall, there are in principle two options.
The naive one would be to call ssh on the first server (from an exec channel), indicating the right server. This would need agent forwarding with JSch, and also doesn't provide the JSch API to access the second server, only the ssh command line.
The better one would be to use the connection to the first server to build up a TCP Tunnel, and use this tunnel to connect to the second server. The JSch Wiki contains a ProxySSH class (together with some example code) which allows to use a JSch session as a tunnel for a second JSch session. (Disclaimer: This class was written mainly by me, with some support from the JSch author.)
When you have your connection to the second server, use either a shell channel or a series of exec channels to execute your commands. (See Shell, Exec or Subsystem Channel in the JSch Wiki for an overview, and the Javadocs for details.)
For your unknown-host-key problem:
The secure version would be to collect all host keys (in a secure way) before and put them in the known_hosts file. (If you simply trust the key which is presented to you, you are vulnerable to a man-in-the-middle attack. If these are of no concern in your network, since it is physically secured, good for you.)
The convenient version is setting the configuration option StrictHostKeyChecking to no - this will add unknown host keys to the host keys file:
JSch.setConfig("StrictHostKeyChecking", "no");
(You can also set it individually on the sessions, if you only want to set it for the proxied sessions and not for the tunnel session. Or override it for the tunnel session with yesor ask - there the MITM danger might be greater.)
A middle way would be to enable actually asking the user (which then should compare the fingerprints to some list) - for this, implement the UserInfo interface and provide the object to the session. (The JSch Wiki contains an example implementation using Swing JOptionPanes, which you can simply use if your client program runs on a system with GUI.)
For the saving of accepted host keys to work, you must use the JSch.setKnownHosts method with a file name argument, not the one with an InputStream argument - else your accepting will have to be repeated for each restart of your client.

Use an SSH tunnel, aka local port forwarding, to open an SSH/SFTP connection to B via A.
Session sessionA = jsch.getSession("usernameA", "hostA");
// ...
sessionA.connect();
int forwardedPort = sessionA.setPortForwardingL(0, "hostB", 22);
Session sessionB = jsch.getSession("usernameB", "localhost", forwardedPort);
// ...
sessionB.connect();
// Use sessionB here for shell/exec/sftp
You may need to deal with UnknownHostKey exception.

This can help anyone. Works fine:
public static void sesionA(){
try {
Session sessionA = jSch.getSession(username, hostA);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sessionA.setConfig(config);
sessionA.setPassword(passwordA);
sessionA.connect();
if(sessionA.isConnected()) {
System.out.println("Connected host A!");
forwardedPort = 2222;
sessionA.setPortForwardingL(forwardedPort, hostB, 22);
}
} catch (JSchException e) {
e.printStackTrace();
}
}
public static void sesionB(){
try {
Session sessionB = jSch.getSession(username, "localhost", forwardedPort);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
sessionB.setConfig(config);
sessionB.setPassword(passwordB);
sessionB.connect();
if(sessionB.isConnected()) {
System.out.println("Connected host B!");
}
}
}

Related

StrictHostChecking with JSCH API [duplicate]

I'm running a java program where I transfer a file from one folder to another, using Java SFTP. The problem I'm having is that I'm getting the following error in my Java SFTP (using JSch) :
C:\Oracle\Middleware\Oracle_Home\oracle_common\jdk\bin\javaw.exe
-server -classpath C:\JDeveloper\mywork\Java_Hello_World.adf;C:\JDeveloper\mywork\Java_Hello_World\Client\classes;C:\Users\ADMIN\Downloads\jsch-0.1.53.jar
-Djavax.net.ssl.trustStore=C:\Users\IBM_AD~1\AppData\Local\Temp\trustStore5840796204189742395.jks
FileTransfer com.jcraft.jsch.JSchException: UnknownHostKey: 127.0.0.1.
RSA key fingerprint is a2:39:3f:44:88:e9:1f:d7:d1:71:f4:85:98:fb:90:dc
at com.jcraft.jsch.Session.checkHost(Session.java:797) at
com.jcraft.jsch.Session.connect(Session.java:342) at
com.jcraft.jsch.Session.connect(Session.java:183) at
FileTransfer.main(FileTransfer.java:33) Process exited with exit code
0.
The following is my code so far:
FileTransfer fileTransfer = new FileTransfer();
JSch jsch = new JSch();
try {
String host = "127.0.0.1";
int port = 22;
String user = "user";
Session session = jsch.getSession(user, host, port);
session = jsch.getSession("username", "127.0.0.1", 22);
session.connect(); // bug here , java.net.ConnectException
ChannelSftp sftp = null;
sftp = (ChannelSftp)session.openChannel("sftp") ; //channel;
//extra config code
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// end extra config code
sftp.rename("C:\\Users\\ADMIN\\Desktop\\Work\\ConnectOne_Bancorp\\Java_Work\\SFTP_1\\house.bmp", "C:\\Users\\ADMIN\\Desktop\\Work\\ConnectOne_Bancorp\\Java_Work\\SFTP_2\\house.bmp");
session.disconnect();
} catch (JSchException e) {
e.printStackTrace();
} catch (SftpException e) {
e.printStackTrace();
} //end-catch
My Cygwin is set up, and I checked (with netstat -a -b ) that it's running.
You are trying to skip a host key checking by setting StrictHostKeyChecking to no.
But you have to do that before the checking, i.e. before the session.connect().
Anyway, you should never do this, unless you do not care about security. The host key checking is there to protect you from man-in-the-middle attacks.
Instead, set up an expected host key to let JSch verify it.
For example:
Call JSch.setKnownHosts providing a path to a .ssh/known_hosts-like file.
To generate the .ssh/known_hosts-like file, you can use an ssh-keyscan command from OpenSSH. If you are connecting from a *nix server, you should have the command available, just run
ssh-keyscan example.com > known_hosts
It will have a format like:
example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0hVqZOvZ7yWgie9OHdTORJVI5fJJoH1yEGamAd5G3werH0z7e9ybtq1mGUeRkJtea7bzru0ISR0EZ9HIONoGYrDmI7S+BiwpDBUKjva4mAsvzzvsy6Ogy/apkxm6Kbcml8u4wjxaOw3NKzKqeBvR3pc+nQVA+SJUZq8D2XBRd4EDUFXeLzwqwen9G7gSLGB1hJkSuRtGRfOHbLUuCKNR8RV82i3JvlSnAwb3MwN0m3WGdlJA8J+5YAg4e6JgSKrsCObZK7W1R6iuyuH1zA+dtAHyDyYVHB4FnYZPL0hgz2PSb9c+iDEiFcT/lT4/dQ+kRW6DYn66lS8peS8zCJ9CSQ==
And reference the generated known_hosts file in your JSch code.
If you are on Windows, you can get a Windows build of ssh-keyscan from Win32-OpenSSH project or Git for Windows.
Call JSch.getHostKeyRepository().add() to provide the expected host key (e.g. hard-coded, as your other credentials).
See Creating JSch HostKey instance from a public key in .pub format.
jsch version : 0.1.55
my problem solved by running :
ssh-keyscan -t rsa <HOST_NAME> >> ~/.ssh/known_hosts
ssh-keyscan -t rsa <IP_ADDRESS_OF_HOST_NAME> >> ~/.ssh/known_hosts
**in my case jsch was looking for ip address in known_hosts file
jsch.setKnownHosts(System.getProperty("user.home")+"/.ssh/known_hosts");
Aside: by "Cygwin" I assume you mean sshd or sftpd, because Cygwin itself doesn't do SSH.
Anyway, if you want Jsch client to accept any key from the host, move the .setConfig calls that sets StrictHostKeyChecking no so it is before session.connect(). Alternatively you must provide access to a store containing the correct key(s) for your hosts(s) as #Martin explains -- and you should always do that when connecting to anything other than "localhost" or possibly a machine certain to be on the same, physically-secure network segment (such as a wired LAN hub within a single room).

What is use of config.put("StrictHostKeyChecking", "no") in JSch

java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
In above code why we need to set StrictHostKeyChecking value as no while connection to SFTP through JSch API?
You should NOT set it actually. You lose much of the SSH/SFTP security by doing to.
The option tells the JSch SSH/SFTP library not to verify public key of the SSH/SFTP server. You are vulnerable to man-in-the-middle attacks, if you do not verify the public key. Of course, unless you are connecting within a private trusted network (so you do not care for security/encryption).
Read about SSH/SFTP host keys:
https://winscp.net/eng/docs/ssh_verifying_the_host_key
StrictHostKeyChecking values: ask | yes | no
default: ask
If this property is set to yes, JSch will never automatically add host keys to the $HOME/.ssh/known_hosts file, and refuses to connect to hosts whose host key has changed. This property forces the user to manually add all new hosts.
If this property is set to no, JSch will automatically add new host keys to the user known hosts files.
If this property is set to ask, new host keys will be added to the user known host files only after the user has confirmed that is what they really want to do, and JSch will refuse to connect to hosts whose host key has changed.

Java - connect to webpage using SSH tunneling

fellow Java coders. I have recently been faced with an interesting task - to create software that would use an SSH tunnel as a proxy for browsing webpages (over HTTPS). After reading some docs on JSCH (http://www.jcraft.com/jsch/, a Java SSH tunneling library), which all gave database connections as an example, I decided to try it myself. Here is the connection code I copied from http://kahimyang.info/kauswagan/code-blogs/1337/ssh-tunneling-with-java-a-database-connection-example
int assigned_port;
int local_port=3309;
// Remote host and port
int remote_port=3306;
String remote_host = "<SSH host goes here>";
String login = "<SSH login goes here>";
String password = "<SSH password goes here>";
try {
JSch jsch = new JSch();
// Create SSH session. Port 22 is your SSH port which
// is open in your firewall setup.
Session session = jsch.getSession(login, remote_host, 22);
session.setPassword(password);
// Additional SSH options. See your ssh_config manual for
// more options. Set options according to your requirements.
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
config.put("Compression", "yes");
config.put("ConnectionAttempts","2");
session.setConfig(config);
// Connect
session.connect();
// Create the tunnel through port forwarding.
// This is basically instructing jsch session to send
// data received from local_port in the local machine to
// remote_port of the remote_host
// assigned_port is the port assigned by jsch for use,
// it may not always be the same as
// local_port.
assigned_port = session.setPortForwardingL(local_port,
remote_host, remote_port);
} catch (JSchException e) {
System.out.println("JSch:" + e.getMessage());
return;
}
if (assigned_port == 0) {
System.out.println("Port forwarding failed!");
return;
}
Now, I am not exactly experienced with all the port forwarding stuff, but, if I understand it correctly, the code is supposed to forward all connections incoming to 127.0.0.1:3309 (or whatever the assigned_port is) through the SSH server. Now I'm stuck. How am I supposed to send a HttpsURLConnection through 127.0.0.1:3309? I tried defining it as an HTTP or HTTPS or SOCKS proxy, but neither works. Can anybody help me?
The code you have posted will forward all traffic from 127.0.0.1:3309 to port 3306 on the SSH server you have connected to.
When using port forwarding you treat the listening address:port as if it were the actual destination. So if you need to use a HttpsURLConnection you would construct it with a URL of
https://127.0.0.1:3309/
Obviously you also need to append a path to the URL depending on what you are trying to achieve. I would suggest modifying your code to use more standard HTTP ports, try with HTTP first and once that is working move to HTTPS
int local_port=8080;
// Remote host and port
int remote_port=80;
The URL for above will be
http://127.0.0.1:8080
You can always test the URL by pasting it into a browser.
One of the problems you may encounter using HTTPS is certificate validation so this is why I suggest testing plain HTTP first to prove your code is working.

Port forwarding with nginx from java

I'm trying to make a java application which uses redis as a backend. Since Redis is a really fast key-value store which I'd like to use, but redis is made to be used with 1 client so it doesn't have an option for user:pass authentication. I'd like to find a way to implement some kind of authentication, so I tried nginx with the redis2 extension. I did this because I could use client-side certficates and HTTPS. But it's making my application really slow.
I'm thinking about using some kind of tunnel which connects to redis via nginx proxy. For this redis would be listen on localhost and there would be an address which I'd like to use to reach redis, but with https authentication. So basically my current method
JAVA - Jedis - LAN - REDIS ,would be
JAVA - Jedis(with localhost as the tunnel entrance?)-
-SSL LAN - Nginx(tunnel exit) - Redis
Any tip for achieving this? I've been googled the web for the last days but i couldn't come up anything that adds only a little overhead to the native connection.
Redis is designed to work on a secure network, behind a backend application. Client applications are not supposed to connect directly to Redis. It makes Redis a poor choice for a 2-tier application.
Now if you still want to use Redis for this, you have several options. You can encapsulate the Redis server in a HTTP interface. This is what the nginx redis2 module provide. You might also want to have a look at webdis, which is similar (and does not depend on nginx). Webdis offers some access control mechanisms. See the documentation.
Another solution is to establish a tunnel, as you proposed. I would not use nginx for this, but just plain old SSH. Let's suppose Redis server runs on machine B (port 6379) and client runs on machine A.
On machine A, I can run:
ssh user#host_B -L 7008:host_B:6379 -N
It will open a tunnel from A to B from local port 7008 (arbitrary choice), and waits. The user should be declared on host B, and its password known. In another session, still on host A, we can now run:
redis-cli -p 7008 ping
Please note a standard Redis client is used. The tunnel handles authentication, encryption and optionally compression in a transparent way for the client.
Now, your client is a Java application, and you probably do not want to run SSH commands to setup the tunnel. Hopefully, you can use the Jsch package to open the tunnel directly from Java. Here is an example with Jedis:
import redis.clients.jedis.*;
import java.util.*;
import com.jcraft.jsch.*;
public class TestTunnel {
Jedis jedis;
Session session;
JSch jsch = new JSch();
int port;
// None of the following should be hardcoded
static String USER = "user"; // SSH user on the redis server host
static String PASSWD = "XXXXXXXX"; // SSH user password
static String HOST = "192.168.1.62"; // Redis server host
static int PORT = 6379; // Redis server port
public TestTunnel() {
try {
// Open the SSH session
session = jsch.getSession( USER, HOST, 22 );
session.setPassword( PASSWD );
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
config.put("Compression", "yes");
config.put("ConnectionAttempts","3");
session.setConfig(config);
session.connect();
// Setup port forwarding from localhost to the Redis server
// Local port is ephemeral (given by the OS)
// Jedis connects to localhost using the local port
port = session.setPortForwardingL( 0, HOST, PORT );
jedis = new Jedis( "127.0.0.1", port );
} catch ( JSchException e ) {
// Proper error handling omitted
System.out.println(e);
}
}
public void disconnect() {
jedis.disconnect();
try {
session.delPortForwardingL( port );
session.disconnect();
} catch ( JSchException e ) {
// Proper error handling omitted
System.out.println(e);
}
}
public void mytest( int n ) {
for ( int k = 0; k < n; k++) {
jedis.set("k" + k, "value"+k);
}
System.out.println("Read: "+jedis.get("k0") );
}
public static void main(String[] args) {
TestTunnel obj = new TestTunnel();
obj.mytest(10);
obj.disconnect();
}
}
It works fine, but please note there is an overhead due to the tunnel. The overhead is very low when the network is slow (the Internet for instance). On a fast LAN (1 GbE), it is much more noticeable: the latency can be multiplied by up to 3 when the tunnel is used. The maximum throughput the Redis server can sustain is also impacted. On server-side, the sshd daemon takes some CPU (more than Redis itself).
That said, I don't think raw performance matters much for a 2-tier application.
Note: There's an SSL version of redis called SSL-REDIS which can be found on github:
https://github.com/bbroerman30/ssl-redis 2.6ish
https://github.com/tritondigital/ssl-redis 2.4ish
With this and modifying the Jedis Java client, SSL authentication could be achieved.

SFTP connection through Java asking for weird authentication

So I'm writing a little program that needs to connect to a remote server through SFTP, pull down a file, and then processes the file. I came across JSch through some answers here and it looked perfect for the task. So far, easy to use and I've got it working, with one minor thing I'd like to fix. I'm using the following code to connect and pull the file down:
JSch jsch = new JSch();
Session session = null;
try {
session = jsch.getSession("username", "127.0.0.1", 22);
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword("password");
session.connect();
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp sftpChannel = (ChannelSftp) channel;
sftpChannel.cd(REMOTE_FTP_DIR);
sftpChannel.lcd(INCOMING_DIR);
sftpChannel.get(TMP_FILE, TMP_FILE);
sftpChannel.exit();
session.disconnect();
} catch (JSchException e) {
e.printStackTrace();
} catch (SftpException e) {
e.printStackTrace();
}
So this works and I get the file. I'm running this code on a linux server and when I run the code JSch asks me for my Kerberos username and password. It looks like:
Kerberos username [george]:
Kerberos password for george:
I just hit enter for both questions and then the program seems to continue on with no problems. However I need this code to be automated through a cron task and so I'd rather not having it pausing the program to ask me these two questions. Is there something I'm not supplying it so that it won't ask this? Something I need to do to stop it asking? Hopefully someone has some ideas. Thanks.
Thought I'd post an answer here since in case anyone else ends up running into a similar issue. Turns out I am missing a piece of code that makes all the difference. I just needed to add
session.setConfig("PreferredAuthentications",
"publickey,keyboard-interactive,password");
before
session.connect();
and everything works perfectly now.
While the solution in the self-accepted answer is correct, it lacks any explanation.
The problem is that the OP have a Kerberos/GSSAPI authentication set as the preferred (the JSch default). Yet OP does not seem to actually use/want it, as OP claims not to specify any username or password for the Kerberos prompts.
This problem can appear spontaneously, when either Kerberos gets installed on the the client PC or the server starts to support Kerberos.
The solution is to remove the Kerberos/GSSAPI (gssapi-with-mic) from the list of preferred authentication methods in JSch:
session.setConfig(
"PreferredAuthentications", "publickey,keyboard-interactive,password");
All answers are correct, I'll just add here the way it can be done for Spring Integration when trying to integrate with an SFTP server.
So, if you are using SFTP Spring Integration and the weird user and password for Kerberos is prompting in the same way the OP is asking.
Then modify your Spring configuration (I'm using Java Spring Integration config, if you are using XML config you can try to translate it yourself - I really don't like XML config :P ):
So in the bean you are using as SessionFactory you need to add this change in config:
#Bean
public SessionFactory<LsEntry> sftpSessionFactory() {
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
factory.setHost("hostname");
factory.setPort(22);
factory.setUser("username");
factory.setPassword("superstrongpassword");
factory.setAllowUnknownKeys(true);
factory.setSessionConfig(buildSessionProperties());
return new CachingSessionFactory<>(factory);
}
/**
* Build JSch property PreferredAuthentications without "gssapi-with-mic"
* This way it won't prompt for Kerberos authentication every time it tries to connect
* to the SFTP.
*/
private Properties buildSessionProperties() {
Properties sessionProperties = new Properties();
sessionProperties.setProperty("PreferredAuthentications", "publickey,keyboard-interactive,password");
return sessionProperties;
}

Categories