Lanzar tareas masivas

a servidores Linux
Posted by Miguel Mora on December 15, 2021

ÍNDICE

1. Introducción
2. Prerrequisitos
     2.1. Cómo instalar SSHPass?
3. ¿Qué vamos a hacer?
4. Pasos
     4.1. Crear ficheros
     4.2. Variables
     4.3. Funciones
          Función "pingable"
          Función "action_remote"
     4.4. Código Principal
          Recorrer servidores
          Comprobar si es alcanzable
          Conectar al servidor
          Ejecutar tareas
          ¿Y si ejecuto tareas con pemisos (sudo)?
5. Extras
     5.1. Permisos sólo lectura
     5.2. Encriptar contraseña
6. Quiero el código

 

1. Introducción


¿No nos ha pasado alguna vez que necesitamos reiniciar una flota entera de servidores después de actualizar el sistema operativo? ¿o quizás necesitamos sacar la información de todos ellos para hacer inventario? Estas son tareas comunes en toda empresa, y más de una vez nos ha tocado lidiar con ellas, con el consumo de tiempo que ello nos lleva.

Realizar múltiples operaciones puede convertirse en una tarea tediosa, sobre todo si necesitamos hacerlo para varios servidores.

En esta web nos gusta optimizar las tareas “time consuming”, por lo que vamos a mostrarte cómo realizar todo tipo de tareas en cientos de servidores Linux. ¡Vamos a ello!

 

2. Prerrequisitos


