Accesibilidad externa a los servicios (Proxy inverso, WAF, etc...)
Introducción
En torno a la accesibilidad externa a los servicios (proxy inverso, WAF, etc.), ha sido realizado a través del software Docker, un software de código abierto que nos automatizará los despliegues de aplicaciones/servicios dentro de contenedores de software que nos ayudará a conseguir una mayor seguridad y escalabilidad en nuestros servicios, dado que, aplicaremos una capa adicional de abstracción que ocultará los detalles de implementación de ciertas funcionalidades y una automatización de virtualización de aplicaciones/servicios de múltiples sistemas operativos existentes a día de hoy.
Explicación de la implementación de los servicios
Aclarado esto, en nuestro caso conseguiremos implementar los servicios de proxy inverso, https, la web de higiene postural y la base de datos encargada de guardar la información de nuestra web a partir de un docker-compose que nos automatizará toda la configuración de estos servicios a partir de unas imágenes oficiales y configuraciones que conseguimos adicionar. En cuanto a la implementación del WAF, deberemos instalarlo y configurarlo de manera manual en nuestro propio contenedor de Nginx una vez ejecutemos nuestro docker-compose, ya que, no pudimos automatizar dicho servicio a partir de un Dockerfile, debido a que por cada versión diferente de Nginx que se disponga, los argumentos a añadir al módulo de ModSecurity son totalmente diferentes y varían entre ellos. De todas formas, no os preocupéis, puesto que detallaremos un punto con todo el proceso de instalación y configuración que deberemos realizar para conseguir aplicar el WAF en nuestro servidor web Nginx sin importar la versión que se tenga.
Implementación de los servicios
Proxy inverso, HTTPS, WordPress y base de datos
Una vez tengamos toda esta explicación en mente, procederemos a pasar a implementar todos estos servicios comentados. Como dijimos anteriormente, el proxy inverso, el https, la web de he higiene postural y la base de datos los pondremos en marcha a partir de un docker-compose. Por dicha razón, deberemos tener instalado antes los paquetes docker, docker.io y docker-compose en nuestro sistema operativo para empezar con todo este proceso. En este caso, dicha implementación se realizará en el sistema operativo Ubuntu 20.04, por lo cual si utilizamos un sistema operativo diferente, deberemos adaptar todos los comandos que visualicemos a nuestro entorno. Comentado esto, empezaremos instalando los paquetes que dijimos anteriormente para poder iniciar con nuestra implementación:
sudo apt update && apt-get install docker docker.io docker-compose -y
Posteriormente, deberemos descargar o copiar el docker-compose y el Dockerfile que nos automatizará los servicios que deseamos. Si queremos descargarlo directamente en nuestro sistema operativo y evitarnos copiar cada uno de los ficheros, podremos ejecutar el siguiente comando que veremos a continuación:
¡NOTA! ---> Recordad que deberéis tener antes el paquete git instalado en vuestro sistema operativo. Para instalarlo deberéis ejecutar el comando apt-get install git -y
git clone https://github.com/AdrianGoFe/Nginx_Reverse_Proxy.git
Luego, tendremos que abrir el fichero docker-compose.yml con el editor de texto nano:
nano docker-compose.yml
Y modificar las siguientes líneas con nuestros datos correspondientes (podremos conseguir dominios y subdominios de manera gratuita durante 1 año en Freenom):
version: '3.3'
services:
nginx_proxy:
build:
context: .
dockerfile: Dockerfile
container_name: nginx_proxy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
- conf-nginx:/etc/nginx
- pers-app:/app
- pers-opt:/opt
- pers-usr-local:/usr/local
networks:
- web
letsencrypt-proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
environment:
DEFAULT_EMAIL: introduce_tu_correo_electrónico
NGINX_PROXY_CONTAINER: nginx_proxy
networks:
- web
wordpress:
container_name: wordpress
image: wordpress:5.9.2
restart: unless-stopped
expose:
- 443
secrets:
- db_user
- db_password
- db_name
environment:
WORDPRESS_DB_HOST: mysql_db
WORDPRESS_DB_USER_FILE: /run/secrets/db_user
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
WORDPRESS_DB_NAME_FILE: /run/secrets/db_name
VIRTUAL_HOST: introduce_tu_dominio, introduce_tus_subdominios, introduce_tus_subdominios
LETSENCRYPT_HOST: introduce_tu_dominio, introduce_tus_subdominios, introduce_tus_subdominios
LETSENCRYPT_EMAIL: introduce_tu_correo_electrónico
volumes:
- wordpress:/var/www/html
depends_on:
- mysql_db
networks:
- web
mysql_db:
container_name: mysql
image: mysql:5.7.17
restart: unless-stopped
secrets:
- db_user
- db_password
- db_name
- db_password_root
environment:
MYSQL_DATABASE_FILE: /run/secrets/db_name
MYSQL_USER_FILE: /run/secrets/db_user
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password_root
volumes:
- mysql_db:/var/lib/mysql
networks:
- web
secrets:
db_user:
file: introduce_la_ruta_de_tu.txt
db_password:
file: introduce_la_ruta_de_tu.txt
db_name:
file: introduce_la_ruta_de_tu.txt
db_password_root:
file: introduce_la_ruta_de_tu.txt
volumes:
wordpress:
mysql_db:
letsencrypt-certs:
letsencrypt-vhost-d:
letsencrypt-html:
conf-nginx:
pers-app:
pers-opt:
pers-usr-local:
networks:
web:
external: true
Sí en caso contrario deseamos copiar cada uno de los ficheros, deberemos crear los ficheros docker-compose y Dockerfile de la siguiente forma para que en un futuro no tengamos problemas en levantar nuestros servicios:
touch docker-compose.yml Dockerfile
Seguidamente, cuando tengamos dichos ficheros creados correctamente, procederemos a abrirlos uno a uno con el editor de texto nano y copiaremos en cada uno de ellos su contenido correspondiente (como en el caso anterior, tened en cuenta las líneas del docker-compose.yml que deberemos modificar para que todo funcione correctamente):
1. nano Dockerfile
Dockerfile
FROM jwilder/nginx-proxy
RUN apt-get update && apt-get install nano git bison build-essential ca-certificates curl dh-autoreconf doxygen \
flex gawk git iputils-ping libcurl4-gnutls-dev libexpat1-dev libgeoip-dev liblmdb-dev \
libpcre3-dev libpcre++-dev libssl-dev libtool libxml2 libxml2-dev libyajl-dev locales \
lua5.3-dev pkg-config wget zlib1g-dev libgd-dev libxslt-dev -y
WORKDIR /app/
2. nano docker-compose.yml
docker-compose.yml
version: '3.3'
services:
nginx_proxy:
build:
context: .
dockerfile: Dockerfile
container_name: nginx_proxy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
- conf-nginx:/etc/nginx
- pers-app:/app
- pers-opt:/opt
- pers-usr-local:/usr/local
networks:
- web
letsencrypt-proxy:
image: jrcs/letsencrypt-nginx-proxy-companion
container_name: letsencrypt-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- letsencrypt-certs:/etc/nginx/certs
- letsencrypt-vhost-d:/etc/nginx/vhost.d
- letsencrypt-html:/usr/share/nginx/html
environment:
DEFAULT_EMAIL: introduce_tu_correo_electrónico
NGINX_PROXY_CONTAINER: nginx_proxy
networks:
- web
wordpress:
container_name: wordpress
image: wordpress:5.9.2
restart: unless-stopped
expose:
- 443
secrets:
- db_user
- db_password
- db_name
environment:
WORDPRESS_DB_HOST: mysql_db
WORDPRESS_DB_USER_FILE: /run/secrets/db_user
WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password
WORDPRESS_DB_NAME_FILE: /run/secrets/db_name
VIRTUAL_HOST: introduce_tu_dominio, introduce_tus_subdominios, introduce_tus_subdominios
LETSENCRYPT_HOST: introduce_tu_dominio, introduce_tus_subdominios, introduce_tus_subdominios
LETSENCRYPT_EMAIL: introduce_tu_correo_electrónico
volumes:
- wordpress:/var/www/html
depends_on:
- mysql_db
networks:
- web
mysql_db:
container_name: mysql
image: mysql:5.7.17
restart: unless-stopped
secrets:
- db_user
- db_password
- db_name
- db_password_root
environment:
MYSQL_DATABASE_FILE: /run/secrets/db_name
MYSQL_USER_FILE: /run/secrets/db_user
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password_root
volumes:
- mysql_db:/var/lib/mysql
networks:
- web
secrets:
db_user:
file: introduce_la_ruta_de_tu.txt
db_password:
file: introduce_la_ruta_de_tu.txt
db_name:
file: introduce_la_ruta_de_tu.txt
db_password_root:
file: introduce_la_ruta_de_tu.txt
volumes:
wordpress:
mysql_db:
letsencrypt-certs:
letsencrypt-vhost-d:
letsencrypt-html:
conf-nginx:
pers-app:
pers-opt:
pers-usr-local:
networks:
web:
external: true
Una vez que tengamos nuestros ficheros docker-compose.yml y Dockerfile listos y bien preparados para poder arrancar nuestros servicios, sólo nos quedara ejecutar el comando encargado de ello:
sudo docker-compose up -d --build
Finalmente, solamente nos quedará visualizar si nuestros contenedores han sido levantados correctamente y sí en uno de nuestros navegadores web favoritos buscando nuestro dominio accedemos correctamente a nuestra web de higiene postural con su candadito HTTPS:
sudo docker ps -a
Y como vemos, si hemos seguido todos los pasos correctamente, nuestros servicios funcionarán con éxito y sin problemas. ¡ENHORABUENA!
WAF
Como ya hemos visto, la implementación de nuestros servicios proxy inverso, HTTPS, la web de higiene postural y su base de datos ha sido algo bastante sencillo y rápido, pero, recordemos que nos falta un elemento muy importante en nuestro servidor web Nginx para que nuestra web contenga una mayor seguridad en la parte del backend. Y sí, esa herramienta es la herramienta WAF (Web Application Firewall), herramienta encargada de garantizar la seguridad del servidor web mediante el análisis de paquetes de petición HTTP/HTTPS y modelos de tráfico. Consiguiendo así, que nuestra página web evite ataques de SQL Injection, Cross-Site Request, entre otros.
¡NOTA! ---> En nuestro caso, para la instalación de este WAF, hemos tenido que realizar un SWAP en nuestra máquina Azure para poder conseguir compilarlo. Para aplicar un SWAP en nuestra máquina Azure podemos seguir este breve tutorial de aquí.
Vale, muy bien, ¿y cómo se instala esta herramienta en nuestro servidor web Nginx?
Pues bien, te lo explicaré a continuación con unos pasos bastante sencillos y breves, dado que, se trata de un proceso bastante largo y podría resultar algo tedioso. Pero no te preocupes, será divertido, mira como se hace:
1) En primer lugar, ejecutaremos en nuestro sistema el comando sudo docker ps -a para visualizar el ID de nuestro contenedor Nginx:
2) Posteriormente, accederemos a dicho contenedor con el siguiente comando:
sudo docker exec -it <id de nuestro contenedor> /bin/bash
3) Cuando nos encontremos dentro del contenedor de nuestro servidor web Nginx, procederemos a dirigirnos al directorio /opt y descargaremos la herramienta WAF a partir de un repositorio de GitHub:
1. cd /opt
2. git clone https://github.com/SpiderLabs/ModSecurity
4) Finalizada la descarga de la herramienta WAF, deberemos acceder al directorio ModSecurity que se nos generará después de descargarlo e inicializar el repositorio Git:
1. cd ModSecurity
2. git submodule init
5) Luego, actualizaremos dicho repositorio con:
git submodule update
6) Después, ejecutaremos los scripts build.sh y configure.sh encargados de construirnos y configurarnos correctamente la compilación de la aplicación WAF:
1. ./build.sh
2. ./configure.sh
7) Ulteriormente, procederemos a compilar la aplicación WAF con el comando make (este proceso puede tarda alrededor de 20 minutos a 1 hora):
make
7) Una vez finalice la compilación de la aplicación WAF, procederemos a instalarla con el comando siguiente (tardará unos breves minutos):
make install
8) Cuando ya tengamos instalada la aplicación WAF sin problemas, nos dirigiremos nuevamente al directorio /opt y descargaremos el conector de Nginx y ModSecurity para que más adelante nuestras reglas funcionen correctamente. Para ello, ejecutaremos los siguientes comandos:
(En la línea marcada podremos sustituir la versión 1.21.6 por la versión que vosotros dispongáis. Para ver vuestra versión de Nginx ejecutad el comando nginx -v)
1. cd ..
2. git clone https://github.com/SpiderLabs/ModSecurity-nginx
3. wget http://nginx.org/download/nginx-1.21.6.tar.gz
9) Seguidamente, deberemos descomprimir el conector de Nginx:
(Sustituid mi versión de Nginx por la que tengáis vosotros)
tar -xvzmf nginx-1.21.6.tar.gz
10) Después, nos dirigiremos al directorio que nos ha generado la descompresión de nuestro conector Nginx y veremos sus argumentos con nginx -V:
(Sustituid mi versión de Nginx por la que tengáis vosotros)
1. cd nginx-1.21.6
2. nginx -V
11) Una vez nos aparezcan los argumentos de nuestro servidor web Nginx, los seleccionaremos con nuestro ratón justo después de configure arguments: hasta al final:
12) Seleccionados todos los argumentos que nos aparecen de nuestro servidor web Nginx, procederemos a configurarlos a partir de nuestro conector ModSecurity para que nuestro WAF funcione a partir de nuestro servidor (este proceso también tardará unos breves minutos):
./configure <pegar los argumentos seleccionados> --add-dynamic-module=../ModSecurity-nginx
13) Justo después de que termine la configuración de los argumentos de nuestro servidor web Nginx, pasaremos a compilar los módulos de su conector con:
make modules
14) Cuando finalice la compilación de los módulos del conector de Nginx, crearemos el directorio modsecurity en /etc/nginx en el cual guardaremos posteriormente el módulo de seguridad HTTP/HTTPS:
mkdir /etc/nginx/modsecurity
15) Creado el directorio modsecurity, copiaremos el módulo de seguridad HTTP/HTTPS justo al directorio que acabamos de crear:
cp objs/ngx_http_modsecurity_module.so /etc/nginx/modsecurity
16) Posteriormente, abriremos la configuración de nuestro servidor web Nginx con el editor de texto nano, e incluiremos dicho módulo que acabamos de copiar en el directorio modsecurity, más una línea de configuración que nos permitirá que en nuestra página web tengamos un tamaño mayor permitido de cuerpo en la solicitud del cliente que nos servirá para subir temas, entre otros (por defecto es 1MB):
nano /etc/nginx/nginx.conf
¡ADVERTENCIA! ---> Una vez acabéis de editar vuestro archivo de configuración de Nginx, no olvidéis de guardarlo con la combinación de teclas Control + O y salir de dicho archivo con Control + X.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
load_module /etc/nginx/modsecurity/ngx_http_modsecurity_module.so;
events {
worker_connections 10240;
}
http {
client_max_body_size 100M;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
daemon off;
17) En el momento que acabemos de editar nuestro archivo de configuración Nginx, procederemos a descargar todas las reglas que le aplicaremos a nuestro WAF. Para ello, nos dirigiremos nuevamente al directorio /opt y descargaremos dichas reglas a partir de un nuevo Git:
1. cd /opt
2. git clone https://github.com/coreruleset/coreruleset modsecurity-crs
18) Seguidamente, cuando hayamos descargado las reglas para nuestro WAF, veremos que se nos aparecerá un nuevo directorio llamado modsecurity-crs, el cual nos deberemos dirigir con el comando cd y cambiarle el nombre a su configuración crs-setup.conf.example por crs-setup.conf. Para realizar todo esto, ejecutaremos los siguientes comandos:
1. cd modsecurity-crs
2. mv crs-setup.conf.example crs-setup.conf
19) Luego, también cambiaremos el nombre de las reglas REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example por REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf con el comando siguiente:
mv rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
20) Hechos estos dos cambios de nombre, procederemos dirigirnos nuevamente al directorio /opt y moveremos el directorio modsecurity-crs al directorio /usr/local. Para hacer esto, ejecutaremos los comandos que veremos a continuación:
1. cd ..
2. mv modsecurity-crs /usr/local/
21) Justo después, crearemos un directorio persistente llamado modsec en el directorio /etc/nginx y copiaremos el unicode de nuestro ModSecurity en dicho directorio:
1. mkdir -p /etc/nginx/modsec
2. cp /opt/ModSecurity/unicode.mapping /etc/nginx/modsec/
22) Ulteriormente, cuando hayamos copiado el unicode de nuestro ModSecurity en el directorio modsec, iremos al directorio de ModSecurity y le cambiaremos el nombre a su archivo de configuración y lo copiaremos en el directorio modsec. Para ello, ejecutad los siguientes comandos:
1. cd ModSecurity
2. mv modsecurity.conf-recommended modsecurity.conf
3. cp modsecurity.conf /etc/nginx/modsec
23) Copiado el archivo de configuración de ModSecurity en el directorio modsec, lo abriremos con el editor de texto nano y cambiaremos la línea SecRuleEngine DetectionOnly por SecRuleEngine On
nano /etc/nginx/modsec/modsecurity.conf
¡ADVERTENCIA! ---> Recordad no olvidar guardar vuestra configuración con la combinación de teclas Control + O y salir de dicho archivo con Control + X.
----------------- Rule engine initialization ----------------------------------------------
# Enable ModSecurity, attaching it to every transaction. Use detection
# only to start with, because that minimises the chances of post-installation
# disruption.
#
SecRuleEngine On
# -- Request body handling ---------------------------------------------------
# Allow ModSecurity to access request bodies. If you don't, ModSecurity
# won't be able to see any POST parameters, which opens a large security
# hole for attackers to exploit.
#
SecRequestBodyAccess On
# Enable XML request body parser.
# Initiate XML Processor in case of xml content-type
#
SecRule REQUEST_HEADERS:Content-Type "(?:application(?:/soap\+|/)|text/)xml" \
"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML"
24) A posteriori, nos dirigiremos al directorio modsec y crearemos con el editor de texto nano el fichero main.conf con el siguiente contenido:
1. cd /etc/nginx/modsec
2. nano main.conf
Contenido que deberemos copiar en el fichero main.conf
¡ADVERTENCIA! ---> Recordad no olvidar guardar vuestra configuración con la combinación de teclas Control + O y salir de dicho archivo con Control + X.
Include /etc/nginx/modsec/modsecurity.conf
Include /usr/local/modsecurity-crs/crs-setup.conf
Include /usr/local/modsecurity-crs/rules/*.conf
25) Una vez acabemos de crear nuestro fichero main.conf con su respectivo contenido, deberemos dirigirnos al directorio /app y cambiarle el nombre al fichero nginx.tmpl por nginx.tmpl para evitar que nos cambie la configuración de nuestro WAF y virtualhosts cada vez que los contenedores Docker se reinicien:
1. cd /app
2. mv nginx.tmpl nginx.tmpl.bk
26) Realizado este cambio de nombre para no tener futuros problemas, nos dirigiremos a nuestro fichero de configuración de nuestros virtualhosts y aplicaremos las siguientes líneas en todos los apartados llamados server:
Fichero de configuración de mi virtualhost
nano /etc/nginx/conf.d/default.conf
Líneas a añadir
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
¡ADVERTENCIA! ---> Recordad no olvidar guardar vuestra configuración con la combinación de teclas Control + O y salir de dicho archivo con Control + X.
# www.higieneposturalcloudgrupo2.tk
upstream www.higieneposturalcloudgrupo2.tk {
## Can be connected with "web" network
server 172.18.0.5:80;
}
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server_name www.higieneposturalcloudgrupo2.tk;
listen 80 ;
access_log /var/log/nginx/access.log vhost;
# Do not HTTPS redirect Let'sEncrypt ACME challenge
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
auth_request off;
allow all;
root /usr/share/nginx/html;
try_files $uri =404;
break;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server_name www.higieneposturalcloudgrupo2.tk;
listen 443 ssl http2 ;
access_log /var/log/nginx/access.log vhost;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_certificate /etc/nginx/certs/www.higieneposturalcloudgrupo2.tk.crt;
ssl_certificate_key /etc/nginx/certs/www.higieneposturalcloudgrupo2.tk.key;
ssl_dhparam /etc/nginx/certs/www.higieneposturalcloudgrupo2.tk.dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/certs/www.higieneposturalcloudgrupo2.tk.chain.pem;
add_header Strict-Transport-Security "max-age=31536000" always;
include /etc/nginx/vhost.d/default;
location / {
proxy_pass http://www.higieneposturalcloudgrupo2.tk;
}
}
27) Ya casi acabando, reiniciaremos nuestro servidor web Nginx para que se apliquen todos los cambios correctamente:
service nginx restart
28) Y finalmente, ejecutaremos un ataque de SQL Injection y otro de Cross-Site Request para visualizar si nuestro WAF está funcionando correctamente:
Sí, nuestra página web nos redirige a una página como la que vemos en las imágenes anteriores, enhorabuena, significará que nuestro WAF está funcionando con éxito.
Webgrafía
- GitHub | docker-compose y Dockerfile de la implementación de servicios
- Imágen Docker Nginx Proxy Reverse
- Imágen Docker LetsEncrypt (HTTPS)
- Imágen Docker WordPress
- Imágen Docker MySQL
- Dominios gratuitos durante 1 año (Freenom)
- Fotografía esquema WAF
- GitHub ModSecurity
- GitHub Conector ModSecurity
- Conector Nginx 1.21.6
- GitHub Reglas WAF