Cómo SSH en un servidor usando Node.js

Cómo configurar un servidor en Digital Ocean, crear una clave SSH y usar el node-ssh paquete a SSH en ese servidor usando su clave SSH.

Primeros pasos

Para este tutorial, vamos a utilizar el marco JavaScript de pila completa de CheatCode, Joystick. Joystick reúne un marco de interfaz de usuario de front-end con un back-end de Node.js para crear aplicaciones.

Para comenzar, querremos instalar Joystick a través de NPM. Asegúrese de estar usando Node.js 16+ antes de instalar para garantizar la compatibilidad (lea este tutorial primero si necesita aprender a instalar Node.js o ejecutar varias versiones en su computadora):

Terminal

npm i -g @joystick.js/cli

Esto instalará Joystick globalmente en su computadora. Una vez instalado, vamos a crear un nuevo proyecto:

Terminal

joystick create app

Después de unos segundos, verá un mensaje desconectado de cd en su nuevo proyecto y ejecute joystick start . Antes de ejecutar eso, necesitamos instalar una dependencia, node-ssh :

Terminal

cd app && npm i node-ssh

Una vez que haya instalado esto, estará listo para iniciar su aplicación:

Terminal

joystick start

Después de esto, su aplicación debería estar ejecutándose y estamos listos para comenzar.

Generación de una clave SSH

Para demostrar el uso de SSH para comunicarse con un servidor, es mejor comenzar asegurándose de tener una clave SSH a mano. Mientras puedas SSH en un servidor usando un nombre de usuario y contraseña, esto debe evitarse ya que las contraseñas por sí solas son más vulnerables a los ataques que un archivo SSH.

Para comenzar, vamos a generar una clave SSH utilizando el estándar ED25519, que es un estándar criptográfico más nuevo, más rápido y más seguro. Primero, queremos crear una nueva carpeta en la raíz de la aplicación generada para nosotros por joystick start llamado private y luego dentro de esa carpeta, crearemos otra llamada ssh :

Terminal

mkdir private
cd private
mkdir ssh
cd ssh

Una vez que hayas cd 'd en /private/ssh desde la raíz de su aplicación, a continuación, queremos generar una clave SSH:

Terminal

ssh-keygen -t ed25519 -C "[email protected]"

En su computadora, debe tener una herramienta integrada llamada ssh-keygen . Como sugiere su nombre, se utiliza para generar claves SSH . Aquí, estamos llamando ssh-keygen pasando dos banderas:-t que representa el "tipo" de clave a generar (aquí, un ed25519 clave) y luego -C que significa "comentario" (aquí, usamos esto para ingresar nuestra dirección de correo electrónico ya que el comentario se agrega al final de nuestra clave pública y sugiere su intención original).

Esto le hará algunas preguntas (pulse Intro/Retorno después de escribir su respuesta para cada una)...

  1. Para el mensaje "Ingrese el archivo en el que guardar la clave", debe ingresar ./<your-email-address> donde <your-email-address> debe reemplazarse con la dirección de correo electrónico que desea usar para esta clave (por ejemplo, ./[email protected] ). Nota :el ./ al principio es importante ya que asegura que el archivo se almacena en el private/ssh carpeta que acabamos de crear.
  2. A continuación, se le pedirá que ingrese una frase de contraseña. Esto es muy recomendable . Agregar una frase de contraseña a su clave SSH agrega otra capa de seguridad para que, en caso de que su clave SSH se filtre o quede expuesta, el atacante también necesita la contraseña de la clave para usarla. Tome nota de la contraseña que ingrese, ya que la usaremos más tarde .
  3. A continuación, se le pedirá que confirme la contraseña que ingresó en el paso 2.

Después de completar esto, debería ver algo como esto impreso en la terminal:

Terminal

Your identification has been saved in ./[email protected]
Your public key has been saved in ./[email protected]
The key fingerprint is:
SHA256:VUwq60W7bY4hWW/rmr4LdvggZ5Vg+JNwGo9nONfe5hs [email protected]
The key's randomart image is:
+--[ED25519 256]--+
|           oo    |
|       .   o.    |
|      + = +      |
|       @ O o     |
|      = S B      |
|       * O =     |
|      . @ = E    |
|       = * X o   |
|         .O=*.   |
+----[SHA256]-----+

