Как создать установку Discourse с использованием открытого WAF

Мне не разрешено публиковать сообщения в разделе «Как сделать», поэтому я размещу это здесь :slight_smile:

Обзор

Цель — добавить WAF к существующим ресурсам, сохраняя производительность на небольшой установке. В этом примере мы будем использовать один экземпляр EC2. Поскольку Discourse работает в Docker, мы можем установить NGINX на хосте и использовать прокси-перенаправление (proxy_pass) для передачи трафика в наш контейнер Docker. Мы также используем RDS для PostgreSQL.

Настройка Discourse

Первым шагом является настройка файла app.yml для индивидуальной установки Discourse. В этом примере мы будем запускать роли web и redis. Роль DB и роль SSL использоваться не будут.

Также мы хотим убедиться, что порты web не открыты в контейнере Docker. Причина в том, что мы будем открывать доступ к web через прокси NGINX на хосте.

Пример файла app.yml

## это шаблон автономного контейнера Docker Discourse «все в одном»
##
## После внесения изменений в этот файл вы ОБЯЗАНЫ выполнить пересборку
## /var/discourse/launcher rebuild app
##
## БУДЬТЕ *ОЧЕНЬ* ОСТОРОЖНЫ ПРИ РЕДАКТИРОВАНИИ!
## YAML-ФАЙЛЫ ЧРЕЗВЫЧАЙНО ЧУВСТВИТЕЛЬНЫ К ОШИБКАМ В ПРОБЕЛАХ ИЛИ ВЫРАВНИВАНИИ!
## для проверки файла посещайте http://www.yamllint.com/ по мере необходимости
templates:
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
## Раскомментируйте эти две строки, если хотите добавить Lets Encrypt (https)
  #- "templates/web.ssl.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"


## какие TCP/IP-порты должен открывать этот контейнер?
## Если вы хотите, чтобы Discourse использовал порт совместно с другим веб-сервером, например Apache или nginx,
## см. https://meta.discourse.org/t/17247 для деталей
expose:
#  - "80:80"   # http
#  - "443:443" # https


params:
  db_default_text_search_config: "pg_catalog.english"
  ## Установите db_shared_buffers максимум на 25% от общего объема памяти.
  ## будет установлено автоматически при загрузке в зависимости от обнаруженной оперативной памяти, или вы можете переопределить
  db_shared_buffers: "768MB"
  ## может улучшить производительность сортировки, но увеличивает использование памяти на подключение
  #db_work_mem: "40MB"
  ## Какую ревизию Git должен использовать этот контейнер? (по умолчанию: tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en
  ## Сколько одновременных веб-запросов поддерживается? Зависит от памяти и ядер CPU.
  ## будет установлено автоматически при загрузке в зависимости от обнаруженных процессоров, или вы можете переопределить
  UNICORN_WORKERS: 2

  ## TODO: Доменное имя, на которое будет реагировать этот экземпляр Discourse
  ## Обязательно. Discourse не будет работать с чистым IP-адресом.
  DISCOURSE_HOSTNAME: cloudforums.net

  ## Раскомментируйте, если хотите, чтобы контейнер запускался с тем же
  ## именем хоста (-h option), что указано выше (по умолчанию "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: Список email-адресов через запятую, которые станут администраторами и разработчиками
  ## при первой регистрации, например 'user1@example.com,user2@example.com'
  DISCOURSE_DEVELOPER_EMAILS: 'youremail@domain.com'

  ## TODO: SMTP-сервер, используемый для проверки новых учетных записей и отправки уведомлений
  ## Адрес SMTP, имя пользователя и пароль обязательны
  ## ВНИМАНИЕ: символ '#' в пароле SMTP может вызвать проблемы!
  DISCOURSE_SMTP_ADDRESS: YOUR_SMTP
  DISCOURSE_SMTP_PORT: 587
  DISCOURSE_SMTP_USER_NAME: YOUR_SMTP_USERNAME
  DISCOURSE_SMTP_PASSWORD: YOUR_SMTP_PASSWORD
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (опционально, по умолчанию true)

  ## Если вы добавили шаблон Lets Encrypt, раскомментируйте ниже, чтобы получить бесплатный SSL-сертификат
  #LETSENCRYPT_ACCOUNT_EMAIL: me@example.com

  DISCOURSE_DB_SOCKET: ''
  DISCOURSE_DB_USERNAME: YOUR_USERNAME
  DISCOURSE_DB_PASSWORD: YOUR_PASSWORD
  DISCOURSE_DB_HOST: YOUR_HOST

  ## HTTP или HTTPS-адрес CDN для этого экземпляра Discourse (настроен на загрузку)
  ## см. https://meta.discourse.org/t/14857 для деталей
  #DISCOURSE_CDN_URL: https://discourse-cdn.example.com
## Контейнер Docker не имеет состояния; все данные хранятся в /shared
volumes:
  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared
  - volume:
      host: /var/discourse/shared/standalone/log/var-log
      guest: /var/log

## Плагины размещаются здесь
## см. https://meta.discourse.org/t/19157 для деталей
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/docker_manager.git

