SSH (a brief introduction)

Por Arrecio


Secure Shell o SSH es un protocolo de comunicación mediante canales encriptados que nació a mediado de los años 90. Si visitamos el enlace a la wikipedia observamos que la lista de enlaces a los RFCs resulta abrumadora lo que nos da a entender que en este humilde post apenas vamos a profundizar en esta tecnología. Por otro lado resulta necesario destacar algunas cosas como que SSH es un protocolo que funciona en la capa de aplicación del modelo OSI a diferencia de otros protocolos de comunicación cifrada como SSL/TLS que lo hacen en la capa de transporte.

Si eres un usuarios medio de Internet probablemente hayas hecho uso aluna vez de SSH ya que es la tecnología estándar para acceder a servidores *NIX remotos. Básicamente consiste en dos aplicaciones, una en el cliente y otra en el servidor, que se conectan entre sí mediante un protocolo de comunicación específico que garantiza una transferencia segura de datos entre las partes. Aunque algunos piensan que bajo SSH se oculta un protocolo SSL/TLS nada más lejos de la realidad, ya que funciona directamente encima de TCP. Esta confusión puede venir de que para su autenticación con claves asimétricas, OpenSSH utiliza claves generadas con OpenSSL.

Las similitudes las podemos observar comparando lo ya visto en Aspectos básicos de SSL/TLS con en la siguiente figura:

Pasos de conexión SSH
Procedimiento de conexión SSH

En la misma se resumen los pasos previos a obtener un shell de texto en un servidor remoto para un determinado usuario. Permite dos métodos de autenticación remota, inicialmente se intenta mediante un sistema de claves asimétricas y si esto fracasa se ofrece el prompt para introducir la contraseña del usuario en el sistema remoto. Una vez establecida la conexión SSH se comporta como una terminal telnet pero con tráfico encriptado.

Preparando nuestras claves

Partimos de un equipo con OpenSSH instalado ya que usaremos este cliente y no Putty, quien tiene su propio sistema generador claves, aquí un tutorial para Putty. Ya he dicho que las últimas versiones de Windows lo proveen de base, y en el caso de que no esté activado puede buscarse en la barra de búsqueda de Windows "Características opcionales" lo que te llevará a la correspondiente entrada en la configuración desde la que podrás activar el cliente. Si estás siguiendo esto desde Linux raro será que no lo tengas ya disponible desde la línea de comandos. El uso más simple para usar el comando ssh (aquí la página del manual) es el siguiente:

$ ssh usuario@servidor.com

Lo normal es que, si el servidor existe y tiene un servicio sshd sirviendo en el puerto 22, se te pregunte inmediatamente la contraseña de usuario dentro de ese equipo. En el caso de que queramos usar un puerto distinto tu opción es -p. Por ejemplo:

$ ssh usuario@servidor.com -p 222

Uso de alias para las conexiones

El fichero de configuración de OpenSSH, que se localiza por defecto en ~/.ssh/config, permite establecer alias para las conexiones. Por ejemplo si quisiéramos simplificar el último comando visto aquí deberíamos incluir en tal fichero (creándolo si no existe) lo siguiente:

Host servidor
    HostName servidor.com
    User usuario
    Port 222

Evidentemente la cadena que va detrás de host será la utilizada como alias para la conexión que podremos usar simplemente con:

$ ssh servidor

Para una documentación completa de las opciones de configuración consultar la página del manual de ssh_config.

Además podemos utilizar un fichero de configuración distinto al de por defecto mediante la opción -F del ejecutable del cliente.

Creación y utilización de las claves

Antes de nada nombrar que de la misma manera que el cliente tiene un fichero de configuración, el servidor tiene otro que en Linux se encuentra habitualmente en /etc/ssh/sshd_config existiendo igualmente un página del manual disponible. También de la misma manera el ejecutable del servidor, sshd, acepta una opción por línea de comandos para utilizar otro fichero, esta opción es la -f (en minúscula a diferencia de la opción a usar en el cliente).

Ya hemos dicho que SSH permite una manera de autentificarse sin necesidad de contraseña: el uso de claves SSL. Además podemos restringir la autenticación únicamente por esta vía, no ofreciendo la posibilidad del uso de contraseñas. Dentro de ese /etc/ssh/sshd_config tenemos dos opciones a tener en cuenta respecto a esto:

PasswordAuthentication no # [yes|no] para permitir o no la autenticación mediante contraseña
PubkeyAuthentication yes  # [yes|no] para permitir o no la autenticación mediante par de claves