Para este lab necesitaremos:

  • Un equipo con Linux (puede ser una máquina virtual, o incluso el subsistema de Windows para Linux (WSL).
  • Tener instalado la aplicación “sshpass”
  • Un poco de conocimiento sobre Bash scripting

 

2.1. ¿Cómo instalar SSHPass?

Para ello utilizaremos el “package manager” por defecto de cada distribución. Algunos ejemplos serían:

Centos: yum install sshpass

Ubuntu: sudo apt-get install sshpass

Fedora: dnf install sshpass

NOTA: Para el caso de Centos, será necesario instalar previamente el repositorio “epel-release”. Podrán instalarlo a través del comando “yum install epel-release”.

 

3. ¿Qué vamos a hacer?


En resumen, estas son las tareas a realizar por cada servidor:

  1. Comprobar que el servidor es “alcanzable” (en términos técnicos, si llega a “ping”)
  2. Nos conectamos al servidor
  3. Ejecutamos las tareas que queramos en el servidor

 

Este es el diagrama de flujo de lo que vamos a hacer.

 

 

4. Pasos


4.1. Crear ficheros

El primer paso será crear los ficheros que requeriremos para este script:

hosts.txt: En este fichero almacenaremos las direcciones de los servidores que queremos conectarnos.

[salvatutiempo@localhost temp]$ nano hosts.txt
1.1.1.1
2.2.2.2
3.3.3.3
4.4.4.4
5.5.5.5

 

pass.txt: En este fichero almacenaremos la contraseña que será necesaria para conectarnos a los servidores, y que utilizará nuestro script para automatizar dicha tarea.

[salvatutiempo@localhost temp]$ nano pass.txt
PASS_SERVIDOR

stt_auto_tasks.sh: Este será el fichero donde pondremos todo el código. Deberemos autorizarle permisos de ejecución, utilizando el comando:

chmod +x stt_autotasks.sh

[salvatutiempo@localhost temp]$ sudo touch stt_auto_tasks.sh
[salvatutiempo@localhost temp]$ sudo chmod +x stt_auto_tasks.sh
[salvatutiempo@localhost temp]$ ls -l stt_auto_tasks.sh
-rwxr-xr-x 1 root root 0 Dec 11 10:44 stt_auto_tasks.sh

A continuación, veremos paso por paso los distintos pasos que tendrá el script

 

4.2. Variables

Definiremos primeramente las variables que serán necesarios posteriormente

SSH="sshpass -e ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null"

HOSTS="hosts.txt"
USER="username"
SSHPASS=$(cat pass.txt)
export SSHPASS

 

SSH: Esta variable poseerá parte del comando que usaremos para conectarnos a los servidores, y que explicaremos posteriormente.

HOSTS: Esta variable contiene la ubicación del fichero de texto donde se encuentra la dirección de todos los servidores que queremos utilizar.

USER: Esta variable tendrá el usuario que utilizaremos para conectarnos a los servidores

SSHPASS: Leerá la contraseña que hemos almacenado para un uso posterior

export SSHPASS: Esta línea permitirá utilizar la variable SSHPASS como variable global

 

4.3. Funciones

Para las tareas repetitivas, crearemos funciones que “lanzaremos” en nuestro código principal. En este caso, necesitaremos:

  • Función ping: Nos permitirá ver si un servidor es “alcanzable” (si hace ping)
  • Función actions_remote: Contendrá las tareas que deseamos realizar en el servidor destino, una vez estemos conectados al mismo.

 

Función “pingable”

Esta función nos permitirá saber si conseguimos llegar al servidor. Para ello utilizaremos el comando “ping” con ciertas opciones:

pingable() {
     echo $(ping -c1 $1 | grep -c error)
}

-c1: indicamos que únicamente se mande un paquete (ICMP) del cual esperamos respuesta

$1: Es nuestro valor de entrada para la función. Esperaremos recibir la dirección del host al cual hacer el “ping”.

| grep -c error: De los resultados obtenidos, contaremos aquellas líneas en las que aparece el texto “error”. Únicamente aparece dicho texto cuando NO conseguimos acceder al mismo.

# EJEMPLO DE PING "ALCANZABLE" A UN HOST (HACE PING)

[salvatutiempo@localhost temp]$ ping -c1 192.168.56.105
PING 192.168.56.105 (192.168.56.105) 56(84) bytes of data.
64 bytes from 192.168.56.105: icmp_seq=1 ttl=64 time=0.179 ms

--- 192.168.56.105 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.179/0.179/0.179/0.000 ms


# EJEMPLO DE PING NO "ALCANZABLE" A UN HOST (NO HACE PING)

[salvatutiempo@localhost temp]$ ping -c1 192.168.56.111
PING 192.168.56.111 (192.168.56.111) 56(84) bytes of data.
From 192.168.56.104 icmp_seq=1 Destination Host Unreachable

--- 192.168.56.111 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

 

Función “actions_remote”

Esta función permitirá concretar las tareas que deseamos realizar en el servidor. En este ejemplo particular, trataremos de extraer cierta información del hardware del servidor, y lo exportaremos.

actions_remote() {
     HOSTNAME=$(hostname)
     IPS=$(hostname -I)
     DISTRI=$(hostnamectl | grep Operating | cut -d: -f2)
     CPUUSE=$(top -b -n 1 | grep Cpu | cut -d, -f 1 | cut -d: -f2)
     MEMFREE=$(free -b | grep ^Mem| tr -s " "| cut -d" " -f 4)
     ROOTFREE=$(df / --output=pcent | sed 1d)
     echo "${HOSTNAME},${IPS},${DISTRI},${CPUUSE},${MEMFREE},${ROOTFREE}"
}

 

Estos son los campos que trataremos de extraer:

HOSTNAME: El nombre de host del sistema

IPS: Las direcciones de red de todas las tarjetas de red del host

DISTRI: La distribución Linux del Host

CPUUSE: La carga actual de CPU

MEMFREE: La memoria RAM disponible (en bytes)

ROOTFREE: El espacio libre disponible en la partición raíz (/)

Toda la información anterior la almacenamos en variables separadas, y en la última línea las exportamos todas.

 

4.4. Código Principal

Una vez hemos definido las funciones que vamos a realizar, vamos a definir nuestro código principal paso a paso.

 

Recorrer servidores

En este primer paso, crearemos un bucle “for” que recorrerá el fichero que contiene la dirección de todos nuestros servidores. Cada línea de dicho fichero la almacenaremos de forma temporal en la variable “$host”

for hosts in $(cat "$HOSTS")
do
     # CODE
done

El resto del código estará dentro de dicho bucle.

 

Comprobar si es alcanzable

Comprobaremos si dicho servidor es “alcanzable” ejecutando nuestra función “pingable” y tratando los resultados

if [ "$PINGABLE" == "0" ]
then
     # IF_CORRECT code
else
     echo "HOST $host is UNREACHABLE"
fi

 

Conectar al servidor

Una vez hemos confirmado que podemos llegar al servidor, trataremos de conectarnos

En la primera parte del comando de conexión, utilizaremos el protocolo SSH. Para ello, podríamos utilizar el comando “ssh”, pero deberíamos poner la contraseña de cada host al cual accediéramos.

Por tanto, para poder automatizar ese proceso, utilizaremos la aplicación “sshpass”.

Para ejecutar el comando, comentaremos algunas opciones que utilizaremos junto con el comando principal:

SSH="sshpass -e ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null"

 

sshpass -e ssh: Esta opción ejecutará el comando sshpass tomando como contraseña para acceder en la variable SSHPASS (opción -e). Previamente debemos haber declarado dicha variable en el código.

SSHPASS=$(cat pass.txt)

 

StrictHostKeychecking: Esta opción permitirá aceptar nuevas conexiones de forma automática al realizar la conexión, opción indispensable para automatizar esta tarea

[user@localhost ~]$ ssh user@192.168.56.105
The authenticity of host '192.168.56.105 (192.168.56.105)' can´t be established.
ECDSA key fingerprint is SHA256:PalS7dsCr0izjrKtK41ED1DKl2X+AHbRPYgcXJfye60.
ECDSA key fingerprint is MD5:26:a2:db:97:30:cc:da:01:cc:8c:07:e8:d0:e6:92:51.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '1.1.1.1' (ECDSA) to the list of known hosts.

 

UserKnownHostsFile: Esta opción especifica el fichero donde se almacenará las “llaves” de las conexiones que realizamos.

En nuestro caso, al utilizar “/dev/null” como destino, al realizar el comando SSH el chequeo previo a conectarse, pensará que nunca se ha conectado antes, por lo que no generará un error de “llave inválida”.

 

Ejecutar tareas

A continuación, definiremos las acciones a realizar una vez nos hayamos conectado al servidor.

$SSH "${USER}@${host}" "$(declare -f actions_remote); actions_remote"

 

${USER}@${host}: En esta sección definiremos la dirección del servidor (host), así como el nombre de usuario con el cual nos vamos a conectar (USER).

 

$(declare -f actions_remote); actions_remote: En esta sección incluimos las tareas que se realizarán en cada host. En nuestro caso:

  • Declararemos en el host destino la función que previamente hemos creado con todas las tareas a realizar ($(declare -f actions_remote))
  • Ejecutaremos dicha función (actions_remote)

 

¿Y si ejecuto tareas con permisos (sudo)?

Puede ser que vayamos a realizar tareas que requieran permisos de administrador. El ejemplo mas típico es realizar el reinicio de dichos hosts.

actions_remote() {
     sudo reboot
}

 

Para ello deberemos realizar ciertos cambios en la línea de opciones extras, que explicaremos a continuación:

$SSH "${USER}@${host}" "echo '${SSHPASS}' | sudo --stdin bash -c '$(declare -f actions_remote); actions_remote'"

 

echo ${SSHPASS} |: Este comando nos permitirá incluir dentro de nuestro host destino la variable SSHPASS. Esta variable la debemos haber declarado previamente con la contraseña de acceso del usuario.

 

sudo --stdin bash -c: Este comando nos permitirá ejecutar los comandos que vayan a continuación con permisos de administrador.

Concretamente trata de ejecutar permisos de administrador (sudo --stdin), recibiendo como entrada la contraseña de usuario (a través de la variable SSHPASS), y el comando que ejecuta es el de ejecutar la shell (bash -c).

 

$(declare -f actions_remote); actions_remote: Esta última parte declara la función de tareas que ejecutaremos en el host destino, y lo ejecutaremos.

 

5. Extras


5.1. Permisos sólo lectura

La contraseña que hemos utilizado para poder conectarnos al servidor suele tener más permisos de los necesarios. Esto hace que pueda ser utilizado por más usuarios de los que debería.

[salvatutiempo@localhost temp]$ ls -l pass.txt
-rw-r--r-- 1 root root 5 Dec  8 09:54 pass.txt

 

En nuestro ejemplo, nuestra contraseña tiene:

  • Permisos de lectura y escritura para el usuario que lo creó (usuario root)
  • Permisos de lectura para cualquier usuario perteneciente al grupo root
  • Permisos de lectura para cualquier otro usuario no perteneciente a los antes definidos

 

Lo recomendable es restringir este acceso, de forma que sólo el usuario que lo creó tenga accesos. Para ello, ejecutaremos el siguiente comando, que limitará a que sea leído por el usuario que lo creó.

sudo chmod 400 pass.txt

Tras los cambios observamos que los permisos han cambiado:

[salvatutiempo@localhost temp]$ sudo chmod 400 pass.txt

[salvatutiempo@localhost temp]$ ls -l pass.txt
-r-------- 1 root root 5 Dec  8 09:54 pass.txt

 

5.2. Encriptar contraseña

Como hemos podido observar en este ejemplo, utilizamos una contraseña almacenada de forma “plana” en un fichero para poder conectarnos a los distintos servidores.

Para que sea más seguro podemos encriptar la contraseña, y desencriptarla en el código, para que sea utilizada únicamente en el momento necesario. En dicha encriptación nos solicitará un “passphrase”, que será nuestra llave para poder desencriptar este fichero.

Para ello:

1. Supongamos que nuestra contraseña se encuentra en el archivo “pass.txt”. Utilizaremos GPG para encriptar el fichero, utilizando el siguiente comando:

$ gpg -c pass.txt

 

2. Nos solicitará un “passphrase” para poder desencriptarlo posteriormente

lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk
x Enter passphrase                                    x
x                                                     x
x                                                     x
x Passphrase ________________________________________ x
x                                                     x
x       <OK>                             <Cancel>     x
mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj

 

Esto nos generará un nuevo fichero con el mismo nombre que el original, pero con una nueva extensión gpg (en nuestro caso, “pass.txt.gpg”).

3. Eliminaremos nuestro fichero plano original “pass.txt” para que no pueda ser leído.

$ sudo rm -rf pass.txt

4. Para hacer más seguro nuestro archivo encriptado, ocultaremos nuestro nuevo archivo. Para ello, agregamos al nombre un “.” (en nuestro ejemplo, “.pass.txt.gpg”). De esta forma, únicamente podrá ser listado utilizando el comando “ls -a”.

5. Almacenaremos también nuestro “passphrase” en nuestro sistema para poder desencriptar posteriormente. En este ejemplo, lo almacenaremos en el fichero “.passphrase.txt” (con el punto “.”  inicial para ocultar el archivo)

6. Restringiremos los permisos de nuestra “passphrase” para que únicamente sea leído por el creador del archivo.

chmod 400 .passphrase.txt

7. Para desencriptar, dado que lo hacemos a través de nuestra variable SSHPASS, haremos que:

  • en lugar de leer nuestro archivo “pass.txt”
SSHPASS=$(cat pass.txt)

 

  • desencripte nuestro nuevo archivo “.pass.txt.gpg” utilizando nuestra “passphrase”
SSHPASS=$(gpg --passphrase-file .passphrase.txt -d .pass.txt.gpg)

 

6. Quiero el código


El código completo lo podrás encontrar en el repositorio que he creado aquí:

Código completo

 

 

¿Te ha gustado?

Si te ha gustado y quieres aportar tu granito de arena para que esta comunidad crezca: