Cómo instalar Discourse con un WAF de código abierto

No tengo permiso para publicar en la sección de Cómo hacer, así que lo publicaré aquí :slight_smile:

Resumen

El objetivo es agregar un WAF a los recursos existentes manteniendo el rendimiento en una instalación pequeña. En este ejemplo, utilizaremos una única instancia de EC2. Dado que Discourse se ejecuta en Docker, podemos instalar NGINX en el host y usar proxy_pass para redirigir el tráfico hacia nuestro contenedor Docker. También estamos utilizando RDS para PostgreSQL.

Configuración de Discourse

Nuestro primer paso es configurar nuestro archivo app.yml para una instalación personalizada de Discourse. En este ejemplo, ejecutaremos los roles web y redis. No utilizaremos el rol de base de datos ni el de SSL.

También queremos asegurarnos de no exponer los puertos web en el contenedor Docker. La razón es que expondremos nuestra web a través del proxy NGINX en el host.

Ejemplo de archivo app.yml

## este es la plantilla del contenedor Docker Discourse todo en uno, independiente
##
## Después de realizar cambios en este archivo, DEBES reconstruir
## /var/discourse/launcher rebuild app
##
## TEN *MUCHO* CUIDADO AL EDITAR!
## ¡LOS ARCHIVOS YAML SON SUPER SUPER SENSIBLES A ERRORES EN LOS ESPACIOS EN BLANCO O EN LA ALINEACIÓN!
## visita http://www.yamllint.com/ para validar este archivo según sea necesario
templates:
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
## Descomenta estas dos líneas si deseas agregar Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"


## ¿qué puertos TCP/IP debe exponer este contenedor?
## Si deseas que Discourse comparta un puerto con otro servidor web como Apache o nginx,
## consulta https://meta.discourse.org/t/17247 para obtener detalles
expose:
#  - "80:80"   # http
#  - "443:443" # https


params:
  db_default_text_search_config: "pg_catalog.english"
  ## Establece db_shared_buffers a un máximo del 25% de la memoria total.
  ## se establecerá automáticamente por bootstrap según la RAM detectada, o puedes sobrescribirlo
  db_shared_buffers: "768MB"
  ## puede mejorar el rendimiento de ordenación, pero añade uso de memoria por conexión
  #db_work_mem: "40MB"
  ## ¿Qué revisión de Git debe usar este contenedor? (por defecto: tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en
  ## ¿Cuántas solicitudes web concurrentes se admiten? Depende de la memoria y los núcleos de CPU.
  ## se establecerá automáticamente por bootstrap según los CPUs detectados, o puedes sobrescribirlo
  UNICORN_WORKERS: 2

  ## TODO: El nombre de dominio al que responderá esta instancia de Discourse
  ## Requerido. Discourse no funcionará con una dirección IP desnuda.
  DISCOURSE_HOSTNAME: cloudforums.net

  ## Descomenta si deseas que el contenedor se inicie con el mismo
  ## nombre de host (opción -h) que se especificó anteriormente (por defecto "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: Lista de correos electrónicos separados por comas que serán administradores y desarrolladores
  ## en el registro inicial, ejemplo 'usuario1@dominio.com,usuario2@dominio.com'
  DISCOURSE_DEVELOPER_EMAILS: 'tuemail@dominio.com'

  ## TODO: El servidor de correo SMTP utilizado para validar nuevas cuentas y enviar notificaciones
  ## Se requieren dirección SMTP, nombre de usuario y contraseña
  ## ¡ADVERTENCIA: el carácter '#' en la contraseña SMTP puede causar problemas!
  DISCOURSE_SMTP_ADDRESS: TU_SMTP
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: TU_USUARIO_SMTP
  DISCOURSE_SMTP_PASSWORD: TU_CONTRASEÑA_SMTP
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (opcional, por defecto true)

  ## Si agregaste la plantilla de Lets Encrypt, descomenta a continuación para obtener un certificado SSL gratuito
  #LETSENCRYPT_ACCOUNT_EMAIL: me@ejemplo.com

  DISCOURSE_DB_SOCKET: ''
  DISCOURSE_DB_USERNAME: TU_USUARIO
  DISCOURSE_DB_PASSWORD: TU_CONTRASEÑA
  DISCOURSE_DB_HOST: TU_HOST

  ## La dirección CDN http o https para esta instancia de Discourse (configurada para extraer)
  ## consulta https://meta.discourse.org/t/14857 para obtener detalles
  #DISCOURSE_CDN_URL: https://discourse-cdn.ejemplo.com