En cuestión de login, y por seguridad, se recomienda no permitir el login del superusuario mediante la siguiente configuración PermitRootLogin no, además la directiva de configuración AllowUsers permite establecer una lista blanca de usuarios admitidos separada por espacios, existiendo la directiva homóloga para los grupos AllowGroups. Así mismo podemos limitar el número de intentos fallidos por usuario antes de desconectar al cliente con MaxAuthTries 3 (siendo 3 en este caso ese número).

Preparado el servicio para recibir intentos de autenticación por clave toda establecerlas para permitir este tipo de conexiones. El comando para generar las claves es el siguiente:

$ ssh-keygen

La página del manual explica las opciones aunque con esa simple ejecución ya podemos generar un par de claves. La opción -t se usa para establecer el tipo de llave, siendo por defecto de tipo ed25519 que utiliza un algoritmo de tipo EdDSA. Para los curiosos el 25519 viene de Curve25519 que a su vez está relacionado con ECDH. Otro comando que probablemente queramos conocer es el -b.

Lo primero que el comando pregunta es donde alojar el archivo con las claves, así como el nombre del fichero. Por defecto la clave privada se alojará en ~/.ssh/id_ed25519 y la pública como el mismo fichero pero con extensión .pub que contendrá una única línea de texto con la clave en formato ASCII. A continuación se preguntará por una passphrase o lo que viene a ser una contraseña. En la mayoría de los casos, si podemos asegurar la custodia de las claves, no utilizaremos contraseña alguna algo que podemos establecer simplemente presionando la tecla Intro. Hay que tener en cuenta que si olvidamos esta contraseña las claves quedarán inservibles.

Generado el par de claves tan sólo queda copiar la clave pública, es decir la que está contenida en el archivo .pub, al fichero ~/.ssh/authorized_keys del lado del servidor siendo ~ el directorio de usuario con el que estará permitido logar con la clave generada. Este archivo ~/.ssh/authorized_keys es un archivo de texto, como lo son los de nuestras claves, y puede contener tantas llaves como pares queramos autorizar para un mismo usuario, una por línea.

La operación de copia puede simplificarse si en el sistema está disponible el script ssh-copy-id utilizando simplemente este comando:

ssh-copy-id usuario@servidor

Lógicamente tienes que tener otra forma de autenticarte en ese mismo usuario.

Finalmente comentar la opción -i del cliente ssh que se acompañaría con el path a la clave privada a utilizar, que podríamos llevar por ejemplo en un llavero USB, recordando que en estos casos quizás lo mejor es tenerla protegida por contraseña.

Funciones de tunneling de SSH

Pero las funciones de SSH van más allá de un telnet seguro. Mediante este protocolo podemos transferir archivos mediante el protocolo SCP o su versión más completa el SFTP, así como crear túneles que es realmente lo que vamos a comentar en este artículo.

En este último enlace de wikipedia se incluye una imagen para representar un túnel de red precisamente usando como ejemplo el uso de tecnología SSH. Además incluye una sección dedica a este punto, por lo que casi podemos afirmar que es el estándar en la materia.

Para entender su utilidad lo mejor es comenzar resolviendo un problema sencillo. Imaginemos que dentro de red local queremos acceder a un equipo que sirve una página web pero el servidor que la sirve no tiene capacidades https y por tanto la conexión desde nuestro navegador es insegura al circular texto plano a través de la conexión. Por algún motivo no queremos que este texto pueda ser interceptado por más gente conectada a la misma red con sus equipos. El panorama es el siguiente:

Conexión claramente insegura

La creación de túneles es una tarea extremadamente sencilla, al menos túneles sobre TCP o Unix Sockets. Una de las principales cualidades de los túneles SSH es que a través de los mismos el tráfico es cifrado.

Estos túneles se construyen entre un cliente SSH que es el que ejecuta el comando de creación del mismo, y un servidor SSH, creando un canal seguro de comunicación por el que circulan paquetes en ambas direcciones:

Conceptualización de un túnel ssh

Un concepto que resulta muy importante entender es que aunque los túneles permiten el tráfico de manera bidireccional, las conexiones son únicamente direccionales.

Lo anterior quiere decir que únicamente uno de los puntos del túnel permite conexiones que recorrerían el túnel hacia el punto de salida que es donde debería estar el servicio que recibiría tales conexiones. El punto en el que se escuchan las direcciones se entiende como punto de entrada y el punto en el que se reciben las conexiones se denomina punto de salida. La dirección del túnel se entiende lógicamente entre el punto de entrada y el punto de salida.

