Configuración básica de seguridad en un VPS

Buenas a todos conejos!! En la línea de la temática de la temática anterior en la que utilizábamos un VPS para desplegar una instancia de GitLab, hoy vengo a enseñar algunos pasos muy básicos y rápidos que nos permitirán securizar nuestro VPS dedicado.

En resumen estos son los pasos que ejecutaremos:

  • Creación del VPS.
  • Configuración de un usuario no privilegiado.
  • Configuración del servicio SSH.
  • Instalación y configuración de UFW.
  • Instalación y configuración de Fail2Ban.

Y como pequeño extra…

  • Automatización del proceso 😉

Creación del VPS

El primer paso ya lo hicimos en el primer apartado de la entrada antes mencionada haciendo uso de los Droplets del proveedor DigitalOcean. Asimismo, tanto DigitalOcean como otros proveedores como AWS permiten crear el Droplet (o equivalente) con una clave SSH de acceso ya preconfigurada (también lo hicimos en la entrada anterior).

Configuración de un usuario no privilegiado

El siguiente paso será la creación y configuración de un usuario no privilegiado en caso de que nuestro proveedor proporcione de forma predeterminada acceso a la máquina con usuario root. Es una buen práctica de seguridad que deshabilitemos esto cuanto antes y permitamos solamente el acceso mediante usuario no privilegiado a la máquina.

Sigamos con el ejemplo de DigitalOcean y un servidor Debian que llamaremos SECURITY-TESTING. De forma predeterminada accedemos por SSH como usuario root. De forma predeterminada también, Debian se despliega con un usuario no privilegiado llamado debian, que configuraremos para ser nuestro usuario no privilegiado.

Lo primero será hacer una copia del directorio oculto /root/.ssh a /home/debian/.ssh. Éste directorio contiene los datos necesarios para la conexión a la máquina por SSH con nuestra clave privada, concretamente en el fichero authorized_keys. Lo que permitiremos así es poder loguearnos en la máquina como debian haciendo uso de dicha clave.

Debemos además establecer a debian como dueño de su directorio .ssh.

# cp -a /root/.ssh /home/debian/.ssh
# chown -R debian:debian /home/debian/.ssh

En este punto, podemos abrir otra sesión SSH como usuario debian haciendo uso de la misma clave y podremos tener acceso a la máquina (dejemos la sesión privilegiada abierta además de ésta para seguir configurando la máquina).

Hecho esto, en nuestra sesión como root, vaciaremos el fichero /root/.ssh/authorized_keys de forma que no impidamos el acceso directo como root a la máquina.

# echo "" > ~/.ssh/authorized_keys

Hecho esto, veremos que ya no podemos usar nuestra clave para hacer login como root (¡así que no cierres la que ya teníamos!).

Podemos observar fácilmente que desde el usuario debian podemos ejecutar fácilmente comandos como administrador o root haciendo uso del comando sudo o simplemente cambiando a usuario root haciendo uso de sudo su.

¿De qué nos sirve acceder a sistema solamente como usuario no privilegiado si de un comando me puedo hacer root? DigitalOcean (entre otros) hace uso de cloud-init para realizar una configuración inicial en la máquina después de su creación, que además de realizar tareas como la preconfiguración de las claves SSH de acceso, también provoca que el usuasrio debian pueda elevarse a root de forma fácil con lo anterior.

Para evitar ésto, renombraremos el fichero /etc/sudoers.d/debian-cloud-init agregándole al final un símbolo ~:

# mv /etc/sudoers.d/debian-cloud-init /etc/sudoers.d/debian-cloud-init~

Ésto hará que el fichero se ignore y anulemos la configuración antes mencionada. Ahora, al ejecutar sudo su como usuario debian, se nos pedirá la contraseña de dicho usuario:

Sin embargo, dado que solamente accederemos al usuario debian mediante SSH, no tenemos ninguna configurada, ni tampoco nos hace falta. No será posible por tanto hacer login como debian mediante contraseña, e incluso si le pusiésemos una, no sería posible ejecutar comandos privilegiados con dicho usuario.

