Un problema que tenemos muchos administradores de sistemas GNU/Linux es cómo ejecutar tareas de mantenimiento de forma eficiente sobre muchos servidores al mismo tiempo. Desde el momento en que nuestra organización pasa a tener varias decenas de servidores, ejecutar operaciones de mantenimiento simples, como por ejemplo actualizar el sistema, pasan a ser tareas tediosas que consumen tiempo y recursos humanos.

Claro que muchas de las tareas de mantenimiento de un sistema operativo se pueden automatizar utilizando scripts y tareas programadas. Sin embargo algunas requieren intervención humana: un claro ejemplo es actualizar el sistema. No se puede actualizar el sistema a ciegas y esperar que todo funcione. Muchas actualizaciones requieren reinicio de servicios o nuevas versiones de archivos de configuración. Sin contar con los conflictos producidos por aplicaciones a medida atadas a versiones específicas de paquetes. Por ello el administrador de sistemas debe examinar lo que cada gestor de paquetes ofrece actualizar, para evaluar si se debe proceder o se requiere alguna clase de intervención manual. Además el administrador de sistemas tiene en cuenta si una actualización requiere reiniciar servicios (lo que tal vez requiera posponer la actualización de un servidor para no afectar la operatoria de la organización) y durante la actualización debe actuar ante incompatibilidades producto de nuevas versiones de archivos de configuración.

Pero, a pesar de estas limitaciones, es posible automatizar el proceso de autenticación en cada sistema remoto. Utilizando un usuario con autenticación SSH con clave pública y capacidad de actualización del sistema, se evita que el administrador deba abrir decenas de sesiones SSH remotas para lanzar las actualizaciones. De esta forma se agiliza el proceso, sin perder control sobre el mismo (como veremos).

En este artículo voy a explicar detalladamente cómo, utilizando un servidor de administración centralizado, elaborar una arquitectura de autenticación que permita lanzar actualizaciones de múltiples servidores remotos desde un simple script Bash.



La idea de este artículo consiste en definir un servidor de administración que cuente con un usuario sin privilegios con la capacidad de conectarse remotamente a todos los servidores de la organización. Este usuario autenticará las sesiones SSH utilizando clave pública, para evitar ingresar contraseñas. Se puede crear un nuevo servidor específico para este rol, o cualquier servidor de nuestra organización puede cumplirlo si estamos "cortos" de recursos. El usuario en cuestión tendrá la capacidad de actualizar cada uno de los sistemas remotos autenticándose como superusuario. Para ello será necesario configurar reglas de sudo restrictivas en cada host.

El script Bash ofrecerá actualizar cada servidor dentro de la organización, tal como se hayan definido previamente en un archivo de configuración. El administrador tendrá la opción de lanzar o no la actualización y, si se procede, podrá interactuar con cada gestor de paquetes.

¡Manos a la obra!

Configuración en el servidor de administración

Loguearse como root en el servidor de administración (a partir de ahora "adminsrv") y crear el usuario "sysadmin" con /home/sysadmin como directorio $HOME:

root@adminsrv:~# useradd -d /home/sysadmin -m -s /bin/bash sysadmin

Cambiar al usuario "sysadmin":

root@adminsrv:~# su - sysadmin

Crear el directorio "scripts":

sysadmin@adminsrv:~$ mkdir scripts

Cambiar al directorio "scripts":

sysadmin@adminsrv:~$ cd scripts/

Crear el archivo de configuración de servidores:

sysadmin@adminsrv:~/scripts$ nano actualizar_servidores.conf

Cada línea de este archivo configura el acceso a un servidor remoto utilizando el formato:

USUARIO:HOSTNAME:PUERTO:SISTEMA

El campo "SISTEMA" debe coincidir con las palabras "debian" o "centos". Puerto indica el puerto donde escucha el servidor SSH.

Como veremos más adelante, el usuario debe soportar el acceso sin contraseña (autenticando con clave pública) y debe tener permiso para ejecutar los comandos apt-get (si es un sistema Debian) o yum (si es un sistema CentOS). Por ejemplo, el contenido del archivo de configuración actualizar_servidores.conf debe ser:

sysadmin:adminsrv.linuxito.com:2022:debian
sysadmin:wwwcentos6.linuxito.com:2022:centos
sysadmin:dbdebian7.linuxito.com:2022:debian

