How to create a java ssh server mock


Yesterday I’ve needed to create a unit test case for a java SFTP tool that reads a file on a ssh server every 30 minutes or so.

First I’ve thought that making a mock with Mockito should solve the problem, but then in production environment some connection instabilities appeared to bug the code. In order to make the test more realistic i  decided to use a ssh server mock in memory using  java during the tests. This way my code would start the server, run the test and stop the server. Easy don’t you think?

Well, the test was easy but finding a good java ssh server that works with sftp was a long task.

After reading some info in other sites I’ve found that there was 2 straight ways to this:

  1. Use Apache Mina SSHD library
  2. Use J2SSH

First i tried Apache Mina SSHD library, as this is by far the best ssh server option in java. But it’s a bit complex too, considering my simple task. Every step i made there was a problem to solve, so I’ve finally got stuck with an I/O error and decided to move on to J2SSH.

As for J2SSH, i can’t say that it is a reliable SSH server.. And it’s certainly a bit complex too.

Most people say that it has many, many problems, but I’ve managed to get it working for my simple test. If you want more from it, you can try J2SSH Fork which solves some common bugs and implements a few tweaks.

I will show you the code I’ve used to get J2SSH working in my tests, but i only needed to connect, list a directory, read some files and move them to another folder.. Other than that i can’t tell you if it will work correctly =(.

I’ve found this library reading BINDUL’s post about this, which explains even better how to do it, i really recommend you to read  his blog too ;).

The steps:

First download J2SSH and add it to your project classpath.

Second create a file called server.xml in your test folder (ex. src/test/resources)

Sample server.xml content:

<?xml version="1.0"?>
<ServerConfiguration>
  <!-- this key needs generating by the provided keygen tool -->
  <ServerHostKey PrivateKeyFile="hostkey.key"/>
  <Port>2022</Port>
  <!-- ListenAddress is optional; the default is 0.0.0.0 which listens
    on any address. If you do specify an address it's highly recommended
    that you use an IP, not a hostname. A hostname will introduce a
    dependency on DNS. -->
  <ListenAddress>127.0.0.1</ListenAddress>
  <MaxConnections>3</MaxConnections>
  <!-- add other authentication methods as desired -->
  <AllowedAuthentication>password</AllowedAuthentication>
  <AllowedAuthentication>keyboard-interactive</AllowedAuthentication>
  <!-- You can specify more subsystems, or even a replacement SFTP subsystem -->
  <Subsystem Name="sftp" Type="class" Provider="com.sshtools.daemon.sftp.SftpSubsystemServer" />
</ServerConfiguration>

Third create a file called platform.xml in your test folder (ex. src/test/resources)

Sample platform.xml content

<?xml version="1.0" encoding="UTF-8"?>
<!--
Platform configuration file - Determines the behaviour of platform specific services
-->
<PlatformConfiguration>
   <!-- The process provider for executing and redirecting a process -->
   <NativeProcessProvider>com.sshtools.daemon.platform.UnsupportedShellProcessProvider</NativeProcessProvider>
   <!-- The authentication provider for authenticating users and obtaining user information -->
   <!--
                WARNING: the dummy provider here doesn't ask for any passwords so obviously it's
                extremely insecure. You should only use it for testing.
        -->
   <NativeAuthenticationProvider>br.com.petrobras.eqsb.mock.DummyAuthenticationProvider</NativeAuthenticationProvider>
   <!-- The file system provider for SFTP -->
   <NativeFileSystemProvider>com.sshtools.daemon.vfs.VirtualFileSystem</NativeFileSystemProvider>
   <!-- Native settings which may be used by the process or authentication provider -->
   <!-- Add native settings here -->
   <!-- <NativeSetting Name="AuthenticateOnDomain" Value="."/> -->
</PlatformConfiguration>

Fourth create a file called hostkey.key in your root project folder (next to .classpath or .project file in eclipse)

Sample hostkey.key content