## El contenedor Docker es sin estado; todos los datos se almacenan en /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Los plugins van aquí
## consulta https://meta.discourse.org/t/19157 para obtener detalles
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Cualquier comando personalizado para ejecutar después de la compilación
run:
  - exec: echo "Inicio de comandos personalizados"
  ## Si deseas establecer la dirección de correo electrónico 'De' para tu primer registro, descomenta y cambia:
  ## Después de recibir el primer correo de registro, vuelve a comentar la línea. Solo necesita ejecutarse una vez.
  - exec: rails c "SiteSetting.enable_badge_sql = true"
  - exec: echo "Fin de comandos personalizados"

Comenzar la instalación de Discourse

Clonar el repositorio de Discourse

sudo -u root git clone https://github.com/discourse/discourse_docker.git /var/discours

Copiar app.yml personalizado para la instalación

Puedes ejecutar el siguiente comando para hacerlo por ti. Solo reemplaza el texto “PEGA AQUÍ TU app.yml COMPLETO” con tu archivo app.yml completo.

sudo -u root cat > /var/discourse/containers/app.yml <<\EOF
PEGA AQUÍ TU app.yml COMPLETO
EOF

Ejecutar la configuración de Discourse

Inicia la configuración de Discourse sin preguntas utilizando el siguiente comando.

sudo -u yes "" | /var/discourse/./discourse-setup

Discourse debería tardar de 10 a 15 minutos en instalarse.

Instalación de NGINX, WAF y GEOIP

Ejecuta las actualizaciones e instala los prerequisitos

apt update -y
apt upgrade -y
apt -y install libpcre3-dev libssl-dev unzip build-essential daemon libxml2-dev libxslt1-dev libgd-dev libgeoip-dev zlib1g-dev libpcre3

Instala la base de datos MaxMind DB para poder ejecutar reglas GEO IP basadas en la ubicación. Puedes usar esta base de datos para enrutar o bloquear el tráfico según la ubicación. Nuestros registros de NGINX ahora mostrarán el país del usuario, incluso si no activas el bloqueo geográfico. I

sudo add-apt-repository -y ppa:maxmind/ppa
apt update -y
apt install -y libmaxminddb0 libmaxminddb-dev mmdb-bin

Descargar y extraer NGINX y NAXSI WAF

Nota: Reemplaza NGINX con la versión más reciente

mkdir ~/nginx-waf
wget https://nginx.org/download/nginx-1.16.1.tar.gz -O ~/nginx-waf/nginx.tar.gz
tar xzf ~/nginx-waf/nginx.tar.gz -C ~/nginx-waf
wget https://github.com/nbs-system/naxsi/archive/master.zip -O ~/nginx-waf/waf.zip
unzip ~/nginx-waf/waf.zip -d ~/nginx-waf/

Clonar el módulo GEO IP2 para NGINX

apt install -y git
git clone https://github.com/leev/ngx_http_geoip2_module.git /etc/ngx_http_geoip2_module

Compilar NGINX

Crearemos un script para hacer esto por nosotros y lo ejecutaremos a continuación.


cat > ~/nginx-waf/nginx-1.16.1/install.sh <<\EOF
cd ~/nginx-waf/nginx-1.16.1/
./configure --conf-path=/etc/nginx/nginx.conf --add-module=../naxsi-master/naxsi_src/ --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --user=www-data --group=www-data --with-http_ssl_module --without-mail_pop3_module --without-mail_smtp_module --without-mail_imap_module --without-http_uwsgi_module --add-dynamic-module=/etc/ngx_http_geoip2_module --without-http_scgi_module --prefix=/usr
make
make install
EOF