Más importante aún, también debería ver dos archivos en private/ssh :private/ssh/<your-email-address> y private/ssh/<your-email-address>.pub . El primero es su clave privada y el último es su clave pública .

La distinción aquí es importante. Como veremos en un momento, le daremos a nuestro .pub o "clave pública" para el host donde vive nuestro servidor. Más tarde, cuando hagamos "SSH en" nuestro servidor, pasaremos nuestra clave privada junto con la solicitud. Detrás de escena, nuestro host verificará si tiene una clave pública correspondiente a esa clave privada. Si lo hace y las firmas coinciden entre sí (y la contraseña es correcta), nuestra solicitud podrá pasar.

Creación de una gota digital del océano

Para demostrar el uso de SSH, necesitamos un servidor remoto con el que podamos comunicarnos. Para nuestro ejemplo, vamos a configurar un droplet en Digital Ocean (Droplet es el nombre comercial de Digital Ocean para una instancia de servidor). Nuestro objetivo será obtener acceso a un servidor, más específicamente, su dirección IP, y usarla en nuestras solicitudes SSH.

Primero, si aún no tiene una cuenta de Digital Ocean, diríjase a la página de registro y cree una cuenta.

Una vez que haya configurado y verificado su cuenta, nos dirigiremos al panel de proyectos y en la esquina superior derecha, haga clic en el botón "Crear" y en el menú desplegable, "Gotas".

En la siguiente pantalla, debemos seleccionar las siguientes opciones:

  1. En "Elegir una imagen", queremos seleccionar el primer cuadro "Ubuntu" y asegurarnos de que la opción "20.04 (LTS) x64" esté seleccionada en el menú desplegable en la parte inferior de ese cuadro.
  2. En "Elegir un plan", queremos seleccionar "Básico" y luego, en "Opciones de CPU", seleccione "Regular con SSD" y la primera opción "$5/mes" con 1GB/1CPU.
  3. En "Elegir una región del centro de datos", seleccione la región más cercana a usted (estoy seleccionando "Nueva York 1" para este tutorial).
  4. En "Autenticación", asegúrese de seleccionar "Claves SSH" y luego, en el cuadro debajo de esto, haga clic en el botón "Nueva clave SSH". Esto revelará una nueva ventana que le solicitará el "contenido de la clave SSH" y un "Nombre". Para el "contenido de la clave SSH", desea pegar el contenido del <your-email-address>.pub archivo de su private/ssh carpeta y para "Nombre", desea ingresar su dirección de correo electrónico.
  1. Opcionalmente, en la parte inferior, debajo de "Elegir un nombre de host", ingresa un nombre más amigable que el generado automáticamente (por ejemplo, "ssh-tutorial" o "cheatcode-tutorial") para que recuerdes para qué sirve.
  2. Haga clic en el botón verde "Crear gota".

Después de esto, será redirigido de nuevo al panel de control de sus proyectos. debería verá una barra de carga para el Droplet que acaba de crear, pero si no lo hace, presione Actualizar y debería aparecer. Una vez que lo haga, haga clic en su nombre para revelar su tablero:

Una vez que veas esto, ¡ya estarás listo! Ahora que tenemos un servidor en el que podemos usar SSH, a continuación, queremos saltar al código de nuestra aplicación y aprender a usar SSH a través de Node.js.

Conectando un getter a SSH en nuestro servidor

Ahora viene la parte divertida. Para demostrar el proceso de usar SSH para conectarse a nuestro servidor, conectaremos un getter en nuestra aplicación Joystick. En Joystick, los captadores son una forma de definir rápidamente las rutas de la API REST que responden a las solicitudes HTTP GET. Los captadores son flexibles porque se pueden llamar directamente como puntos finales HTTP simples o mediante el get() función integrada en el @joystick.js/ui y @joystick.js/node paquetes.

Desde la raíz de la aplicación, queremos abrir el /api/index.js archivo que se generó para nosotros cuando ejecutamos joystick create app más temprano. Este archivo se conoce como el "esquema" de nuestra API en Joystick. En el interior, verá que se exporta un objeto JavaScript sin formato con dos propiedades predefinidas:getters y setters .

