27 febrero 2013

Estableciendo conexiones SSH con Java (y Android)

Mientras programaba DRGPIO pensé que una de las funciones que deseaba incluir era un instalador automático de WebIOPi vía SSH.

¿Por qué? Pues porque si bien yo me considero un usuario de Linux un poco hardcore, de esos que prefieren tener que compilar mil paquetes a mano antes de ejecutar ese diabólico comando "apt-get", estoy muy consciente que la mayoría de usuarios de Android y la Raspberry Pi, posiblemente quieran una solución que simplemente puedan descargar y usar.

En esta pequeñísima entrada les intentaré explicar qué es y como funciona la biblioteca que utilizo para iniciar conexiones SSH en Android del instalador automatizado incluido dentro de DRGPIO.

Nota: Esta entrada asume que usted posee conocimientos básicos sobre E/S en Java.

Presentando JSch


Una regla básica en la programación es no intentar re-inventar la rueda, en estos dorados tiempos esto se hace muy fácil con la ayuda de Google.

Luego de realizar una muy corta investigación y de revisar diferentes opciones, me tope con JSch, esta es una pequeña biblioteca para Java que provee facilidades para conexiones por medio de canales seguros SSH.

JSch utiliza los conceptos de "sesión" y "canal", la sesión podemos decir que es nuestro medio seguro de comunicación con el servidor remoto. El "canal" en cambio es una vía de comunicación que nos permite acceder a la funcionalidad en el host que deseamos acceder.

JSch incluye por defecto varios canales para realizar por ejemplo ejecución de comandos remotos, consola remota, FTP seguro, conexiones TCP-IP, entre otras funciones.

Utilizando JSch


Primero necesitan descargar JSch y agregarlo al CLASSPATH de Java. Recuerden que los jar necesitan ser agregados con todo y el nombre de archivo al classpath y no solo el nombre del directorio.

Este es un ejemplo de adición de JSch al CLASSPATH considerando que se encuentra en el mismo directorio que estamos ejecutando los comandos.
$ export CLASSPATH=$CLASSPATH:$PWD/jsch-0.1.49.jar

Si están utilizando un IDE busquen en la ayuda como pueden importar bibliotecas externas al mismo.

Una vez lo tengan instalado lo primero es crear un objeto del tipo JSch. Este objeto nos servirá para crear sesiones y manejar todas las credenciales de autenticación. (Añadiré una clase que nos servirá de esqueleto para nuestra aplicación).

ConexionSSH.java:
import com.jcraft.jsch.*;

public class ConexionSSH {

  private JSch jsch;

  public ConexionSSH() {
    jsch = new JSch();
  }

  public static void main(String args[]) {
    new ConexionSSH();
  }
}

Antes de conectar la sesión, debemos especificar las "credenciales" de autenticación. Recuenden que SSH puede identificarse por medio de combinación de usuario/password o por medio de pares de llaves publica/privada, JSch soporta ambos métodos. En este ejemplo nos conectaremos usando un usuario y password para no complicar la codificación y mantener este ejemplo sencillo.

Para realizar lo anterior utilizaremos el objeto JSch y crearemos una nueva sesión especificando el nombre de usuario y el host al que deseamos conectarnos. Una vez hecho esto especificamos la clave a usar. 

Para este ejemplo, antes de iniciar la sesión estableceramos que no se realice una revisión estricta de las llaves por medio del método estático "JSch.setConfig". Si no deshabilitamos esta opción entonces deberemos especificarle a JSch la ubicación del archivo "know_hosts" que guarda la lista de servidores SSH de confianza. 

Para aplicaciones de "producción" les recomiendo no des habilitar esta opción y especificar el archivo de "hosts" conocidos.

Nota: Es necesario capturar la excepción del tipo JSchException al utilizar esta biblioteca.
import com.jcraft.jsch.*;

public class ConexionSSH {

  private JSch jsch;

  public ConexionSSH() {
    jsch = new JSch();

    // Es necesario capturar JSchException
    try {

      // NO realizar revision estricta de llaves
      JSch.setConfig("StrictHostKeyChecking", "no");

      // Creamos la nueva sesion SSH
      Session sesion = jsch.getSession("usuario","host");

      // Establecemos la clave
      sesion.setPassword("su_clave");

    } catch(JSchException e) {
      System.out.println("Error de JSCH. Mensaje: "+e.getMessage());
    }
  }

  public static void main(String args[]) {
    new ConexionSSH();
  }
}

Si recuerdan al principio les mencionaba que la "sesión" es simplemente una forma de identificar al medio seguro por el cual nos comunicamos. Para realmente hacer algo "útil" se hace necesario abrir uno o varios canales. 