Crear el script Bash de actualización automágica:

sysadmin@adminsrv:~/scripts$ nano actualizar_servidores.sh

El script tiene el siguiente contenido:

#!/bin/bash

CONF="actualizar_servidores.conf"
CMD_DEBIAN="sudo apt-get update && sudo apt-get upgrade && sudo apt-get clean"
CMD_CENTOS="sudo yum update && sudo yum clean all"

# CONF - username:host:port:os

for SERVER in $(cat $CONF)
do
    # Parsear linea de configuración
    USER=$(echo $SERVER | cut -d ':' -f1)
    HOST=$(echo $SERVER | cut -d ':' -f2)
    PORT=$(echo $SERVER | cut -d ':' -f3)
    OS=$(echo $SERVER | cut -d ':' -f4)

    # Ofrecer actualizar el servidor actual
    echo -e "#\n##\n###\n####"
    read -p "¿Desea actualizar $HOST? (Si/No): " ANS

    if [[ $ANS =~ ^[SsYy]$ ]]
    then
        # Actualizar el servidor
        case $OS in
            "debian") ssh -p $PORT $USER@$HOST $CMD_DEBIAN;;
            "centos") ssh -t -p $PORT $USER@$HOST $CMD_CENTOS;;
        esac
    fi

done

 

El script sólo soporta sistemas operativos basados en Debian y derivados, y sistemas operativos basados en Red Hat y derivados, por ello el campo "SISTEMA" sólo admite las palabras clave "debian" y "centos". Esto se debe a que hasta el momento soporta el uso de los gestores de paquetes apt y yum. Cada quien puede personalizar el script para que admita otros sistemas (por ejemplo Arch y derivados), sólo basta con agregar una nueva línea en la cláusula "case" (por ejemplo para trabajar con pacman).

Una vez creado el script de actualización automática de servidores remotos, es necesario generar el par de claves de autenticación SSH para que el usuario "sysadmin" pueda loguearse remotamente en cualquier sistema de nuestra organización, sin necesidad de ingresar su contraseña.

Cambiar al directorio $HOME:

sysadmin@adminsrv:~$ cd

Generar el par de claves de autenticación para SSH de tipo DSA (protocolo SSH versión 2):

sysadmin@adminsrv:~$ ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/home/sysadmin/.ssh/id_dsa): 
Created directory '/home/sysadmin/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/sysadmin/.ssh/id_dsa.
Your public key has been saved in /home/sysadmin/.ssh/id_dsa.pub.
The key fingerprint is:
a2:d5:d3:28:f9:8f:e7:88:8d:73:53:ca:13:4c:2c:9a sysadmin@adminsrv
The key's randomart image is:
+--[ DSA 1024]----+
|  .haaerk        |
|  E. hd          |
|    . ku gfffd   |
|    -,.,f        |
|        S        |
|     ffddd       |
|     .XfB +      |
|     .=+ds       |
|    hdf=.+o      |
+-----------------+

Revisar los permisos de las claves generadas. id_dsa es la clave privada y debe tener permisos restrictivos (600) para que no pueda ser leída por nadie:

sysadmin@adminsrv:~$ ls -lath .ssh/
total 16K
drwx------ 2 sysadmin sysadmin 4,0K nov  4 11:04 .
-rw------- 1 sysadmin sysadmin  668 nov  4 11:04 id_dsa
-rw-r--r-- 1 sysadmin sysadmin  607 nov  4 11:04 id_dsa.pub
drwxr-xr-x 4 sysadmin sysadmin 4,0K nov  4 11:04 ..

La clave privada será el talón de Aquiles de nuestro esquema de autenticación. Debe ser lo más secreta posible, ya que con la misma se podrá acceder a cualquier servidor de nuestra organización (aunque por supuesto sin privilegios de superusuario).

Además, el directorio /home/sysadmin/.ssh/ debe tener permisos 700 (en todos los servidores).

Hasta aquí hemos finalizado la configuración del servidor de administración.

Configuración en los servidores remotos