## Любые пользовательские команды для выполнения после сборки
run:
  - exec: echo "Начало пользовательских команд"
  ## Если вы хотите установить адрес email «От» для вашей первой регистрации, раскомментируйте и измените:
  ## После получения первого email регистрации закомментируйте строку снова. Выполнять нужно только один раз.
  - exec: rails c "SiteSetting.enable_badge_sql = true"
  - exec: echo "Конец пользовательских команд"

Начало установки Discourse

Клонирование репозитория Discourse

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

Копирование пользовательского app.yml для установки

Вы можете выполнить команду ниже, чтобы сделать это за вас. Просто замените тестовую строку «PASTE YOUR FULL APP.YML HERE» на ваш полный файл app.yml.

sudo -u root cat > /var/discourse/containers/app.yml <<\EOF
PASTE YOUR FULL APP.YML HERE
EOF

Запуск настройки Discourse

Запустите настройку Discourse без запросов с помощью команды ниже.

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

Установка Discourse должна занять 10–15 минут.

Установка NGINX, WAF и GEOIP

Выполните обновление и установите предварительные требования

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

Установите базу данных MaxMind, чтобы вы могли применять правила GEO IP в зависимости от местоположения. Вы можете использовать эту базу данных для маршрутизации или блокировки трафика в зависимости от местоположения. Теперь в логах NGINX будет отображаться страна пользователя, даже если вы не включите гео-блокировку. I

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

Загрузка и извлечение NGINX и WAF NAXSI

Примечание: замените NGINX на последнюю версию

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/

Клонирование модуля GEO IP2 для NGINX через Git

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

Компиляция NGINX

Мы создадим скрипт для выполнения этого за нас и запустим его ниже


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

Создание правил брандмауэра

Нижеприведенные правила по умолчанию включат WAF и заблокируют вредоносные запросы. Если вы хотите запустить WAF в режиме обучения, вы можете сделать это, добавив Learning Mode в файл правил ниже.

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



cat > /etc/nginx/naxsi.rules <<\EOF
SecRulesEnabled;
DeniedUrl "/RequestDenied";
## Проверка правил Naxsi
CheckRule "$SQL >= 8" BLOCK;
CheckRule "$RFI >= 8" BLOCK;
CheckRule "$TRAVERSAL >= 4" BLOCK;
CheckRule "$EVADE >= 4" BLOCK;
CheckRule "$XSS >= 8" BLOCK;
EOF

Создание файла конфигурации NGINX

По умолчанию в этом файле конфигурации гео-блокировка закомментирована, но вы можете раскомментировать и включить её, если хотите.

Примечание: сначала мы должны запустить NGINX БЕЗ SSL. Потому что нам нужно, чтобы certbot увидел живой домен. Мы получим SSL на следующем шаге и фактически скопируем новый файл конфигурации NGINX, чтобы заменить этот.

Примечание: замените cloudforums.net на ваш домен

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

Создание скрипта upstart для NGINX

Нам нужно создать скрипт для запуска службы NGINX.

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 
 # Включите настройки nginx по умолчанию, если они доступны 
 if [ -f /etc/nginx ] ; then 
         . /etc/nginx 
 fi
 
 set -e
 
 case "$1" in 
     start)
         echo -n "Запуск $DESC: " 
         start-stop-daemon --start --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON -- $DAEMON_OPTS 
         echo "$NAME." 
         ;; 
     stop) 
         echo -n "Остановка $DESC: " 
         start-stop-daemon --stop --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON 
         echo "$NAME." 
         ;; 
     restart|force-reload) 
         echo -n "Перезапуск $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 "Перезагрузка конфигурации $DESC: " 
         start-stop-daemon --stop --signal HUP --quiet --pidfile /var/run/nginx.pid \ 
             --exec $DAEMON 
         echo "$NAME." 
         ;; 
     *) 
         N=/etc/init.d/$NAME 
         echo "Использование: $N {start|stop|restart|force-reload}" >&2 
         exit 1 
         ;; 
 esac
 
 exit 0
EOF

systemctl daemon-reload

Создание файла службы NGINX

cat > /lib/systemd/system/nginx.service <<\EOF
[Unit]
Description=Высокопроизводительный веб-сервер и сервер обратного прокси
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

Загрузка базы данных стран 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/

Включение и запуск NGINX

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

Добавление SSL-сертификата для вашего домена

Не забудьте заменить cloudforums.net на ваш домен.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 youremail@domain.com

Создание нового файла NGINX с правилами SSL

Создайте новый файл NGINX с вашим только что загруженным SSL-сертификатом.

Примечание: измените cloudforums.net на ваш домен


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';
    
    #***********************************************************
    #Раскомментируйте для гео-блокировки. По умолчанию в настройках ниже только US
    #***********************************************************
    #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;
       }
    }
    # 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

Перезапуск NGINX

systemctl restart nginx

Теперь у вас есть потрясающий WAF с открытым исходным кодом!! :slight_smile:

Бесстыдная реклама

Не забудьте присоединиться к нам на cloudforums.net, если вам понравилась эта инструкция. Мы очень рады видеть вас у нас. Мы ничего не продаем и не размещаем рекламу. Ищем только хороших участников IT-обсуждений :slight_smile:

Одношаговая автоматическая установка NGINX / WAF после установки Discourse. Не забудьте изменить доменное имя с cloudforums.net на ваше доменное имя.

Просто выполните

sh discourse_install.sh