Vamos a conectar nuestra sesión y vamos a abrir un ChannelShell que nos ayudará a enviar los comandos de consola. Esto lo realizaremos con la ayuda del método "openChannel" de la sesión.
import com.jcraft.jsch.*;

public class ConexionSSH {

  private JSch jsch;

  public ConexionSSH() {
    jsch = new JSch();

    // Es necesario capturar JSchException
    try {

      // NO realizar revision estricta de llaves
      JSch.setConfig("StrictHostKeyChecking", "no");

      // Creamos la nueva sesion SSH
      Session sesion = jsch.getSession("usuario","host");

      // Establecemos la clave
      sesion.setPassword("su_clave");

      // Conectamos la sesion
      sesion.connect();

      // Obtenemos un nuevo canal para enviar/recibir comandos
      // de consola
      ChannelShell consola = sesion.openChannel("shell");
    } catch(JSchException e) {
      System.out.println("Error de JSCH. Mensaje: "+e.getMessage());
    }
  }

  public static void main(String args[]) {
    new ConexionSSH();
  }
}

Hasta este punto estamos listos para enviar y recibir comandos. Con el canal de "consola" solo necesitamos especificar un "InputStream" que será el encargado de recibir los comandos que serán enviados al servidor remoto. Y un "OutputStream" que será encargado de desplegar las respuestas enviadas desde el servidor. Por ahora ya que este es una entrada introductoria, simplemente reutilizaremos las entradas y salidas del sistema para enviar y recibir comandos a nuestro programa:
import com.jcraft.jsch.*;

public class ConexionSSH {

  private JSch jsch;

  public ConexionSSH() {
    jsch = new JSch();

    // Es necesario capturar JSchException
    try {

      // NO realizar revision estricta de llaves
      JSch.setConfig("StrictHostKeyChecking", "no");

      // Creamos la nueva sesion SSH
      Session sesion = jsch.getSession("usuario","host");

      // Establecemos la clave
      sesion.setPassword("su_clave");

      // Conectamos la sesion
      sesion.connect();

      // Obtenemos un nuevo canal para enviar/recibir comandos
      // de consola
      ChannelShell consola = sesion.openChannel("shell");

      // Utilizamos la entrada y salida estándar del sistema
      // para recibir comandos y desplegar el resultado
      consola.setInputStream(System.in);
      consola.setOutputStream(System.out);

    } catch(JSchException e) {
      System.out.println("Error de JSCH. Mensaje: "+e.getMessage());
    }
  }

  public static void main(String args[]) {
    new ConexionSSH();
  }
}

Por último lo único que tenemos que hacer es "conectar" el canal:
import com.jcraft.jsch.*;

public class ConexionSSH {

  private JSch jsch;

  public ConexionSSH() {
    jsch = new JSch();

    // Es necesario capturar JSchException
    try {

      // NO realizar revision estricta de llaves
      JSch.setConfig("StrictHostKeyChecking", "no");

      // Creamos la nueva sesion SSH
      Session sesion = jsch.getSession("usuario","host");

      // Establecemos la clave
      sesion.setPassword("su_clave");

      // Conectamos la sesion
      sesion.connect();

      // Obtenemos un nuevo canal para enviar/recibir comandos
      // de consola
      ChannelShell consola = sesion.openChannel("shell");

      // Utilizamos la entrada y salida estándar del sistema
      // para recibir comandos y desplegar el resultado
      consola.setInputStream(System.in);
      consola.setOutputStream(System.out); 

      // Conectamos nuestro canal
      consola.connect();
    } catch(JSchException e) {  
      System.out.println("Error de JSCH. Mensaje: "+e.getMessage());
    }
  }

  public static void main(String args[]) {
    new ConexionSSH();
  }
}

Y... ¡Listo!

Si ahora ejecutamos java ConexionSSH debería de conectarse al servidor que le especifiquemos y todo lo que escribamos a la consola debería ser enviado al servidor y las respuestas impresas en pantalla:

¡Y también funciona en Android!


Así como lo leen, esta biblioteca funciona sin mayor problema en Android. Simplemente importen el .jar de JSch al directorio "libs" y podrán usarlo de manera similar. Obviamente en Android deberán de escribir al InputStream del canal utilizando alguna estrategia diferente a utilizar System.in como de igual manera deben utilizar otra estrategia para leer lo que JSch escriba al OutputStream. Pero de eso hablaremos en otra entrada.



No habiendo nada más que decir... ¡Hasta la Próxima!

Referencias:


1 comentario:

davidbarsanz barsanz dijo...

Hola Mario, antes de nada agradecer tu trabajo a través de estos tutoriales...

Quisiera saber si has implementado para android las conexioes SSH que has dejado en este post.