En una aplicación Joystick, getters contiene las definiciones para el captador puntos finales que desea definir en su aplicación (nuevamente, estos son puntos finales HTTP GET) y setters contiene las definiciones para el setter puntos finales que desea definir en su aplicación (estos son puntos finales HTTP POST). El primero está destinado a "obtener" o leer datos en su aplicación, mientras que el último está diseñado para crear, actualizar y eliminar datos en su aplicación.

En este archivo, vamos a definir un getter llamado serverFileTree . El objetivo de este captador será SSH en nuestro servidor y ejecutar Linux ls -al comando que enumera todos los archivos en el directorio raíz (más sobre esto en un momento) de la máquina en la que estamos SSH. Si recuperamos una lista, podemos confirmar que hemos establecido una conexión con éxito.

/api/index.js

import joystick from '@joystick.js/node';
import { NodeSSH } from 'node-ssh';

export default {
  getters: {
    serverFileTree: {
      get: async () => {
        const ssh = new NodeSSH();

        await ssh.connect({
          host: joystick?.settings?.private?.ssh?.ipAddress,
          username: 'root',
          privateKey: `${process.cwd()}/private/ssh/[email protected]`,
          passphrase: joystick?.settings?.private?.ssh?.passphrase,
        });

        const result = await ssh.execCommand(`ls -al`, { cwd: '/', options: { pty: true } });

        return result?.stdout;
      },
    },
  },
  setters: {},
};

Debido a que no necesitamos mucho código, aquí mostramos la implementación completa. Comenzando desde arriba, queremos importar dos cosas:

  1. joystick del @joystick.js/node paquete que usaremos para acceder a la configuración de nuestra aplicación.
  2. { NodeSSH } de node-ssh lo que nos ayudará a establecer una conexión SSH autenticada a nuestro servidor y ejecutar comandos en él.

Abajo en nuestro getters existente objeto, hemos agregado una propiedad serverFileTree que es el nombre de nuestro captador y le hemos asignado un objeto que definirá ese captador. En ese objeto, hemos agregado una sola propiedad get que se asigna a una función.

Esa función get() es lo que Joystick llama automáticamente cada vez que se realiza una solicitud al serverFileTree adquiridor. Como explicamos anteriormente, esto se puede hacer a través del get() función en @joystick.js/ui y @joystick.js/node como get('serverFileTree') , o directamente a través de una solicitud HTTP como http://localhost:2600/api/_getters/serverFileTree (el /api/_getters/<getter-name> Joystick genera automáticamente una parte de esa URL).

Dentro de esa función, nuestro objetivo es "obtener" algunos datos y devolverlos. Esos datos pueden provenir de cualquier parte . En este caso, queremos usar SSH en el servidor que configuramos anteriormente, ejecutar un comando en él y luego devolver el resultado de ejecutar ese comando desde nuestro getter.

Para hacer eso, primero, necesitamos crear una instancia de NodeSSH con new NodeSSH() . Esto nos brinda un nuevo "espacio de trabajo" (por así decirlo) para conectarnos a nuestro servidor y ejecutar nuestros comandos en él. Aquí, tomamos esa instancia y la almacenamos en una variable ssh .

Luego, frente a la función pasada a nuestro get propiedad, hemos agregado la palabra clave async para permitirnos usar la abreviatura await sintaxis cuando se trabaja con JavaScript Promises. Estamos haciendo esto aquí porque esperamos los métodos del node-ssh paquete para devolver JavaScript Promises.

Nuestro primer paso, y el más importante, es establecer una conexión con nuestro servidor. Para hacerlo llamamos al await ssh.connect() pasando un objeto de opciones con:

  • host que es la dirección IP del servidor al que queremos conectarnos.
  • username que es el nombre de usuario en el servidor al que nos estamos conectando que queremos usar (en este caso, estamos usando el root usuario proporcionado por Ubuntu, el sistema operativo que le dijimos a Digital Ocean que instalara en nuestro servidor).
  • privateKey que es la ruta al archivo de clave privada que generamos anteriormente (recuerde, le dimos la parte de clave pública de esto a Digital Ocean anteriormente). Aquí, el process.cwd() está recuperando la ruta del "directorio de trabajo actual" de Node.js, que esperamos que sea la ruta completa al app carpeta que creamos con joystick create app . Concatenamos esto junto con /private/ssh/<your-email-address> para apuntar a nuestra clave privada SSH.
  • passphrase la contraseña que ingresó al generar su clave SSH.

