如何使用开源 WAF 构建 Discourse 安装

由于我不被允许在“操作指南”板块发帖,所以我会在这里发布此内容 :slight_smile:

概述

目标是在保持小型安装性能的同时,为现有资源添加 WAF。在本示例中,我们将使用单个 EC2 实例。由于 Discourse 运行在 Docker 中,我们可以在宿主机上安装 NGINX,并使用 proxy_pass 将流量转发到我们的 Docker 容器中。我们还使用 RDS 作为 PostgreSQL 数据库。

Discourse 配置

我们的第一步是配置自定义 Discourse 安装的 app.yml 文件。在本示例中,我们将运行 web 和 redis 角色。我们将不使用 DB 角色或 SSL 角色。

此外,我们要确保不在 Docker 容器中暴露 web 端口。原因是我们将通过宿主机的 NGINX 代理来暴露 web 服务。

app.yml 文件示例

## 这是独立式 Discourse Docker 容器的模板
##
## 修改此文件后,您必须重新构建
## /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 等其他 Web 服务器共享端口,
## 请参阅 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%。
  ## 将根据检测到的 RAM 自动由 bootstrap 设置,您也可以覆盖
  db_shared_buffers: "768MB"
  ## 可以提高排序性能,但会增加每个连接的内存使用量
  #db_work_mem: "40MB"
  ## 此容器应使用哪个 Git 版本?(默认:tests-passed)
  #version: tests-passed

env:
  LANG: en_US.UTF-8
  # DISCOURSE_DEFAULT_LOCALE: en
  ## 支持多少个并发 Web 请求?取决于内存和 CPU 核心数。
  ## 将根据检测到的 CPU 自动由 bootstrap 设置,您也可以覆盖
  UNICORN_WORKERS: 2

  ## TODO: 此 Discourse 实例将响应的域名
  ## 必需。Discourse 无法在纯 IP 地址上运行。
  DISCOURSE_HOSTNAME: cloudforums.net

  ## 如果您希望容器以与上述相同的
  ## 主机名(-h 选项)启动,请取消注释(默认值为 "$hostname-$config")
  #DOCKER_USE_HOSTNAME: true

  ## TODO: 初始注册时将设为管理员和开发人员的逗号分隔电子邮件列表
  ## 示例:'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

  ## 此 Discourse 实例的 HTTP 或 HTTPS CDN 地址(配置为拉取)
  ## 详见 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 "开始自定义命令"
  ## 如果您想设置首次注册的'发件人'电子邮件地址,请取消注释并修改:
  ## 收到第一封注册邮件后,请重新注释该行。它只需运行一次。
  - 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 日志现在也会显示用户的国家。

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

下载并解压 NGINX 和 NAXSI WAF

注意:请将 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

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 配置文件

在此配置文件中,默认情况下地理封锁已被注释掉,但如果您需要,可以取消注释并启用。

注意:我们必须首先在不使用 SSL 的情况下运行 NGINX。因为我们需要 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

为 NGINX 创建 upstart 脚本

我们需要为 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=A high performance web server and a reverse proxy server
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 替换为您的 domain.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

创建带有 SSL 规则的新 NGINX 文件

使用您刚刚下载的 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';
    
    #***********************************************************
    #取消注释以进行地理封锁。默认情况下,下方设置中仅限美国
    #***********************************************************
    #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