-----BEGIN DSA PRIVATE KEY-----
MIIBvAIBAAKBgQDJhbBmBIlrSeF4YKcP7cB38Qf+lxlJsZVBU+OA8bJo5XsJ6g7x
hGkWGUbv/DjkNBnRvB9nQmFho/pXDNAhz/9tjIqmZifMqVn7yVfIYgNWW6eekzEv
qr1LWIvzm97PRLgNOUwWNp9qHYVg4srZxC1u5F5a+Su04rDMUtvnSwipQwIVAJ9N
DIm1l3DXczHUSSWT1jvRUfdlAoGBALzjClBUBz/XfmMI6Wy2VwATXbI0DPkh7Abn
kNFWQCvm3ob9WTTYlKiRtoo1d71BW/7WB3fWvPahJAPfAxpI8v3k5KibQqpcXjj6
Bo2opzKidLMnpduWyectUw8JiKdGq4gXrgjIU6ucrm/MahAK8xFQhAxHf3PJE0fz
XByjSMC8AoGBAMAD1PkgtwK/sl3mji9bQDofUurHof1aQh/7q91TdYlhY/HBXpv5
rs09sOCCAsRK2yaEcsU967xiMJ4zeABGt3uXBeddG+540JlcBqSl45RaJJcO6AH3
hVk7TAy1pLgMhpOt0XE1MylRF8cY9VfZ9Thj56jWylCDv4m8JaRYBpxrAhRNOllu
5nUYR0Te6j8XEufluVJ64Q==
-----END DSA PRIVATE KEY-----

Fifth, create the java code that will authorize the user and inform the user home folder like :

(you will need to edit the platform.xml file and point to this class inside the <NativeAuthenticationProvider> tag)

import java.io.File;
import java.io.IOException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sshtools.daemon.platform.NativeAuthenticationProvider;
import com.sshtools.daemon.platform.PasswordChangeException;

/**
 * This authentication provider provides no authentication at all, and just lets anybody in so you should never use it!
 *
 * It is really just for testing.
 */
public class DummyAuthenticationProvider extends NativeAuthenticationProvider {

	Log log = LogFactory.getLog(DummyAuthenticationProvider.class);

	public DummyAuthenticationProvider() {
		log.error("DummyAuthenticationProvider is in use. This is only for testing.");
	}

	@Override
	public boolean changePassword(String username, String oldpassword, String newpassword) {
		return false;
	}

	@Override
	public String getHomeDirectory(String username) throws IOException {
		return new File("src/test/resources/csv/transfer").getCanonicalPath();
	}

	@Override
	public void logoffUser() throws IOException {

	}

	@Override
	public boolean logonUser(String username, String password) throws
                            PasswordChangeException, IOException {
               return true;
        }

       @Override
       public boolean logonUser(String username) throws IOException {
              return true;
       }

}

Sixth and last, create the SFTP server java code:

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;

import com.sshtools.daemon.SshServer;
import com.sshtools.daemon.configuration.ServerConfiguration;
import com.sshtools.daemon.configuration.XmlServerConfigurationContext;
import com.sshtools.daemon.forwarding.ForwardingServer;
import com.sshtools.daemon.session.SessionChannelFactory;
import com.sshtools.j2ssh.configuration.ConfigurationException;
import com.sshtools.j2ssh.configuration.ConfigurationLoader;
import com.sshtools.j2ssh.connection.ConnectionProtocol;

/**
 * To use this server you will need:
 * hostkey.key, server.xml, platform.xml and DummyAuthenticationProvider.java
 *
 * @author diniz
 *
 */
public class SSHServerMock {

	private String serverXmlRelativePath = "src/test/resources/server.xml";

	private String platformXmlRelativePath = "src/test/resources/platform.xml";

	public SSHServerMock(){
		this(null, null);
	}

	public SSHServerMock(String serverXmlRelativePath, String platformXmlRelativePath){
		this.serverXmlRelativePath = serverXmlRelativePath==null?this.serverXmlRelativePath:serverXmlRelativePath;
		this.platformXmlRelativePath = platformXmlRelativePath==null?this.platformXmlRelativePath:platformXmlRelativePath;
	}

	public void start() throws IOException {
		Executors.newSingleThreadExecutor().submit(new Callable<Object>() {
	        @Override
	        public Object call () throws Exception {
	        	XmlServerConfigurationContext context = new XmlServerConfigurationContext();
	    		context.setServerConfigurationResource(ConfigurationLoader.checkAndGetProperty("sshtools.server", serverXmlRelativePath));
	    		context.setPlatformConfigurationResource(System.getProperty("sshtools.platform", platformXmlRelativePath));
	    		ConfigurationLoader.initialize(false, context);
	    		SshServer server = new SshServer() {

	    			public void configureServices(ConnectionProtocol connection) throws IOException {
	    				connection.addChannelFactory(SessionChannelFactory.SESSION_CHANNEL, new SessionChannelFactory());
	    				if (ConfigurationLoader.isConfigurationAvailable(ServerConfiguration.class)) {
	    					if (((ServerConfiguration) ConfigurationLoader.getConfiguration(ServerConfiguration.class)).getAllowTcpForwarding()) {
	    						new ForwardingServer(connection);
	    					}
	    				}
	    			}

	    			public void shutdown(String msg) {
	    				// Disconnect all sessions
	    			}

	    		};
	    		server.startServer();
	            return null;
	        }
	    });
	}

	public void stop() throws ConfigurationException, UnknownHostException, IOException {
		Socket socket = new Socket(InetAddress.getLocalHost(),
				((com.sshtools.daemon.configuration.ServerConfiguration) ConfigurationLoader.getConfiguration(com.sshtools.daemon.configuration.ServerConfiguration.class)).getCommandPort());

		// Write the command id
		socket.getOutputStream().write(0x3a);
		// Write the length of the message (max 255)
		String msg = "bye";
		int len = (msg.length() <= 255) ? msg.length() : 255;
		socket.getOutputStream().write(len);
		if (len > 0) {
			socket.getOutputStream().write(msg.substring(0, len).getBytes());
		}
		socket.close();
	}

Here’s the eclipse project with everything configured and working. Just start the (org.test.)FtpServerTest.java class with junit and the server will be up and running for 20 minutes. When connected, you will see that it is listing the files available in the src\test\resources\csv\transfer folder.

Now you’re ready to write more realistic test codes for your project! You can even use Filezilla to connect to this mock server if you need. It’s pretty fast too =).

28 thoughts on “How to create a java ssh server mock

  1. Nilesh

    Hi, thanks for the article. Can you also post some examples of your JUnit Tests? I created the setup as per your instructions, but not able to connect it using a SFTP client like CyberDuck

  2. Nilesh

    Thanks Pedro. I think yesterday my brain was not working at all ;-). I was getting confused about the authentication and complicating things. After I looked at your unit test, I saw a comment which said, any user/password and thats when I realized that I should login anonymously. And it worked like a charm. Thanks for to the point, step by step article. I googled so much and bumped into the article that you referred. I found the original article too verbose and with many details. I scrolled down and found link to your blog in the comment you made. Glad I saw your comment. Your blog is bookmarked :-))

    Thanks again!

  3. Thanks a lot Nilesh, really =). I’m glad to hear that you got it working!
    And thanks for leaving your comment too, indeed the junit source code makes a huge difference, it’s a lot of information to process with just words…

    Just take a look at the login issue again cause you don’t need to be ANONYMOUS, you can login with a username and password.
    To do that, change the tag inside platform.xml and point to org.something.YourDummyAuthenticationProvider class.
    Then everytime an user tries to login, the server will call your class to validate it. So you will be able to deny or allow any user you want 🙂

  4. Hi!
    First thank you for the post thats cool!
    I try to involve this in maven project, but the one i get is this exception:

    WARN No appenders could be found for logger (com.sshtools.j2ssh.configuration.ConfigurationLoader).
    log4j:WARN Please initialize the log4j system properly

    Do yuo have idea where i’m wrong?

  5. Hi zkalevzhivko !
    The message “log4j:WARN Please initialize the log4j system properly” means that some library in your project has a dependency to log4j jar.

    The message “WARN No appenders could be found for logger ” means that, since your project consequently depends on log4j jar, it’s not finding the log4j.properties that contains the log4j configuration to be used.

    To solve it, you need to create a file called log4j.properties in your classpath.
    Then paste this in your new log4j.properties file and it should be good to go:

    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.Threshold=INFO
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=- %m%n
    log4j.rootCategory = ERROR, CONSOLE

    1. No zkalev, i didn’t.
      My tests didn’t required it to go further into the certification steps. All i needed was to validate the server with the “fake” host.key file.
      J2SSH was made to support that though, so i believe it should work =)

      1. First, you need to add a repository that contains the j2ssh jars inside your pom.xml. Then add in the dependencies section of your pom.xml the j2ssh jars that your project will depend on.
        Ex.
        <repositories>
        <repository>
        <id>releases</id>
        <name>Java Net maven repository 2</name>
        <url>http://download.java.net/maven/2 </url>
        </repository>
        </repositories>

        <dependencies>

        <dependency>
        <groupId>sshtools</groupId>
        <artifactId>j2ssh-daemon</artifactId>
        <version>0.2.9</version>
        </dependency>

        <dependency>
        <groupId>sshtools</groupId>
        <artifactId>j2ssh-common</artifactId>
        <version>0.2.9</version>
        </dependency>

        <dependency>
        <groupId>sshtools</groupId>
        <artifactId>j2ssh-core</artifactId>
        <version>0.2.9</version>
        </dependency>

        </dependencies>

        Now maven knows which and where to download the j2ssh jars that your project will need =)

  6. Thanks for explanation!Yes i was did it ,in other case the code would have never compile.My answer is not correct.I can’t connect to the server when use maven install.In eclipse it’s work properly.I wrote code that create folder in given directory and move given files in that folder.In eclipse work.My code works with real server in maven.I have class that create connection to the server and method cleanDyrectory(“./directoryForCleaning”,sftp).I think the problem is in that maven doesn’t recognize “./directoryForCleaning” like directory on server.I try different cases to give root dir for server but without succeed.
    Thank’s a lot for that you tried to help me!!!

    1. Thanks zkalev, i think i’m understanding what’s the problem now.
      In short, you’ve created a new method called cleanDirectory(String, SFTP). It works perfectly in eclipse but it’s not working when you build it with maven.
      This makes sense. Since in eclipse your project is not a jar, you can create, delete and edit any folder you want as you would in any other folder.
      But when maven builds the project it turns your project into a .jar file. And since a jar file has a zipped content, java doesn’t allows you to delete, create or edit anything inside it.
      What you need to do is to create, delete or edit these folders outside the project folder, like “c:\directoryForCleaning” instead of “.\directoryForCleaning”.
      Since this folder will not be in the project folder, maven will not place it into the generated jar file and you will be able do create, delete or edit it.
      Send me an email (itsiastic@gmail.com) with the files you changed so i can build it here. I can take a look if you want =).

  7. Thank’s pdiniz,that sounds right and logically.But now i realized that i can’t connect to the server,just with method connect() without given directory.Should i put all from test/resources folder outside of project,and how to reach it without connections with current computer (i.e. if sombody pull my code it must work on his computer)?
    My code just move files and i try to test it.
    Thank’s !

    1. What a wonderful news zkalev!!
      I was planning to write a new version of the unit test. This time pointing the home dir to “c:\anyFolder”, so you would be able to delete and move files freely.
      I guess it’s not necessary anymore since you already made it work.
      I’m really glad to see that it’s working for you now =).

  8. Koi

    Hi Pedro, great work! works perfectly. I’ve a couple of questions about this project.
    please contact me via email.
    btw the link for mina sshd seems to be broken 🙂
    hope to hear from you soon.
    greeets, Koi

  9. wonderful post! Many thanks… I tried to to run your test. It runs and it sems that server is started. I tried to put debug points inside DummyAuthenticationProvider. Code never stops at debug points. Even info statement inside constructor of DummyAuthenticationProvider is never printed. Seems that DummyAuthenticationProvider is not used at all.

    1. Hi prithvipatil, thanks for your comment. Did you download the sample coded i’ve provided: https://docs.google.com/file/d/0B-QS6MSZCNkQeXF4bmZ2azB6a28/edit ? I’ve just downloaded it a few seconds ago, imported it as a java project in eclipse, started the class FtpServerTest via junit and connected to the server on localhost:2022 using cyberduck. In cyberduck i’ve selected the SFTP connection type and provided a random user:pass like teste:teste. Debug worked correctly as expected, it stopped at the constructor DummyAuthenticationProvider(), at getHomeDirectory() and at logonUser().
      Check again please if you’re really using a SFTP connection and providing a user:pass credentials. Also, can you post your project so i can download and test it?

  10. Also when I tried to run test build project in maven based env, I get following exception:
    ava.lang.NoClassDefFoundError: Could not initialize class com.sshtools.j2ssh.configuration.ConfigurationLoader
    at org.test.SSHServerMock.stop(SSHServerMock.java:72)
    at org.test.FtpServerTest.tearDown(FtpServerTest.java:35)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:33)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252)
    at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141)
    at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189)
    at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165)
    at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85)
    at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115)
    at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75)

    I have added following dependencies:

    sshtools
    j2ssh-core
    0.2.9

    sshtools
    j2ssh-common
    0.2.9

    sshtools
    j2ssh-daemon
    0.2.9

    junit
    junit
    4.12

    1. The version seems to be ok, but apparently some j2ssh class is missing. Please try to post your project so i can download and test it, it looks like a classpath problem only.

      1. prithvipatil, i’ve found the problem:
        Your pom.xml does not includes commons-logging and j2ssh depends on it to work correctly. Just add this maven dependency and the error will be gone:

        <dependency>
        <groupId>sshtools<groupId>
        <artifactId>j2ssh-core<artifactId>
        <version>0.2.9<version>
        </dependency>

        Hope it helps!

      2. I am just amazed to see how you find the issue :O Cause logs didnt say anything about commons logging!!! I appreciate your prompt help. Thanks a ton 🙂

        Another thing, Server is up now it seems as my junit test is waiting for completion. I tried to connect to the server, using filezilla, winscp or putty client. Nothing worked. I used following config:
        host: 127.0.0.1
        port: 2022
        username: test
        password: test

        I also tried to change host to localhost. Also tried changing port to 22. But nothing worked.

    2. Filezilla logs:

      Status: Resolving address of localhost
      Status: Connecting to 127.0.0.1:2022…
      Status: Connection attempt failed with “ECONNREFUSED – Connection refused by server”, trying next address.
      Status: Connecting to [::1]:2022…
      Status: Connection attempt failed with “ECONNREFUSED – Connection refused by server”.
      Error: Could not connect to server
      Status: Waiting to retry…
      Status: Resolving address of localhost
      Status: Connecting to 127.0.0.1:2022…
      Status: Connection attempt failed with “ECONNREFUSED – Connection refused by server”, trying next address.
      Status: Connecting to [::1]:2022…
      Status: Connection attempt failed with “ECONNREFUSED – Connection refused by server”.
      Error: Could not connect to server

      1. prithvipatil, you are missing the hostkey.key file. Put a hostkey.key in the root of your project folder and it will work. Your project worked here after adding it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s