Hablando de lo cual, ¿cuál es la contraseña del usuario root? De momento no hay ninguna configurada, dado que, de nuevo, el acceso al VPS en principio se hacía solamente por clave SSH. Estableceremos una para poder escalar de forma segura a root desde usuario no privilegiado haciendo uso de la misma (no como antes, cuando podíamos hacerlo automágicamente) y ejecutar tareas de administración.

Tendremos que configurarla en nuestra sesión privilegiada y establecer una suficientemente larga y compleja:

# passwd

Ahora, desde nuestra sesión como debian, podremos ejecutar su root para hacer uso de la contraseña configurada y obtener una sesión de administrador.

Configuración del servicio SSH

Realizaremos algunos cambios en el fichero /etc/ssh/sshd_config para configurar el servicio SSH mediante el cual accedemos a la máquina. Una buena guía a seguir es deshabilitar todo aquello que sepas que no vas a utilizar.

Aunque algunos valores que voy a nombrar ya están establecidos con el valor que indico de forma predeterminada, prefiero explicarlos y activarlos de forma explícita por si nos encontramos en una máquina con una configuración diferente a la habitual y además para que os quede claro el significado de cada valor 🙂

Lo más básico será cambiar el puerto en el que escucha el servicio. Cambiar el puerto no es una medida de seguridad en sí, dado que simplemente es añadir «seguridad por oscuridad», y eso no es añadir seguridad. Sin embargo, evitaremos que las botnet nos estén bombardeando de forma constante el puerto 22 (normalmente sin éxito, dado que no tenemos acceso por contraseña).

Podemos cambiarlo por el que queramos. Por poner un ejemplo, el 3322. Cambiaremos la línea Port:

Port 3322

Dado que el servicio SSH deberá ser accedido desde fuera, lo dejaremos escuchando en 0.0.0.0 para IPv4 en la línea ListenAddress, que indica que escuchará en todas las direcciones IPv4 que tenga asignadas la máquina. Puede verse también que hay otra línea ListenAddress que establece lo correspondiente a IPv6, pero como de forma predeterminada DigitalOcean no conecta este protocolo al Droplet, podemos ignorarlo. Si queremos forzar a usar solamente IPv4, podemos establecer el valor de la línea AddressFamily a inet.

AddressFamily inet
ListenAddress 0.0.0.0

Estableceremos el valor LoginGraceTime a 10, de forma que a los 10 segundos de mostrar la pantalla de login de SSH la sesión terminará si no se ha conseguido abrir sesión.

LoginGraceTime 10

Cambiaremos la línea PermitRootLogin a no, impidiendo que pueda intentar iniciarse sesión directamente como  usuario root.

PermitRootLogin no

La opción StrictModes, que ya de forma predeterminada está activada, permite que el servicio SSH compruebe al inicio si el usuario con el que queremos tener sesión tiene bien configurados los permisos de sus ficheros de claves, como por ejemplo el authorized_keys del que hablábamos antes. Si no lo están, no nos dejará establecer sesión. Podemos comprobarlo cambiando los permisos de dicho fichero de 600 a 666 e intentar abrir sesión, observando los logs de SSH en /var/log/auth:

# chmod 666 /home/debian/.ssh/authorized_keys
# tail -f /var/log/auth

Por tanto dejamos la línea en yes:

StrictModes yes

Configuraremos las líneas MaxAuthTries a 1, de forma que solamente pueda haber un intento de autenticación por sesión, y MaxSessions a 3, de forma que solamente pueda haber abiertas 3 sesiones SSH simultáneas.

MaxAuthTries 1
MaxSessions 3

La línea PubkeyAuthentication la dejaremos puesta en yes, permitiendo nuestro acceso mediante claves SSH al usuario debian.

PubkeyAuthentication yes

La línea HostbasedAuthentication permite que una máquina remota en la que confiamos pueda de forma automática iniciar sesión en nuestro VPS por SSH. Toda máquina que haga uso de un cliente SSH tiene configurada una clave host, independiente a las claves de usuario. Para ello, añadiríamos en el fichero ~/.ssh/known_hosts de un usuario local la clave pública de dicha máquina, permitiendo que dicha máquina pueda iniciar sesión de forma fácil en nuestro VPS con dicho usuario. Esto suele usarse en entornos en los que tenemos una máquina que monitoriza a otras, accediendo a ellas periódicamente. Dado que no vamos a usar esta funcionalidad, lo dejaremos en no.

HostbasedAuthentication no

Cambiaremos la línea PasswordAuthentication a no, dado que  únicamente accederemos a la máquina haciendo uso de claves SSH, nunca con contraseña. Dejaremos la línea PermitEmptyPasswords en no, impidiendo el uso de contraseñas vacías. Realmente con la opción anterior es redundante, dado que nunca se dará el caso de pedir una contraseña de acceso.

PasswordAuthentication no
PermitEmptyPasswords no

La opción ChallengeResponseAuthentication permite establecer opciones avanzadas de autenticación haciendo uso de un servicio independiente, como podría ser PAM. No vamos a hacer uso de ello así que lo dejaremos en no.

ChallengeResponseAuthentication no

Asimismo, dejaremos las opciones de autenticación de Kerberos (KerberosAuthentication) y de GSSAPI (GSSAPIAuthentication) desactivadads.

KerberosAuthentication no
GSSAPIAuthentication no

Asimismo, dado que solamente permitimos el acceso mediante claves SSH, podemos deshabilitar el uso del servicio PAM (Pluggable Authentication Module).

UsePAM no

Dado que el único uso que vamos a dar al servicio SSH va a ser la conexión directa a la máquina, podemos desactivar las opciones relacionadas con el forwarding y el tunelado, así como el X11 Forwarding para el tunelado de aplicaciones gráficas.

AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
PermitTunnel no

Por último estableceremos el intervalo ClientAliveInterval a 60 segundos, tras lo cual el servidor enviará un mensaje al cliente para ver si sigue conectado; si no responde, acabará la sesión. El valor ClientAliveCountMax 1 hará que se cierre el canal la primera vez que falle esta comprobación. Asimismo dejaremos la opción TCPKeepAlive activada para que periódicamente se compruebe el estado del canal y se cierre si se detecta que ha caído.

ClientAliveInterval 60
ClientAliveCountMax 1
TCPKeepAlive yes

Por último configuraremos MaxStartups. Consta de tres valores separados por dos puntos. El primer valor establece el número máximo de sesiones sin autenticar (pantallas de login), tras el cual comenzarán a rechazarse un cierto porcentaje de nuevas conexiones (segundo valor), aumentando linearmente hasta que se llegue a cierto número de sesiones sin autenticar (el tercer valor), en cuyo punto se rechazarán todas las nuevas sesiones.

Elegiremos el valor predeterminado 10:30:100.

MaxStartUps 10:30:100

Con ésto deberíamos poder tener una configuración SSH bastante segura. Os dejo un fichero gist con un sshd_config que recoge todas las configuraciones que hemos descrito, y que podréis subir a una máquina recién creada para configurar el servicio de forma rápida. He dejado el puerto 22 predeterminado, pero recordad que podría ser conveniente cambiarlo.

Para descargar el gist a la máquina (previa copia de seguridad del fichero anterior) y cargar la nueva configuración  del servicio SSH:

# cp /etc/ssh/sshd_config /etc/ssh/sshd_config.back
# wget https://gist.githubusercontent.com/hartek/82decb8f0817d1a6ec8a10454e9134c4/raw/c4aa0d62231e1ecf269ce04a43deb263c9ae0cd5/sshd_config -O /etc/ssh/sshd_config
# systemctl reload ssh

Con eso deberíamos tener nuestro servicio SSH configurado y corriendo de forma decentemente segura 🙂

Instalación y configuración de UFW

UFW o Uncomplicated FireWall es un wrapper de IPTables que nos permite crear reglas de Firewall de forma rápida y sencilla sobre una máquina Linux. Es una gran opción si lo que nos interesa es crear unas cuantas reglas sencillas para permitir o denegar conexiones a nuestra máquina.

La herramienta puede descargarse desde los repositorios de casi cualquier distribución. Por ejemplo con Debian podemos instalar UFW con apt:

# apt install ufw

De forma predeterminada, UFW una vez activado rechazará toda conexión entrante a nuestra máquina. Dependiendo de nuestra configuración y de los servicios que queramos ofrecer al exterior con nuestro VPS, querremos permitir la conexión desde ciertos puertos.