sh ~/nginx-waf/nginx-1.16.1/install.sh

Crear reglas del firewall

La configuración predeterminada a continuación activará el WAF y bloqueará las solicitudes maliciosas. Si deseas ejecutar el WAF en modo de aprendizaje, puedes hacerlo agregando Modo de aprendizaje al archivo de reglas a continuación.

cp ~/nginx-waf/naxsi-master/naxsi_config/naxsi_core.rules /etc/nginx/



cat > /etc/nginx/naxsi.rules <<\EOF
SecRulesEnabled;
DeniedUrl "/RequestDenied";
## Verificar reglas de Naxsi
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
EOF

Crear archivo de configuración de NGINX

Por defecto, en este archivo de configuración, el bloqueo geográfico está comentado, pero puedes descomentar y activarlo si lo deseas.

Nota: Debemos ejecutar NGINX SIN SSL al principio. Porque necesitamos que certbot vea un dominio activo. Obtendremos SSL en un paso posterior y en realidad copiaremos un nuevo archivo de configuración de NGINX para reemplazar este uno

Nota: Reemplaza cloudforums.net con tu nombre de dominio

cat > /etc/nginx/nginx.conf <<\EOF
#user  nobody;
worker_processes  1;
load_module modules/ngx_http_geoip2_module.so;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    include       /etc/nginx/naxsi_core.rules;
        include     /etc/nginx/conf.d/*.conf;
        include     /etc/nginx/sites-enabled/*;
    
    geoip2 /etc/geo_ip/GeoLite2-Country.mmdb {
        $geoip2_data_country_code source=$remote_addr country iso_code;
        $geoip2_data_country_name source=$remote_addr country names en;
    }  
    log_format  main_geo  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for" '
                          '$geoip2_data_country_code $geoip2_data_country_name';
    
    access_log /var/log/nginx/access.log main_geo;
   
    default_type  application/octet-stream;
    error_log /var/log/nginx/error.log;
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    #gzip  on;
    server {
        listen       80;
        server_name cloudforums.net;
        root /;
 
        location /.well-known/acme-challenge/ {
                root /var/www;
        }
        location / {
            return 301 https://$host$request_uri;
            include /etc/nginx/naxsi.rules;
                root   html;
                index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}
EOF

Crear script upstart para NGINX

Necesitamos crear un script para que el servicio NGINX se inicie.

cat > /etc/init.d/nginx <<\EOF
#! /bin/sh PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin 
 DAEMON=/usr/sbin/nginx 
 NAME=nginx 
 DESC=nginx
 
 test -x $DAEMON || exit 0 
 # Incluir valores predeterminados de nginx si están disponibles 
 if [ -f /etc/nginx ] ; then 
         . /etc/nginx 
 fi
 
 set -e
 
 case "$1" in 
     start)
         echo -n "Iniciando $DESC: " 
         start-stop-daemon --start --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON -- $DAEMON_OPTS 
         echo "$NAME." 
         ;; 
     stop) 
         echo -n "Deteniendo $DESC: " 
         start-stop-daemon --stop --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON 
         echo "$NAME." 
         ;; 
     restart|force-reload) 
         echo -n "Reiniciando $DESC: " 
         start-stop-daemon --stop --quiet --pidfile \ 
             /var/run/nginx.pid --exec $DAEMON 
         sleep 1 start-stop-daemon --start --quiet --pidfile \ 
             /var/run/nginx.pid --exec $DAEMON -- $DAEMON_OPTS 
         echo "$NAME." 
         ;; 
     reload) 
         echo -n "Recargando la configuración de $DESC: " 
         start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON 
         echo "$NAME." 
         ;; 
     *) 
         N=/etc/init.d/$NAME 
         echo "Uso: $N {start|stop|restart|force-reload}" >&2 
         exit 1 
         ;; 
 esac
 
 exit 0
EOF

systemctl daemon-reload

Crear archivo de servicio NGINX

cat > /lib/systemd/system/nginx.service <<\EOF
[Unit]
Description=Un servidor web de alto rendimiento y un servidor proxy inverso
Documentation=man:nginx(8)
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/usr/sbin/nginx -s reload
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target
EOF

Descargar base de datos de países Geo IP

mkdir /etc/geo_ip
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz
gzip -d GeoLite2-Country.mmdb.gz
mv GeoLite2-Country.mmdb /etc/geo_ip/

Habilitar e iniciar NGINX

systemctl stop apache2
systemctl daemon-reload
systemctl enable nginx
systemctl start nginx

Agregar certificado SSL para tu dominio

No olvides reemplazar cloudforums.net con tu dominio.com

mkdir /var/www
apt-get -y update
apt-get -y install letsencrypt
yes "n" | letsencrypt certonly --webroot --agree-tos -w /var/www -d cloudforums.net -m tuemail@dominio.com

Crear nuevo archivo NGINX con reglas SSL

Crea un nuevo archivo NGINX con tu certificado SSL que acabas de descargar.

Nota: cambia cloudforums.net por tu dominio


cat > /etc/nginx/nginx.conf <<\EOF
#user  nobody;
worker_processes  1;
load_module modules/ngx_http_geoip2_module.so;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    include       /etc/nginx/naxsi_core.rules;
        include     /etc/nginx/conf.d/*.conf;
        include     /etc/nginx/sites-enabled/*;
    
    geoip2 /etc/geo_ip/GeoLite2-Country.mmdb {
        $geoip2_data_country_code source=$remote_addr country iso_code;
        $geoip2_data_country_name source=$remote_addr country names en;
    }  
    log_format  main_geo  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for" '
                          '$geoip2_data_country_code $geoip2_data_country_name';
    
    #***********************************************************
    #Descomenta para hacer bloqueo geográfico. Por defecto solo está configurado para EE. UU. en la configuración a continuación
    #***********************************************************
    #map $geoip2_data_country_code $allowed_country {
    #    default no;
    #    US yes;
    # }
    access_log /var/log/nginx/access.log main_geo;
   
    default_type  application/octet-stream;
    error_log /var/log/nginx/error.log;
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    #keepalive_timeout  0;
    keepalive_timeout  65;
    #gzip  on;
    server {
        listen       80;
        server_name  cloudforums.net;
        root /;
 
        location /.well-known/acme-challenge/ {
                root /var/www;
        }
        location / {
            return 301 https://$server_name$request_uri;
            include /etc/nginx/naxsi.rules;
                root   html;
                index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    server {
      listen 443 ssl;  listen [::]:443 ssl;
      server_name cloudforums.net;  
      ssl on;
      ssl_certificate      /etc/letsencrypt/live/cloudforums.net/fullchain.pem;
      ssl_certificate_key  /etc/letsencrypt/live/cloudforums.net/privkey.pem;
      ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
      ssl_protocols TLSv1.2;
      ssl_prefer_server_ciphers on;
      ssl_session_cache shared:SSL:10m;
      add_header Strict-Transport-Security "max-age=63072000;";
      ssl_stapling on;
      ssl_stapling_verify on;
      client_max_body_size 0;
      location / {
        proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
        proxy_set_header Host $http_host;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Real-IP $remote_addr;
       }
    }
    # Servidor HTTPS
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;
    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;
    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;
    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;
    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}
}
EOF

Reiniciar NGINX

systemctl restart nginx

¡Ahora tienes un WAF de código abierto increíble!! :slight_smile:

Publicidad descarada

Asegúrate de unirte a nosotros en cloudforums.net si disfrutaste esta guía. Realmente nos encantaría tenerte aquí. No vendemos nada ni tenemos anuncios. Solo buscamos buenas publicaciones de TI :slight_smile:

Instalación automatizada de NGINX / WAF con un solo clic después de instalar Discourse. No olvides cambiar el nombre de dominio de cloudforums.net por el tuyo.

Simplemente ejecuta

sh discourse_install.sh