En el último dibujo hemos representado un túnel pero no hemos identificado cuales son los puntos de entrada y de salida, que no tienen porqué coincidir con los lugares donde se sitúan respectivamente el cliente SSH y el servidor SSH, algo que veremos pronto.

Así, mediante el uso de túneles SSH podremos encriptar toda la comunicación entre un navegador web y un servidor http del servidor mediante una conexión punto a punto con la que en lugar de conectar el navegador directamente al servidor http utilizando el canal inseguro de nuestra LAN, lo que haremos será conectar ese navegador al punto de entrada de un túnel dentro del mismo equipo y desde ahí enviar todo el tráfico de manera cifrada hacia el equipo servidor, y en concreto al servicio que sirve la web.

Partiendo de la imagen de nuestro problema inicial el túnel podemos construir un túnel de tal manera que el resultado sería el siguiente:

Conexión segura mediante túnel ssh local

Las flechas blancas indican las direcciones de las conexiones, es decir nos indican que el cliente web (por ejemplo firefox) está en el portátil y sel servidor web (por ejemplo apache) está en el servidor.

Para obtener este resultado OpenSSH nos permite utilizar dos tipos de túneles, los locales y los remotos o también llamados reversos.

La diferencia entre ambos radica en el lugar en el que se sitúa el punto de entrada del túnel. En los túneles locales el punto de entrada se sitúa en el equipo que construye el túnel, es decir el que hace las funciones de cliente ssh. Por el contrario, en los túneles remotos o reversos el punto de entrada se sitúa en el equipo que aloja el servidor ssh.

Con OpenSSH un túnel se describe de la siguiente manera:

[bind_address:]bind_port:host_address:host_port

Donde [bind_address:]bind_port representa el punto de entrada (el que recibirá las conexiones) y host_address:host_port representa el punto de salida. Por analogía podemos llamar al punto de entrada bind_point y al punto de salida host_point.

Ahora mismo vamos a ignorar el parámetro opcional bind_address y no lo vamos a escribir una vez que hablaremos de él más adelante.

Solución mediante túnel local

Para crear un túnel local tan sólo necesitas ejecutar ssh utilizando la opción -L. En concreto hablando de túnel TCP podemos nombrar sus parámetros tal que sigue:

$ ssh -L [bind_address:]bind_port:host_address:host_port usuario@servidor

Donde ya hemos dicho que "[bind_address:]bind_port" es el punto de entrada y "host_address:host_port" el de salida. En cuanto a usuario@servidor este debe corresponder al usuario que será dueño del túnel en la dirección del servidor que corresponda.

La opción -L está indicando que el bind_point se establecerá en el equipo local, el que ejecuta el comando.

Tal y como se observa en la imagen última imagen que vimos, la dirección para acceder a la página web ha cambiado. Ya no accedemos a través de la dirección "remota" sino a través de una dirección loopback y un puerto que en este caso se ha decidido que sea el 8080 (es decir a través http:\\127.0.0.1:8080). Observamos además que el protocolo sigue siendo http y no https ya que el servidor no acepta ese tipo de conexiones. El cifrado está en el túnel y no en el servicio web que sigue siendo el mismo.

En nuestro ejemplo necesitaríamos ejecutar siguiente comando en el portátil:

$ ssh -L 8080:192.168.1.200:80 usuario@192.168.1.200 -N

Como se observa se ha omitido la dirección del punto de entrada y únicamente se indica el puerto de entrada. Luego hablaremos de ello pero en este momento tan sólo debemos conocer que en este caso nos estamos refiriendo a la dirección loopback que sería la que se tomaría por defecto.

La traducción en lenguaje natural del anterior comando sería: "Construye un túnel hacia el equipo 192.168.1.200 con las credenciales de usuario. El túnel escuchará localmente en el puerto 8080 y enviará las conexiones que se produzcan hacia el equipo remoto con IP 92.168.1.200, en el puerto 80 del mismo."

La opción -N se indica para no obtener el shell de destino obteniendo así un túnel puro.

Hay algo que es necesario comentar, especialmente de cara al futuro, y es que la dirección host_address se refiere relativa al equipo que hace de punto de salida. Es decir que podríamos cambiar el comando a la forma siguiente:

$ ssh -L 8080:localhost:80 usuario@192.168.1.200 -N

Insisto. La dirección localhost se refiere a una dirección que será resuelta en el equipo donde se encuentra el punto de salida del túnel, que en este caso es el equipo que aloja el servidor ssh.

Veamos ahora como podríamos configurar esto en el ~/.ssh/config, lo cual se hace mediante la directiva LocalForward:

Host tunel_local
    HostName 192.168.1.200
    User usuario
    Port 22
    LocalForward 8080 localhost:80

Solución mediante túnel remoto

Como este artículo tiene ánimo ilustrativo vamos a explicar como se haría la conexión mediante un túnel remoto aunque claramente no es la solución adecuada, siendo la adecuada precisamente la que acabamos de ver mediante el túnel local.

En cuanto al túnel remoto este se sigue entendiendo como construido desde el equipo que ejecuta la orden de cliente pero en este caso se invierten los puntos de entrada y de salida. Este tipo de túneles se llaman también reverso y se construyen con la opción -R del comando ssh por lo que ambas palabras le vienen bien. Su forma general para túneles bajo TCP es:

$ ssh -R [bind_address:]bind_port:host_address:host_port usuario@servidor

De nuevo volvemos a ignorar el parámetro opcional bind_address una vez que hablaremos de él más adelante.

Para solucionar nuestro problema mediante un túnel remoto o reverso debemos cambiar los papeles. Quien antes creaba el túnel ahora lo recibe, y viceversa, lo que significa que quien debe correr el servicio OpenSSH server es el portátil lo cual requiere actuar un poco contra la lógica.

En cuanto a la línea de comandos a ejecutar en el servidor pero mediante el cliente ssh sería la siguiente:

$ ssh -R 8080:localhost:80 usuario@192.168.1.18

La cual se lee en lenguaje natural como sigue: "Construye un túnel remoto hacia el equipo 192.168.1.18. Haz que en el puerto 8080 de tal equipo se escuchen conexiones que serán dirigidas al puerto 80 del equipo local."

Insistir en que el túnel creado es exactamente el mismo, tal y como se observa en la cadena que lo describe: 8080:localhost:80. Tan sólo cambia quien hace el papel de cliente ssh y de servidor ssh. Y aunque en este momento parezca que el túnel remoto tiene poco sentido sí que lo tiene, especialmente en los casos en los que queremos crear conexiones a través de firewalls.

En cuanto a la entrada en el fichero de configuración ~/.ssh/config la directiva a utilizar es RemoteForward.

Host tunel_remoto
    HostName 192.168.1.18
    User usuario
    Port 22
    RemoteForward 8080 localhost:80

Dando sentido a [bind_address:]

En algunas anotaciones hemos dicho que ignoráramos aquella parte en la que aparecía bind_address: en la línea de comandos general porque hablaríamos de ello más adelante. Si tenemos en cuenta que en un mismo equipo pueden distinguirse tantas direcciones IP locales como dispositivos de red haya configurados (físicos o virtuales), la dirección bind_address se refiere precisamente a la dirección a la que se vinculará el bind_port y que necesariamente será la que tengamos que usar para conectar mediante el túnel.

Por defecto este valor será 127.0.0.1 que es la dirección del dispositivo loopback y de hecho este valor será el único admitido a no ser que cierta directiva del archivo de configuración sshd_config se configure tal que sigue:

GatewayPorts clientspecified

Establecida dicha directiva, y volviendo a nuestro túnel local, si establecemos el siguiente comando para crear el túnel:

$ ssh -L 0.0.0.0:8080:localhost:80 usuario@192.168.1.200 -N

Entonces la dirección para acceder al túnel no sería la loopback sino que sería la llamada wildcard o * lo que significa que se puede acceder utilizando cualquiera de las direcciones disponibles en el portátil. Esto implica que cualquier equipo con capacidad de conectar al puerto 8080 del portátil podría acceder a través de él al servicio httpd del servidor, usando el mismo túnel seguro.

Existe otra configuración para tal directiva que sería GatewayPorts yes por la cual la dirección por defecto de bind_address sería precisamente 0.0.0.0. Establecida esta configuración entonces nuestro comando inicial se tendría como el realizado en esta última ocasión.

Hablando de los túneles remotos la función es la misma aunque tiene efectos en el equipo remoto en lugar de en el local.

Restringir los túneles vía sshd_config

En una configuración en la que nosotros seamos los únicos usuarios del sistema probablemente queramos total libertad en las creaciones de los túneles. En caso de que permitamos más usuarios y no queramos que estos husmeen donde no deben tenemos que proteger las conexiones no deseadas. Una manera de hacerlo es establecer una white list en el archivo de configuración del servidor ssh con entradas como la siguiente:

PermitOpen host_address:host_port

Haciendo esto, si el punto de salida del túnel no viene incluido en esa whitelist la creación del túnel será rechazada.

Referencias: SSH Essentials: Working with SSH Servers, Clients, and Keys