Para cada uno de los servidores que se han configurado dentro del archivo /home/sysadmin/scripts/actualizar_servidores.conf (en el servidor de administración), será necesario: 1- crear el usuario "sysadmin"; 2- configurar el acceso sin contraseña autenticando mediante clave pública; y 3- configurar la herramienta sudo para que "sysadmin" pueda ejecutar el gestor de paquetes autenticando como superusuario sin solicitar contraseña.

Repetir los siguientes pasos para cada servidor. A modo de ejemplo utilizo el servidor ficticio "dbdebian7":

Crear el usuario "sysadmin"

Loguearse como root en cada servidor remoto y crear el usuario "sysadmin":

root@dbdebian7:~# useradd -d /home/sysadmin -m -s /bin/bash sysadmin
Configurar el acceso sin contraseña

El siguiente paso consiste en configurar cada servidor remoto para que "sysadmin" pueda acceder mediante SSH autenticando mediante clave pública.

Cambiar al usuario "sysadmin":

root@dbdebian7:~# su - sysadmin

Si no existe, crear el directorio .ssh y asignarle los permisos adecuados:

sysadmin@dbdebian7:~$ mkdir .ssh
sysadmin@dbdebian7:~$ chmod 700 .ssh/

Si el archivo .ssh/authorized_keys no existe, crearlo con los permisos adecuados (de lo contrario la autenticación con clave pública no funcionará correctamente):

sysadmin@dbdebian7:~$ touch .ssh/authorized_keys
sysadmin@dbdebian7:~$ chmod 644 .ssh/authorized_keys

Para que el usuario "sysadmin" del sistema "adminsrv" pueda ingresar localmente como "sysadmin", su clave pública de encontrarse dentro del archivo .ssh/authorized_keys.

Volver a la sesión de "root" en "dbdebian7":

sysadmin@dbdebian7:~$ exit
root@dbdebian7:~#

Verificar que el servidor SSH en cada servidor remoto permita la autenticación mediante clave pública. Para ello, abrir el archivo /etc/ssh/sshd_config:

root@dbdebian7:~# nano /etc/ssh/sshd_config

Verificar que las siguientes líneas no estén comentadas:

PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

Si en lugar de DSA se utilizan claves RSA, será necesario además descomentar la línea:

RSAAuthentication yes

Si se han realizado cambios, reiniciar el servidor SSH ejecutando service ssh restart (Red Hat y derivados) o service sshd restart (Debian y derivados).

Volver a la sesión de "sysadmin" en el servidor de administración ("adminsrv").

Desde el servidor de administración, concatenar el contenido de la clave pública del usuario "sysadmin" local en los archivos .ssh/authorized_keys en todos los servidores remotos, para permitir autenticar sin password al usuario "sysadmin" (remoto):

sysadmin@adminsrv:~$ cat .ssh/id_dsa.pub | ssh -p 2022 root@dbdebian7.linuxito.com "cat >> /home/sysadmin/.ssh/authorized_keys"

Repetir este comando para cada hostname configurado en el archivo /home/sysadmin/scripts/actualizar_servidores.conf.

Notar que en el ejemplo me he logueado en cada servidor remoto como root. Por seguridad no es recomendable habilitar el login de root por SSH y suele estar deshabilitado (PermitRootLogin No). Pero ya que se está escribiendo en el $HOME del usuario "sysadmin", será necesario habilitar temporalmente el acceso por SSH como root, o asignarle un password a "sysadmin" ejecutando passwd sysadmin (para loguearse en el comando anterior como "sysadmin" en vez de root).

Para cada servidor, verificar que la autenticación con clave pública funcione:

sysadmin@adminsrv:~$ ssh -p 2022 sysadmin@dbdebian7.linuxito.com
Linux dbdebian7 3.2.0-4-amd64 #1 SMP Debian 3.2.60-1+deb7u3 x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Oct 30 13:08:51 2013 from hal9000.linuxito.com
sysadmin@dbdebian7:~$

Tal como muestra la captura anterior, debe ser posible ingresar desde el sistema "adminsrv" sin solicitar el password de "sysadmin".

Configurar sudo para que "sysadmin" pueda sólo actualizar el sistema

El siguiente paso consiste en permitir que "sysadmin" en "dbdebian7" pueda ejecutar el gestor de paquetes como superusuario, sin necesidad de ingresar su contraseña (que por cierto no se ha asignado). Por ejemplo, si se trata de un servidor Debian será necesario ejecutar sudo apt-get update sin que se requiera ingresar el password de "sysadmin".

