Proxy Reverso OpenResty(Nginx) + CrowdSec + Cloudflare + Ubuntu 22.04
Referencias:
- crowdsec.net
- openresty.org
- cloudflare.com
Actualizar e Instalar dependencias
Como base usamos Ubuntu 22.04 LTS
user@reverso:~$ sudo apt update && sudo apt upgrade -y
Instalar OpenResty desde repositorios oficiales
Requerimientos previos
user@reverso:~$ sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates vim pgp
Importar llave PGP
user@reverso:~$ wget -O - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg
Agregar repositorio para Ubuntu 22
user@reverso:~$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list > /dev/null
Actualizar e instalar
user@reverso:~$ sudo apt-get update && sudo apt-get -y install openresty
Instalar CrowdSec
Crear una cuenta en crowdsec.net y realizar el primero y ultimo paso para agregar una nueva instancia

Comandos
user@reverso:~$ curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
user@reverso:~$ sudo apt-get install crowdsec
user@reverso:~$ sudo cscli console enroll XXXXXXXXXXXXXXX
Instalar OpenResty Bouncer de CrowdSec
user@reverso:~$ wget https://github.com/crowdsecurity/cs-openresty-bouncer/releases/download/v0.1.10/crowdsec-openresty-bouncer.tgz
user@reverso:~$ tar xvzf crowdsec-openresty-bouncer.tgz
user@reverso:~$ cd crowdsec-openresty-bouncer-v*/
user@reverso:~/crowdsec-openresty-bouncer-v0.1.10$ sudo ./install.sh
Configurar OpenResty para utilizar CrowdSec
Incluir en la seccion HTTP del archivo "/usr/local/openresty/nginx/conf/nginx.conf" el siguiente texto:
include /usr/local/openresty/nginx/conf/conf.d/crowdsec_openresty.conf;
Reiniciar OpenResty
user@reverso:~$ sudo systemctl restart openresty.service
Volver a la web y aceptar la instancia nueva