Llamando al elefante en la habitación, tenemos dos líneas aquí que probablemente no tengan sentido:joystick?.settings?.private?.ssh?.ipAddress y joystick?.settings?.private?.ssh?.passphrase . Aquí, estamos extrayendo valores de nuestro archivo de configuración que aún no hemos discutido.

/configuraciones.desarrollo.json

{
  "config": {
    "databases": [
      {
        "provider": "mongodb",
        "users": true,
        "options": {}
      }
    ],
    "i18n": {
      "defaultLanguage": "en-US"
    },
    "middleware": {},
    "email": {
      "from": "",
      "smtp": {
        "host": "",
        "port": 587,
        "username": "",
        "password": ""
      }
    }
  },
  "global": {},
  "public": {},
  "private": {
    "ssh": {
      "ipAddress": "<ip address goes here>",
      "passphrase": "<ssh key password goes here>"
    }
  }
}

Si abrimos ese archivo, en la parte inferior debajo del private objeto, queremos agregar otro objeto ssh y en ese objeto, defina dos propiedades establecidas en cadenas:ipAddress y passphrase . Como se indica aquí, los completaremos con la dirección IP (indicada en el panel de Digital Ocean como ipv4: 167.99.145.55 cerca de la parte superior de la página de resumen de su Droplet) de nuestro servidor y la contraseña que ingresó al generar su clave SSH.

/api/index.js

import joystick from '@joystick.js/node';
import { NodeSSH } from 'node-ssh';

export default {
  getters: {
    serverFileTree: {
      get: async () => {
        const ssh = new NodeSSH();

        await ssh.connect({
          host: joystick?.settings?.private?.ssh?.ipAddress,
          username: 'root',
          privateKey: `${process.cwd()}/private/ssh/[email protected]`,
          passphrase: joystick?.settings?.private?.ssh?.passphrase,
        });

        const result = await ssh.execCommand(`ls -al`, { cwd: '/', options: { pty: true } });

        return result?.stdout;
      },
    },
  },
  setters: {},
};

Una vez que su configuración se actualice y guarde, finalmente, estaremos listos para ejecutar comandos en nuestro servidor. Para hacerlo, solo necesitamos llamar al await ssh.execCommand() . A esa función, como cadena para el primer argumento, le pasamos el comando que queremos ejecutar y luego como segundo argumento, un objeto de opciones para la solicitud. Aquí, estamos configurando dos:cwd a / (que dice "cuando ejecute este comando, ejecútelo desde la raíz absoluta del servidor") y pty: true que le dice a node-ssh para permitir la entrada/salida de texto y se requiere para que ciertos comandos funcionen usando este proceso.

Con eso, almacenamos nuestra llamada en una variable const result que esperamos que contenga un objeto con un stdout (salida estándar) y stderr (error estándar), las cuales son cadenas de salida al ejecutar el comando en el servidor.

Finalmente, debido a que podemos confiar en que el comando que estamos ejecutando debería funcionar sin errores, desde nuestro getter devolvemos result?.stdout . Con esto, deberíamos tener una conexión SSH funcional a nuestro servidor. Si abrimos un navegador web y visitamos http://localhost:2600/api/_getters/serverFileTree después de un breve retraso, deberíamos ver la salida del comando devuelta al navegador.

Terminando

En este tutorial, aprendimos cómo crear un par de claves SSH, configurar un servidor en Digital Ocean y conectarnos a ese servidor mediante SSH. Aprendimos cómo crear un punto de conexión getter en una aplicación Joystick y cómo usar el node-ssh paquete de ese captador para ejecutar comandos en el servidor remoto y devolver su salida como la respuesta del punto final.