Por seguridad es necesario configurar un sudo restrictivo. No deseamos que "sysadmin" pueda ejecutar cualquier cosa como root sin necesidad de ingresar contraseña, pues esto sería un fallo de seguridad grave. Sólo se desea que el usuario "sysadmin" pueda ejecutar como superusuario (root), sin necesidad de ingresar su contraseña, los comandos sudo apt-get update, sudo apt-get upgrade y sudo apt-get clean.

Cambiar al directorio de configuración de reglas de sudo:

root@dbdebian7:~# cd /etc/sudoers.d/

Dentro de este directorio, crear dos archivos de configuración de reglas de sudo: uno de configuración global para el servidor (llamado "servidor"), y otro de configuración de permisos del usuario "sysadmin" (llamado "sysadmin").

Crear el archivo de configuración de reglas globales:

root@dbdebian7:/etc/sudoers.d# nano servidor

Sea cual sea el sistema (Debian o CentOS), el archivo /etc/sudoers.d/servidor debe tener el siguiente contenido:

# Servidor
Host_Alias SERVIDOR = localhost, dbdebian7.linuxito.com, dbdebian7

Cambiar la variable "SERVIDOR" para que refleje el nombre de host de cada servidor.

Luego será necesario definir las reglas para que "sysadmin" pueda ejecutar sólo el gestor de paquetes, autenticando como superusuario y sin necesidad de ingresar su contraseña:

root@dbdebian7:/etc/sudoers.d# nano sysadmin

Si es un sistema Debian, debe tener el siguiente contenido:

# Usuario "sysadmin"
User_Alias USR_ADMIN = sysadmin

# Comandos para gestionar servicios
Cmnd_Alias CMD_SERVICE = /usr/sbin/service *

# Comandos para actualizar el sistema
Cmnd_Alias CMD_APT_UPDATE = /usr/bin/apt-get update
Cmnd_Alias CMD_APT_UPGRADE = /usr/bin/apt-get upgrade
Cmnd_Alias CMD_APT_CLEAN = /usr/bin/apt-get clean

# Reglas
USR_ADMIN SERVIDOR = CMD_SERVICE
USR_ADMIN SERVIDOR = NOPASSWD:CMD_APT_UPDATE
USR_ADMIN SERVIDOR = NOPASSWD:CMD_APT_UPGRADE
USR_ADMIN SERVIDOR = NOPASSWD:CMD_APT_CLEAN

Sólo se permite que "sysadmin" pueda ejecutar sin ingresar password los comandos apt-get update, apt-get upgrade y apt-get clean (sin embargo, "sysadmin" no tiene permisos para, por ejemplo, instalar o eliminar paquetes). Notar la opción "NOPASSWD:" delante de éstos comandos. En cambio para usar service deberá ingresar el password (que por cierto no tiene, así que hasta que no se ejecute passwd sysadmin como root, no se podrá ejecutar sudo service ... como "sysadmin").

Si en cambio es un servidor CentOS, el archivo /etc/sudoers.d/sysadmin deberá tener el siguiente contenido:

# Usuario "sysadmin"
User_Alias USR_ADMIN = sysadmin

# Comandos para gestionar servicios
Cmnd_Alias CMD_SERVICE = /usr/sbin/service *

# Comandos para actualizar el sistema
Cmnd_Alias CMD_YUM_UPDATE = /usr/bin/yum update
Cmnd_Alias CMD_YUM_CLEAN_ALL = /usr/bin/yum clean all

# Reglas
USR_ADMIN SERVIDOR = CMD_SERVICE
USR_ADMIN SERVIDOR = NOPASSWD:CMD_YUM_UPDATE
USR_ADMIN SERVIDOR = NOPASSWD:CMD_YUM_CLEAN_ALL

En este caso se nota que en vez de apt se autorizan los comandos necesarios de yum.

Luego de crear los archivos /etc/sudoers.d/servidor y /etc/sudoers.d/sysadmin, es necesario configurar sus permisos a nivel filesystem de forma adecuada:

root@dbdebian7:/etc/sudoers.d# chmod 440 *

Prueba del script