Configurar OpenResty a la Nginx way
Hay algunas cosas que tenemos que configurar manualmente, no tanto como si lo hubieramos compilado desde cero, por ejemplo el script de systemd
para iniciar o detener el servicio ya se encuetra creado al configurar el repositorio oficial que tiene los .deb actualizados a la par de repositorio o casi 😈, pero para hacerlo lo mas parecido a lo que estamos acostumbrados en una instalacion de Nginx -comun- queremos en su lugar cosas como las carpetas de los Vhost y logs
Usuario www-data, worker_prosses y pid
Editar el archivo /usr/local/openresty/nginx/conf/nginx.conf
user www-data;
worker_processes auto;
pid /usr/local/openresty/nginx/logs/nginx.pid;
Crear carpeta de logs
user@reverso:~$ sudo mkdir /var/log/openresty
Configurar seccion HTTP
Verificar o agregar los siguientes valores
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
access_log /var/log/openresty/access.log;
error_log /var/log/openresty/error.log;
gzip on;
gzip_disable "msie6";
include ../sites/*;
💂Por probelmas de seguridad dejo solo 1.2 y 1.3 en la seccion de ssl_protocols, dejo a modo de documentacion los protocolos posibles TLSv1 TLSv1.1 TLSv1.2 TLSv1.3
Crear carpeta para para Vhost
user@reverso:~$ sudo mkdir /usr/local/openresty/nginx/sites
Crear archivo default
/usr/local/openresty/nginx/sites/default.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
root /usr/local/openresty/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;
location = /error.html {
root /usr/local/openresty/nginx/html;
}
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
include /usr/local/openresty/nginx/snippets/self-signed.conf;
include /usr/local/openresty/nginx/snippets/ssl-params.conf;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
root /usr/local/openresty/nginx/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;
location = /error.html {
root /usr/local/openresty/nginx/html;
}
}
🔥Atencion, es importante conservar en cada Vhost resolver 8.8.8.8 8.8.4.4 valid=300s;
y resolver_timeout 5s;
Crear archivo de error generico error.html
Una de las mejores practicas es eliminar toda informacion sobre el software que estamos usando, es epecial la version, por eso vamos a armar un pagina de error personalizada, crear el archivo /usr/local/openresty/nginx/html/error.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
@charset "UTF-8";h1,p{margin-top:0}.container,.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0}.row,body{display:flex}.main,.sub{text-align:center!important}:root{--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg,rgba(255, 255, 255, 0.15),rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-2xl:2rem;--bs-border-radius-pill:50rem;--bs-link-color:#0d6efd;--bs-link-hover-color:#0a58ca;--bs-code-color:#d63384;--bs-highlight-bg:#fff3cd}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}.main,h1{font-weight:500;line-height:1.2}h1{margin-bottom:.5rem;font-size:calc(1.375rem + 1.5vw)}p{margin-bottom:1rem}a{color:var(--bs-link-color);color:#277dfd;text-decoration:none}a:hover{color:var(--bs-link-hover-color)}.container,.row>*{width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5)}.container{margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){h1{font-size:2.5rem}.container{max-width:1140px}}@media (min-width:1400px){.container{max-width:1320px}}.row{flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;max-width:100%;margin-top:var(--bs-gutter-y)}.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.w-100{width:100%!important}.justify-content-center{justify-content:center!important}.m-auto{margin:auto!important}body,html{height:100%}body{align-items:center;background-color:#fbfbfb}.main{font-size:2.1rem;padding-bottom:.8rem!important;margin-bottom:0;padding-top:.2rem!important}.sub{font-size:1rem;font-weight:300;color:#000000a8}
</style>
<main class="w-100 m-auto">
<div class="container">
<div class="row justify-content-center">
<h1 class="main">ERROR</h1>
<p class="sub">Se encontro un error</p>
</div>
</div>
</main>
</body>
</html>
⭐ La pagina siguiente fue basada en el ejemplo https://codepen.io/corbpie/pen/KKRwGPM
CatchAll index.html
Algo bien util es tener una pagina que conteste de manera correcta cualquier colicitud de forma directa para poder detectar que reverso esta contestando, claramente manteniendo anonimato de toda informacion posiblemente sensible, no publique por ejmplo el nombre del servidor/hostname pero si podemos publciar que el que esta contestando es el "Reverso A" por ejmplo, sin dar mucha informacion, esta practica es realmente util si tenemos varios reversos en entornos de alta disponibilidad y al debugear necesitamos algo de informacion para no volvernos locos ante un problema.
Modificar el archivo index.html de alguna forma, vamos a volver a utilziar el formato para mostrar errores pero cambiando los textos /usr/local/openresty/nginx/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
@charset "UTF-8";h1,p{margin-top:0}.container,.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0}.row,body{display:flex}.main,.sub{text-align:center!important}:root{--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg,rgba(255, 255, 255, 0.15),rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-2xl:2rem;--bs-border-radius-pill:50rem;--bs-link-color:#0d6efd;--bs-link-hover-color:#0a58ca;--bs-code-color:#d63384;--bs-highlight-bg:#fff3cd}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}.main,h1{font-weight:500;line-height:1.2}h1{margin-bottom:.5rem;font-size:calc(1.375rem + 1.5vw)}p{margin-bottom:1rem}a{color:var(--bs-link-color);color:#277dfd;text-decoration:none}a:hover{color:var(--bs-link-hover-color)}.container,.row>*{width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5)}.container{margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){h1{font-size:2.5rem}.container{max-width:1140px}}@media (min-width:1400px){.container{max-width:1320px}}.row{flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;max-width:100%;margin-top:var(--bs-gutter-y)}.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.w-100{width:100%!important}.justify-content-center{justify-content:center!important}.m-auto{margin:auto!important}body,html{height:100%}body{align-items:center;background-color:#fbfbfb}.main{font-size:2.1rem;padding-bottom:.8rem!important;margin-bottom:0;padding-top:.2rem!important}.sub{font-size:1rem;font-weight:300;color:#000000a8}
</style>
<main class="w-100 m-auto">
<div class="container">
<div class="row justify-content-center">
<h1 class="main">REVERSO</h1>
<p class="sub">Primario</p>
</div>
</div>
</main>
</body>
</html>
Eliminar la seccion server
Eliminar la seccion server{
del archivo /usr/local/openresty/nginx/conf/nginx.conf
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
Generar cerfiticados y SSL autofirmado
asegurar dhparam
Este comando puede tardar mucho, a tomar cafe ☕
user@reverso:~$ sudo openssl dhparam -out /etc/ssl/private/dhparam.pem 4096
Generar SSL autofirmado
user@reverso:~$ sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt
Crear carpeta para snippets
user@reverso:~$ sudo mkdir /usr/local/openresty/nginx/snippets/
Archivo /usr/local/openresty/nginx/snippets/self-signed.conf
Crear el archivo con las siguientes dos lineas
ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
Archivo /usr/local/openresty/nginx/snippets/ssl-params.conf
Crear el archivo con las siguientes dos lineas
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/private/dhparam.pem;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# Disable strict transport security for now. You can uncomment the following
# line if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
Configurar llaves reCaptcha de Google
Desde el panel de google o tambien puede ser desde google cloud hay 1millon de consultas por mes gratis

Completar los campos
- Etiqueta
- reCaptcha V2
- Dominios (completar todos los dominios
⭐ Atencion, en el ejemplo puse LOCALHOST pero no es recomendable dejar estos dominios, puede utilizar llaves de prueba o bien tener dos cuentas de reCaptcha, una para el entorno de pruebas y otro para produccion.
- Aceptar las condiciones del servicio de reCAPTCHA
Completar llaves en OpenResty Bouncer

Completar las llaves en el archivo /etc/crowdsec/bouncers/crowdsec-openresty-bouncer.conf
y completar los campos SECRET_KEY
y SITE_KEY
# ReCaptcha Secret Key
SECRET_KEY=COMPLETAR_CON_LLAVE_VERDE
# Recaptcha Site key
SITE_KEY=COMPLETAR_CON_LLAVE_ROJA
Reiniciar y probar
user@reverso:~$ sudo systemctl restart openresty
Documentacion de Referencia
https://www.digitalocean.com/community/tutorials/how-to-use-the-openresty-web-framework-for-nginx-on-ubuntu-16-04