En caso de SSH y otros servicios conocidos, UFW permite indicar simplemente su nombre y habilitará las conexiones al puerto predeterminado, como puede ser en caso de OpenSSH:

# ufw allow openssh

Se nos abrirá el puerto 22 en TCP y TCPv6 desde cualquier lugar.

Sin embargo, en nuestro caso esto no aplica, dado que cambiamos el puerto de escucha, y además solo nos interesa IPv4. Habilitaremos las conexiones desde el exterior a nuestro servicio SSH, corriendo en el puerto TCP 3322, y posteriormente habilitaremos el servicio (aceptaremos la advertencia con y) y consultaremos su estado y reglas:

# ufw allow proto tcp to 0.0.0.0/0 port 3322
# ufw enable
# ufw status

Así, las únicas conexiones entrantes permitidas serán a nuestro puerto SSH. Si quisiésemos solamente admitir conexiones desde una dirección IP determinada, como puede ser nuestra casa, puesto de trabajo o una máquina de gestión, podríamos sustituir el valor 0.0.0.0/0 (que indica cualquier dirección IPv4) por la dirección correspondiente.

Para borrar una regla escribiremos el comando:

# ufw delete <rule>

Donde <rule> será la regla completa escrita anteriormente. Si usamos el comando ufw status numbered, podremos ver las reglas numeradas, y será posible usar el número de la regla en el comando para eliminarla sin tener que reescribirla.

# ufw status numbered
# ufw status <number>

Podríamos repetir la operativa agregando la regla correspondiente al puerto de cualquier servicio que tengamos corriendo en la máquina. Por ejemplo, si tenemos un GitLab en el puerto 80 podríamos usar:

# ufw allow proto tcp to 0.0.0.0/0 port 80

Normalmente veréis que se utilizan reglas mucho más simples del estilo:

# ufw allow 80/tcp

Esto es una simplificación que permite tanto TCPv4 como TCPv6 desde cualquier lugar. De nuevo, preferiremos ser lo más concretos posible 🙂 Para más configuraciones y detalles sobre UFW os dejo un buen tutorial de DigitalOcean.

Instalación y configuración de Fail2ban

El último paso que vamos a realizar es la instalación y configuración básica de la herramienta Fail2Ban. Este servicio realiza una inspección constante de los ficheros de log de otros servicios, y realiza acciones en base a ellos, normalmente relacionadas con la restricción de acceso.

En la práctica, utilizaremos Fail2Ban para vigilar los intentos de conexión a través de SSH y bloquear direcciones IP que estén realizando varios intentos de conexión, que podrían significar un ataque de fuerza bruta.

El primer paso como siempre es instalar la herramienta. Podremos hacerlo desde los repositorios de casi cualquier distribución. En Debian utilizaremos apt:

# apt install fail2ban

Podemos comprobar el estado del servicio, en el cual podemos ver también que de forma predeterminada ya está protegiendo el servicio SSH.

# fail2ban-client status

En fail2ban se configuran diversas jails o celdas, las cuales representan una capa de protección alrededor de un servicio en base a la inspección de sus logs. Podemos inspeccionar el estado de cada una de las celdas complementando el comando anterior con el nombre de la misma. En nuestro caso, comprobaremos el estado de la celda sshd que protege nuestro servicio SSH:

# fail2ban-client status sshd

En la misma salida del comando de estado vemos varios elementos.

  • Primero, las líneas Currently failed y Total failed indican el número de direcciones IP con intentos fallidos de inicio de sesión, y el número total de intentos fallidos.
  • File list indica el fichero o ficheros que fail2ban está monitorizando para el servicio.
  • Currently banned y Total banned indican el número de direcciones IP bloqueadas en el momento actual y desde que se inició el servicio.
  • Banned IP list indican las direcciones IP concretas actualmente bloqueadas.

Lo primero que haremos es realizar una copia del fichero de configuración /etc/fail2ban/jail.conf a un nuevo fichero jail.local en el mismo directorio, previniendo que futuras actualizaciones del servicio sobreescriban nuestras configuraciones.

# cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Si editamos el fichero recien creado, veremos las líneas correspondientes a la jail del servicio sshd que queremos proteger, activada de forma predeterminada en Debian (la razón de esto es el fichero /etc/fail2ban/jail.d/defaults-debian.conf). Indica de forma predeterminada el puerto estándar de SSH como puerto. Podemos cambiar éste valor al número de puerto en el que está escuchando nuestro servicio, en este caso el 3322. Podemos dejar los paths de los logs tal como están, dado que usamos los predeterminados.

Siguiendo la configuración del fichero jail.local, localizaremos la línea banaction en la sección [DEFAULT]. Ésta línea establece la acción que se realizará a la hora de efectuar un bloqueo. Las acciones se encuentran en ficheros de configuración en /etc/fail2ban/actions.d.

De forma predeterminada, la acción es iptables-multiport, haciendo uso de IPTables puro. Nosotros, para simplificar la visualización de las reglas, utilizaremos UFW, colocando el valor ufw en esta línea.

Dejaremos sin cambiar la siguiente línea, banaction_allports, que realiza un bloqueo a una IP en todos los puertos, pero que no utilizaremos en nuestro servicio SSH.

Siguiendo con la configuración, cambiaremos las siguientes líneas en la sección [DEFAULT]:

  • La línea bantime representa el número de segundos que un host es bloqueado del servicio. El valor predeterminado es de 600 segundos. Lo estableceremos a 1200 segundos, es decir, dos horas.
  • La línea findtime es el la ventana temporal en la que una dirección IP intenta acceder al servicio de forma errónea el número máximo de veces. Lo estableceremos a 1200 segundos o 2 horas, endureciendo el predeterminado de 600 segundos.
  • La línea maxretry es el número máximo de intentos dentro de la ventana anterior antes de ser baneado, 5 de forma predeterminada. Lo estableceremos en 3 intentos.

Otra opción que puede ser más cómoda es configurar todo esto de una vez y a nivel de jail de SSH únicamente (al fin y al cabo es el servicio a controlar) creando un fichero /etc/fail2ban/jail.d/sshd.conf con el contenido:

[sshd]
enabled = true
port = 3322
banaction = ufw
bantime = 1200
findtime = 1200
maxretry = 5

Podemos cargar la nueva configuración del servicio fail2ban con el siguiente comando:

# systemctl reload fail2ban

Si realizamos varios intentos fallidos de sesión SSH desde otra dirección IP, podremos ver que fail2ban lista dicha dirección IP como bloqueada, y efectivamente ya no podremos acceder desde la misma hasta dentro de dos horas.

Como pequeño dato importante, con el siguiente comando podremos desbloquear manualmente una dirección IP:

# fail2ban-client set <jail> unbanip <ip_address>

Y hasta aquí la configuración manual de la seguridad básica de nuestro VPS 🙂

Automatización del proceso

Por último, podemos hacer que todo lo anterior se ejecute de forma automatizada cuando creemos la máquina. Recogeremos los pasos anteriores dentro de un script y lo ejecutaremos de forma remota en nuestra máquina virtual.

Para ello, he creado el siguiente Gist:

Podréis ejecutarlo con el siguiente comando, contando con que tenéis una máquina virtual Debian que queréis configurar y que tenéis acceso mediante root a la misma con clave SSH:

$ ssh -t -i <private_key> root@<vps_address> 'wget --no-cache https://gist.githubusercontent.com/hartek/c9af1c4246bef5228f48ca8c9d51bdb8/raw/d5070c09988c5673ea06bcd6890e8a15a2ddc255/secure_vps.sh -O secure_vps.sh && bash secure_vps.sh'

En la ejecución veréis cómo se van instalando los paquetes necesarios y cómo va configurando los servicios (haciendo uso también del gist de ssh_config que vimos más atrás). Se os pedirá la contraseña de root a establecer y el puerto SSH que queráis utilizar para el servicio. Una vez termine, podréis acceder al VPS con el usuario debian y la misma clave privada que ya teníais. ¡Importante usar el parámetro -t de SSH para utilizar una Shell interactiva y que funcione bien la entrada por teclado!

¡Eso es todo! Espero que os haya gustado y os sirva para aprender 🙂 Un saludo!!