Una vez que se ha creado el archivo de configuración de servidores, y se ha replicado la configuración de SSH y sudoers, es posible verificar el correcto funcionamiento.

De vuelta en el servidor de administración, cambiar al directorio de scripts de "sysadmin":

sysadmin@adminsrv:~$ cd scripts/

Otorgar permiso de ejecución al script:

sysadmin@adminsrv:~/scripts$ chmod +x actualizar_servidores.sh

Finalmente ejecutarlo y ver como fluye:

sysadmin@admin-db:~/scripts$ ./actualizar_servidores.sh 
#
##
###
####
¿Desea actualizar adminsrv.linuxito.com? (Si/No): n
#
##
###
####
¿Desea actualizar wwwcentos6.linuxito.com? (Si/No): n
#
##
###
####
¿Desea actualizar dbdebian7.linuxito.com? (Si/No): y
Obj http://ftp.ca.debian.org wheezy Release.gpg
Des:1 http://security.debian.org wheezy/updates Release.gpg [836 B]
Des:2 http://ftp.ca.debian.org wheezy-updates Release.gpg [836 B]
Des:3 http://security.debian.org wheezy/updates Release [102 kB]
Obj http://ftp.ca.debian.org wheezy Release
Des:4 http://ftp.ca.debian.org wheezy-updates Release [124 kB]
Des:5 http://security.debian.org wheezy/updates/main amd64 Packages [221 kB]
Des:6 http://security.debian.org wheezy/updates/main Translation-en [125 kB]
Obj http://ftp.ca.debian.org wheezy/main amd64 Packages
Obj http://ftp.ca.debian.org wheezy/main Translation-es
Obj http://ftp.ca.debian.org wheezy/main Translation-en
Obj http://ftp.ca.debian.org wheezy-updates/main amd64 Packages/DiffIndex
Obj http://ftp.ca.debian.org wheezy-updates/main Translation-en/DiffIndex
Descargados 573 kB en 5seg. (107 kB/s)
Leyendo lista de paquetes...
Leyendo lista de paquetes...
Creando árbol de dependencias...
Leyendo la información de estado...
Se actualizarán los siguientes paquetes:
  libapache2-mod-php5 php5 php5-cli php5-common php5-curl php5-gd php5-intl
  php5-mcrypt php5-pgsql php5-sqlite php5-xmlrpc php5-xsl
12 actualizados, 0 se instalarán, 0 para eliminar y 0 no actualizados.
Necesito descargar 6.223 kB de archivos.
Se utilizarán 776 kB de espacio de disco adicional después de esta operación.
¿Desea continuar [S/n]? y
...

El funcionamiento del script es sencillo: por cada línea del archivo, extrae los parámetros de conexión y el tipo de sistema operativo y luego ofrece la actualización. Si se trata de un Debian ejecuta remotamente sudo apt-get update && sudo apt-get upgrade && sudo apt-get clean. Si en cambio es CentOS ejecuta sudo yum update && sudo yum clean all.

Para el caso de CentOS utilicé el parámetro -t en la conexión SSH para forzar la reserva de una TTY, ya que por defecto en CentOS (al menos en la versión 6) no se abre una TTY cuando se ejecuta un comando sin abrir una sesión. La necesidad de reservar una TTY surge del hecho que sudo requiere una TTY para ejecutar comandos como superusuario.

Gracias a que la entrada estándar se redirige a la sesión remota, es posible interactuar con el gestor de paquetes y decidir si actualizar o no de acuerdo a lo que ofrece (ya que tanto apt como yum solicitan confirmación antes de proceder). De esta forma tenemos un script semiautomático donde, si todo va bien, sólo hay que apretar la letra y griega. Por lo tanto, la próxima vez que surja un heartbleed, shellshock, o algo por el estilo, sólo tendré que sentarme y presionar 'Y', lo cual me recuerda a un episodio de Los Simpsons.

Me pueden agradecer en los comentarios la próxima vez que necesiten actualizar todos sus servidores de forma urgente.

Referencias

Cómo autenticar con clave pública en SSH

Cómo concatenar a un archivo remoto vía SSH

Cómo permitir que un usuario pueda ejecutar como root sólo un comando específico utilizando sudo


Tal vez pueda interesarte


Compartí este artículo