net.javaforge.blog - Embedded SSH daemon and remote shell for your java application

Install this theme
Embedded SSH daemon and remote shell for your java application

Wouldn’t it be nice to have a possibility to connect you application securely via SSH with a secret user/password and communicate with it within the application specific shell? That was my first idea as I found the Apache’s SSHD project.

Apache SSHD is a 100% pure java library to support the SSH protocols on both the client and server side. This library is based on Apache MINA, a scalable and high performance asynchronous IO library.

This blog post demonstrates how is it possible to create a application shell using Apache SSHD and JLine projects.

  1. To start a new demo project we will use Maven’s archetype:generate goal and create a new project skeleton:
    mvn archetype:generate \
     -DgroupId=net.javaforge.blog \
     -DartifactId=sshd-daemon-demo \
     -DarchetypeArtifactId=maven-archetype-quickstart \
     -DinteractiveMode=false
    
  2. Now we will put all neccessary dependencies to the project’s pom.xml:
    <dependency>
    	<groupId>org.apache.sshd</groupId>
    	<artifactId>sshd-core</artifactId>
    	<version>0.9.0</version>
    </dependency>
    <dependency>
    	<groupId>jline</groupId>
    	<artifactId>jline</artifactId>
    	<version>2.11</version>
    </dependency>
    <!-- not really necessary, just to simplify logging in this demo -->
    <dependency>
    	<groupId>org.slf4j</groupId>
    	<artifactId>slf4j-simple</artifactId>
    	<version>1.6.6</version>
    </dependency> 
    
  3. We want our application shell to be secured by the username and password, thus we will implement SSHD’s PasswordAuthenticator interface and accept in this demo all users, those password is equal to the username (surely in the real world application you have to use some user’s datastore and only accept users that are allowed to connect your application via ssh).

    public class InAppPasswordAuthenticator implements PasswordAuthenticator {
     @Override
     public boolean authenticate(String username, String password, ServerSession session) {
     return username != null && username.equals(password);
     }
    } 
    
  4. Next step it to implement the shell environment. To do this we have to create our own Factory implementation, what is able to handle user’s I/O.
    In this example I will use the JLine library for handling console input. It provides out of the box miscellaneous functionalities (“tab”-complition, command history, ..) and is very similar to BSD editline and GNU readline.
    Our demo shell implementation will support following features:
    • output welcome banner on ssh session startup
    • “TAB”-complition support for following commands: quit, exit, version, help
    • entering “quit” or “exit” command will close the running SSH session
    • entering “version” command will output some dummy version string
    • entering “help” command will output “Help is not implemented yet…” string
    • any other input will be echoed back to the user
    public class InAppShellFactory implements Factory {
     public Command create() {
     return new InAppShell();
     }
     private static class InAppShell implements Command, Runnable {
     private static final Logger log = LoggerFactory.getLogger(InAppShell.class);
     public static final boolean IS_MAC_OSX = System.getProperty("os.name").startsWith("Mac OS X");
     private static final String SHELL_THREAD_NAME = "InAppShell";
     private static final String SHELL_PROMPT = "app> ";
     private static final String SHELL_CMD_QUIT = "quit";
     private static final String SHELL_CMD_EXIT = "exit";
     private static final String SHELL_CMD_VERSION = "version";
     private static final String SHELL_CMD_HELP = "help";
     private InputStream in;
     private OutputStream out;
     private OutputStream err;
     private ExitCallback callback;
     private Environment environment;
     private Thread thread;
     private ConsoleReader reader;
     private PrintWriter writer;
     public InputStream getIn() {
     return in;
     }
     public OutputStream getOut() {
     return out;
     }
     public OutputStream getErr() {
     return err;
     }
     public Environment getEnvironment() {
     return environment;
     }
     public void setInputStream(InputStream in) {
     this.in = in;
     }
     public void setOutputStream(OutputStream out) {
     this.out = out;
     }
     public void setErrorStream(OutputStream err) {
     this.err = err;
     }
     public void setExitCallback(ExitCallback callback) {
     this.callback = callback;
     }
     public void start(Environment env) throws IOException {
     environment = env;
     thread = new Thread(this, SHELL_THREAD_NAME);
     thread.start();
     }
     public void destroy() {
     if (reader != null)
     reader.shutdown();
     thread.interrupt();
     }
     @Override
     public void run() {
     try {
     reader = new ConsoleReader(in, new FilterOutputStream(out) {
     @Override
     public void write(final int i) throws IOException {
     super.write(i);
     // workaround for MacOSX!! reset line after CR..
     if (IS_MAC_OSX && i == ConsoleReader.CR.toCharArray()[0]) {
     super.write(ConsoleReader.RESET_LINE);
     }
     }
     });
     reader.setPrompt(SHELL_PROMPT);
     reader.addCompleter(new StringsCompleter(SHELL_CMD_QUIT,
     SHELL_CMD_EXIT, SHELL_CMD_VERSION, SHELL_CMD_HELP));
     writer = new PrintWriter(reader.getOutput());
     // output welcome banner on ssh session startup
     writer.println("****************************************************");
     writer.println("* Welcome to Application Shell. *");
     writer.println("****************************************************");
     writer.flush();
     String line;
     while ((line = reader.readLine()) != null) {
     handleUserInput(line.trim());
     }
     } catch (InterruptedIOException e) {
     // Ignore
     } catch (Exception e) {
     log.error("Error executing InAppShell...", e);
     } finally {
     callback.onExit(0);
     }
     }
     private void handleUserInput(String line) throws InterruptedIOException {
     if (line.equalsIgnoreCase(SHELL_CMD_QUIT)
     || line.equalsIgnoreCase(SHELL_CMD_EXIT))
     throw new InterruptedIOException();
     String response;
     if (line.equalsIgnoreCase(SHELL_CMD_VERSION))
     response = "InApp version 1.0.0";
     else if (line.equalsIgnoreCase(SHELL_CMD_HELP))
     response = "Help is not implemented yet...";
     else
     response = "======> \"" + line + "\"";
     writer.println(response);
     writer.flush();
     }
     }
    }
    
  5. Last but not least is to combine all this things together and start the embedded SSH server daemon on desired port (5222 in our demo):
    public class App {
    	public static void main(String[] args) throws Throwable {
    		SshServer sshd = SshServer.setUpDefaultServer();
    		sshd.setPort(5222);
    		sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser"));
    		sshd.setPasswordAuthenticator(new InAppPasswordAuthenticator());
    		sshd.setShellFactory(new InAppShellFactory());
    		sshd.start();
    	}
    } 
    

    The KeyPairProvider above is used on the server side to provide the host key.

Now after starting our SSH daemon we can connect it using ordinary SSH client. Find below a screenshot of my session with the InAppShell:

[画像:image]

The project source code is hosted on GitHub under https://github.com/bigpuritz/javaforge-blog/tree/master/sshd-daemon-demo

PRINT THIS POST WORDS: View comments 11/29/13 — 10:46pm Short URL: https://tmblr.co/ZwTERu-oehaM Filed under: #ssh #apache mina #sshd #shell
 
View the discussion thread
Blog comments powered by Disqus

AltStyle によって変換されたページ (->オリジナル) /