Introducción
Desplegar componentes discretos en la configuración de su aplicación en diferentes nodos es una forma común de reducir la carga y comenzar a escalar horizontalmente. Un ejemplo típico es configurar una base de datos en un servidor separado de su aplicación. Aunque hay varias ventajas con esta configuración, la conexión a través de una red plantea un nuevo conjunto de preocupaciones de seguridad.
En esta guía, demostraremos cómo configurar un firewall en cada uno de sus servidores en una configuración distribuida. Configuraremos nuestra política para permitir el tráfico previsto entre nuestros componentes, al tiempo que denegamos otro tráfico.
También puede configurar Firewalls en la Nube de DigitalOcean, que se ejecutan como una capa adicional externa a sus servidores en la infraestructura de DigitalOcean. De esta manera, no es necesario configurar un firewall en los propios servidores.
Para la demostración en esta guía, utilizaremos dos servidores Ubuntu 22.04. Uno tendrá una aplicación web servida con Nginx y el otro alojará la base de datos MySQL para la aplicación. Aunque utilizaremos esta configuración como ejemplo, debería poder extrapolar las técnicas involucradas para adaptarse a los requisitos de su propio servidor.
Prerrequisitos
Para empezar, deberás tener dos servidores Ubuntu 22.04 nuevos. Agrega una cuenta de usuario regular con privilegios de sudo
en cada uno. Para hacer esto, sigue nuestra guía de configuración inicial de servidor Ubuntu 22.04.
La configuración de la aplicación que estaremos asegurando se basa en esta guía. Si deseas seguir ese ejemplo, configura tus servidores de aplicación y base de datos según lo indicado en ese tutorial. De lo contrario, puedes utilizar este artículo como referencia general.
Paso 1 — Configuración de un Cortafuegos
Comenzarás implementando una configuración básica de cortafuegos para cada uno de tus servidores. La política que implementaremos adopta un enfoque de seguridad en primer lugar. Bloquearemos casi todo, excepto el tráfico SSH, y luego abriremos agujeros en el cortafuegos para nuestra aplicación específica.
Esta guía sigue la sintaxis de iptables
. iptables
se instala automáticamente en Ubuntu 22.04 utilizando un backend de nftables
, así que no deberías tener que instalar paquetes adicionales.
Usando nano
o tu editor de texto favorito, abre el archivo /etc/iptables/rules.v4
:
- sudo nano /etc/iptables/rules.v4
Pega la configuración de la guía de plantilla de cortafuegos.
*filter
# Permitir todo el tráfico saliente, pero dejar caer los paquetes entrantes y de reenvío por defecto
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
# Cadenas personalizadas por protocolo
:UDP - [0:0]
:TCP - [0:0]
:ICMP - [0:0]
# Tráfico UDP aceptable
# Tráfico TCP aceptable
-A TCP -p tcp --dport 22 -j ACCEPT
# Tráfico ICMP aceptable
# Política de aceptación de plantilla
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
# Dejar caer paquetes inválidos
-A INPUT -m conntrack --ctstate INVALID -j DROP
# Pasar tráfico a cadenas específicas de protocolo
## Solo permitir nuevas conexiones (las establecidas y relacionadas deberían ser manejadas)
## Para TCP, adicionalmente solo permitir nuevos paquetes SYN ya que es el único válido
## método para establecer una nueva conexión TCP
-A INPUT -p udp -m conntrack --ctstate NEW -j UDP
-A INPUT -p tcp --syn -m conntrack --ctstate NEW -j TCP
-A INPUT -p icmp -m conntrack --ctstate NEW -j ICMP
# Rechazar cualquier cosa que haya llegado hasta este punto
## Intentar ser específico del protocolo con un mensaje de rechazo
-A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
-A INPUT -p tcp -j REJECT --reject-with tcp-reset
-A INPUT -j REJECT --reject-with icmp-proto-unreachable
# Confirmar los cambios
COMMIT
*raw
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
*security
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
Guardar y cerrar el archivo. Si estás usando nano
, presiona Ctrl+X
para salir, luego cuando se te pida, Y
y luego Enter.
Si estás implementando esto en un entorno en vivo, no recargues tus reglas de firewall aún. Cargar el conjunto de reglas descrito aquí dejará caer inmediatamente la conexión entre tu aplicación y el servidor de base de datos. Necesitarás ajustar las reglas para reflejar nuestras necesidades operativas antes de recargar.
Paso 2 — Descubre los Puertos Utilizados por tus Servicios
Para permitir la comunicación entre tus componentes, necesitas conocer los puertos de red que se están utilizando. Puedes encontrar los puertos de red correctos examinando tus archivos de configuración, pero un método agnóstico de aplicación para encontrar los puertos correctos es simplemente verificar qué servicios están escuchando conexiones en cada una de nuestras máquinas.
Puedes usar la herramienta netstat
para descubrir esto. Dado que tu aplicación solo se comunica a través de IPv4, agregaremos el argumento -4
, pero puedes quitarlo si también estás utilizando IPv6. Los otros argumentos que necesitas para encontrar tus servicios en ejecución son -p
, -l
, -u
, -n
y -t
, que puedes proporcionar como -plunt
.
Estos argumentos se pueden desglosar de la siguiente manera:
p
: Show the PID and name of the program to which each socket belongs.l
: Show only listening sockets.u
: Show UDP traffic.n
: Show numeric output instead of service names.t
: Show TCP traffic.
- sudo netstat -4plunt
En tu servidor web, tu salida podría verse así:
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1058/sshd
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 4187/nginx
La primera columna resaltada muestra la dirección IP y el puerto en los que el servicio resaltado al final de la línea está escuchando. La dirección especial 0.0.0.0
significa que el servicio en cuestión está escuchando en todas las direcciones disponibles.
En tu servidor de bases de datos, tu salida podría verse así:
- sudo netstat -4plunt
OutputActive Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1097/sshd
tcp 0 0 192.0.2.30:3306 0.0.0.0:* LISTEN 3112/mysqld
Puedes leer estas columnas exactamente de la misma manera. En este ejemplo, la dirección 192.0.2.30
representa la dirección IP privada del servidor de bases de datos. En el tutorial previo, restringiste MySQL a la interfaz privada por razones de seguridad.
Toma nota de los valores que encuentres en este paso. Estos son los detalles de red que necesitarás para ajustar la configuración de tu firewall.
En tu servidor web, necesitas asegurarte de que los siguientes puertos sean accesibles:
- Puerto 80 en todas las direcciones
- Puerto 22 en todas las direcciones (ya considerado en las reglas del firewall)
Tu servidor de base de datos tendría que asegurarse de que los siguientes puertos sean accesibles:
- Puerto 3306 en la dirección
192.0.2.30
(o la interfaz asociada a ella) - Puerto 22 en todas las direcciones (ya considerado en las reglas del firewall)
Paso 3 — Ajustar las Reglas del Firewall del Servidor Web
Ahora que tienes la información de puertos que necesitas, ajustarás el conjunto de reglas del firewall de tu servidor web. Abre el archivo de reglas en tu editor con privilegios de sudo
:
- sudo nano /etc/iptables/rules.v4
En el servidor web, necesitas añadir el puerto 80 a tu lista de tráfico aceptable. Dado que el servidor está escuchando en todas las direcciones disponibles —los servidores web generalmente esperan ser accesibles desde cualquier lugar—, no restringirás la regla por interfaz o dirección de destino.
Tus visitantes web utilizarán el protocolo TCP para conectarse. Tu framework ya tiene una cadena personalizada llamada TCP
para excepciones de aplicaciones TCP. Puedes añadir el puerto 80 a esa cadena, justo debajo de la excepción para tu puerto SSH:
*filter
. . .
# Tráfico TCP aceptable
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 80 -j ACCEPT
. . .
Su servidor web iniciará la conexión con su servidor de base de datos. Su tráfico saliente no está restringido en su firewall y el tráfico entrante asociado con conexiones establecidas está permitido, por lo que no tenemos que abrir ningún puerto adicional en este servidor para permitir esta conexión.
Guarde y cierre el archivo cuando haya terminado. Su servidor web ahora tiene una política de firewall que permitirá todo el tráfico legítimo mientras bloquea todo lo demás.
Pruebe su archivo de reglas en busca de errores de sintaxis:
- sudo iptables-restore -t < /etc/iptables/rules.v4
Si no se muestran errores de sintaxis, recargue el firewall para implementar el nuevo conjunto de reglas:
- sudo service iptables-persistent reload
Paso 4 — Ajustar las Reglas del Firewall del Servidor de Base de Datos
En su servidor de base de datos, necesita permitir el acceso al puerto 3306
en la dirección IP privada de su servidor. En este caso, esa dirección era 192.0.2.30
. Puede limitar el acceso destinado específicamente a esta dirección, o puede limitar el acceso haciendo coincidir con la interfaz a la que se asigna esa dirección.
Para encontrar la interfaz de red asociada con esa dirección, ejecute ip -4 addr show scope global
:
- ip -4 addr show scope global
Output2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 203.0.113.5/24 brd 104.236.113.255 scope global eth0
valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 192.0.2.30/24 brd 192.0.2.255 scope global eth1
valid_lft forever preferred_lft forever
Las áreas resaltadas muestran que la interfaz eth1
está asociada con esa dirección.
A continuación, ajustará las reglas del firewall en el servidor de base de datos. Abra el archivo de reglas con privilegios de sudo
en su servidor de base de datos:
- sudo nano /etc/iptables/rules.v4
Una vez más, agregarás una regla a nuestra cadena TCP
para formar una excepción para la conexión entre tus servidores web y de base de datos.
Para restringir el acceso basado en la dirección actual en cuestión, agregarías la regla de esta manera:
*filter
. . .
# Tráfico TCP aceptable
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -d 192.0.2.30 -j ACCEPT
. . .
Si prefieres permitir la excepción basada en la interfaz que alberga esa dirección, puedes agregar una regla similar a esta en su lugar:
*filter
. . .
# Tráfico TCP aceptable
-A TCP -p tcp --dport 22 -j ACCEPT
-A TCP -p tcp --dport 3306 -i eth1 -j ACCEPT
. . .
Guarda y cierra el archivo cuando hayas terminado.
Verifica errores de sintaxis con este comando:
- sudo iptables-restore -t < /etc/iptables/rules.v4
Cuando estés listo, recarga las reglas del firewall:
- sudo service iptables-persistent reload
Ahora ambos de tus servidores deberían estar protegidos sin restringir el flujo necesario de datos entre ellos.
Conclusión
Implementar un firewall adecuado siempre debería ser parte de tu plan de implementación al configurar una aplicación. Aunque demostramos esta configuración usando dos servidores con Nginx y MySQL, las técnicas demostradas arriba son aplicables independientemente de tus opciones tecnológicas específicas.
Para aprender más sobre firewalls e iptables
específicamente, echa un vistazo a las siguientes guías: