1Panel旁路接入雷池WAF(非反向代理套娃)

1Panel旁路接入雷池WAF(非反向代理套娃)

在选型WAF过程中,我对比了南墙WAF和雷池WAF,最终选择了社区活跃度更高的雷池WAF。通过实践,我成功实现了在1Panel旁路接入雷池WAF,无需修改网络架构,且不影响站点管理功能。安装雷池WAF社区版,我分析了其一键安装脚本,并通过Docker Compose配置,实现了WAF的无感接入。尽管遇到了一些误拦截的问题,但通过调整配置和使用白名单,问题得到了缓解。

选型

在WAF选型过程中,考虑了两款WAF解决方案:南墙WAF和雷池WAF。通过GitHub社区活跃度指标来看,雷池WAF的社区活跃度明显领先于南墙WAF。然而,鉴于我缺乏进行拦截效果测试的能力,我无法确定哪款WAF在拦截效果方面更为优越。

在 1Panel 的问题反馈中,雷池WAF的开发人员推荐了一种旁路模式接入方式,使用 t1k Lua 模块,该方式可实现WAF接入,而无需进行大规模的网络架构修改。

本文实践了在保留 1Panel 的站点功能的同时,无需修改端口,无需在WAF里进行其他操作,即可接入WAF,从而实现无感接入,和正常使用 1Panel 的站点管理功能无异。

雷池安装程序分析

雷池的一键安装是以shell脚本的形式分发,将其下载下来进行分析。

bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)"

将脚本内容交给ChatGPT分析,得到以下回应:

这个安装脚本的具体意图是安装雷池(SafeLine)WAF社区版。下面是脚本的主要步骤和功能:

  1. 显示雷池(SafeLine)WAF的标志性图形。
  2. 定义了一个名为qrcode的函数,用于显示雷池项目讨论组的二维码。
  3. 定义了几个辅助函数:
    • command_exists:检查命令是否存在。
    • space_left:检查指定目录的剩余存储空间。
    • start_docker:启动并启用Docker服务。
    • confirm:用于确认用户的输入,提示用户是否继续操作。
    • info:用于显示一般信息。
    • warning:用于显示警告信息。
    • abort:用于显示错误信息并退出脚本。
  4. 检查CPU是否支持ssse3指令集,如果不支持则终止安装。
  5. 设置安装目录为/data/safeline,用户可以选择自定义目录。
  6. 检查脚本是否在Bash中运行,是否为标准输入设备,是否以root权限运行,并确认脚本调用方式正常。
  7. 检查Docker环境是否已安装,如果没有安装,则提示用户是否自动安装Docker。
  8. 检查Docker服务状态,如果异常则终止安装。
  9. 检查Docker Compose是否已安装,如果没有安装则提示用户是否自动安装Docker Compose。
  10. 提示用户输入雷池安装目录,默认为/data/safeline,并检查目录的存储空间是否足够。
  11. 创建安装目录,并下载compose.yaml文件和.env文件。
  12. .env文件中设置一些配置参数,如安装目录、镜像标签、管理端口等。
  13. 开始下载Docker镜像,并启动Docker容器。
  14. 显示雷池WAF社区版安装成功的二维码,并提供访问控制台的链接。

总的来说,这个脚本的目的是帮助用户安装雷池WAF社区版,它会自动安装Docker和Docker Compose,然后下载所需的镜像和配置文件,并最终启动WAF服务。用户可以在安装过程中自定义安装目录等配置信息。

可以看到脚本内容主要是以检查环境,创建文件夹及.env文件和启动docker-compose为主。

写入.env文件的内容如下:

echo "SAFELINE_DIR=$safeline_path" >> .env
echo "IMAGE_TAG=latest" >> .env
echo "MGT_PORT=9443" >> .env
    echo "POSTGRES_PASSWORD=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" >> .env
echo "REDIS_PASSWORD=$(LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c 32)" >> .env
echo "SUBNET_PREFIX=172.22.222" >> .env

这些内容后续可手动填写,无需使用安装脚本。

接着将compose.yaml文件下载下来进行分析。在这个Docker Compose文件中,定义了以下几个服务:

  1. postgres(PostgreSQL数据库服务):用于存储SafeLine WAF社区版的数据,包括配置信息和日志。
  2. redis(Redis服务):用于作为缓存和消息队列,以提供快速的数据访问和通信。
  3. management(SafeLine管理API服务):这是SafeLine WAF的管理接口,允许用户配置和管理WAF的规则和策略。
  4. detector(SafeLine Detector服务):SafeLine WAF的核心组件,用于检测和拦截恶意流量。
  5. mario(SafeLine Mario服务):SafeLine WAF的一个组件,用于处理日志数据和性能监控。
  6. tengine(SafeLine Tengine服务):一个Web服务器,用于代理和处理网络流量,实现Web应用防火墙的功能。

我想实现的是WAF的旁路接入,因此首先要干掉的是tengine,再在1Panel中的OpenResty安装t1k

1Panel + 雷池部署配置

.env文件

/data/safeline目录下创建.env文件,并添加以下内容:

SAFELINE_DIR=/data/safeline
MGT_PORT=9443
POSTGRES_PASSWORD=postgres密码
REDIS_PASSWORD=redis密码
SUBNET_PREFIX=192.168.199
IMAGE_TAG=latest-stream
IMAGE_PREFIX=chaitin

您需要手动填写这些配置信息。

983E4361-4677-43EE-8112-92802D2F21E0.png

6E00C1C6-270C-42E4-925F-ABC232C4FD55.png

Docker Compose文件

/data/safeline目录下创建docker-compose.yml文件,以下是我修改的Docker Compose文件:

docker-compose.yml
networks:
  safeline-ce:
    name: safeline-ce
    driver: bridge
    ipam:
      driver: default
      config:
      - gateway: ${SUBNET_PREFIX:?SUBNET_PREFIX required}.1
        subnet: ${SUBNET_PREFIX}.0/24
    driver_opts:
      com.docker.network.bridge.name: safeline-ce

services:
  postgres:
    container_name: safeline-postgres
    restart: always
    image: postgres:15.2
    volumes:
    - ${SAFELINE_DIR}/resources/postgres/data:/var/lib/postgresql/data
    - /etc/localtime:/etc/localtime:ro
    environment:
    - POSTGRES_USER=safeline-ce
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?postgres password required}
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.2
    cap_drop:
    - net_raw
    command: [postgres, -c, max_connections=200]
  redis:
    container_name: safeline-redis
    restart: always
    image: redis:7.0.10
    volumes:
      - ${SAFELINE_DIR}/resources/redis/data:/data
      - /etc/localtime:/etc/localtime:ro
    command: redis-server --appendonly yes --requirepass  ${REDIS_PASSWORD}
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.3
    cap_drop:
      - net_raw
    sysctls:
      net.core.somaxconn: "511"
  management:
    container_name: safeline-mgt-api
    restart: always
    image: chaitin/safeline-mgt-api:${IMAGE_TAG:?image tag required}
    volumes:
    - ${SAFELINE_DIR?safeline dir required}/resources/management:/resources/management
    - ${SAFELINE_DIR}/resources/nginx:/resources/nginx
    - ${SAFELINE_DIR}/logs:/logs
    - /etc/localtime:/etc/localtime:ro
    ports:
    - ${MGT_PORT:-9443}:1443
    environment:
    - MANAGEMENT_RESOURCES_DIR=/resources/management
    - NGINX_RESOURCES_DIR=/resources/nginx
    - DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-postgres/safeline-ce
    - MARIO_URL=http://safeline-mario:3335
    - DETECTOR_URL=http://safeline-detector:8001
    - REDIS_URL=redis://:${REDIS_PASSWORD}@safeline-redis:6379/0
    - MANAGEMENT_LOGS_DIR=/logs/management
    dns: 
      - 119.29.29.29
      - 223.5.5.5
      - 180.76.76.76
      - 1.2.4.8
      - 114.114.114.114
      - 8.8.8.8
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.4
    cap_drop:
    - net_raw
  detector:
    container_name: safeline-detector
    restart: always
    image: chaitin/safeline-detector:${IMAGE_TAG}
    volumes:
    - ${SAFELINE_DIR}/resources/detector:/resources/detector
    - ${SAFELINE_DIR}/logs/detector:/logs/detector
    - /etc/localtime:/etc/localtime:ro
    environment:
    - LOG_DIR=/logs/detector
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.5
    cap_drop:
    - net_raw
  mario:
    container_name: safeline-mario
    restart: always
    image: chaitin/safeline-mario:${IMAGE_TAG}
    volumes:
    - ${SAFELINE_DIR}/resources/mario:/resources/mario
    - ${SAFELINE_DIR}/logs/mario:/logs/mario
    - /etc/localtime:/etc/localtime:ro
    environment:
    - LOG_DIR=/logs/mario
    - GOGC=100
    - DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-postgres/safeline-ce
    - REDIS_URL=redis://:${REDIS_PASSWORD}@safeline-redis:6379/0
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.6
    cap_drop:
    - net_raw

compose 文件经常更新,详见下方文章更新记录。

启动

/data/safeline目录下使用以下命令启动服务:

docker compose up -d

1Panel的OpenResty替换tengine

在正常安装并配置1Panel后,在应用商店安装OpenResty

请注意,应用商店安装的OpenResty可能不包含t1k模块,因此您可以在docker-compose文件中添加安装命令,如下所示:

        entrypoint:
            - /bin/sh
            - -c
            - |
              luarocks install lua-resty-t1k --server https://luarocks.cn
              ln -s /usr/local/openresty/luajit/share/lua/5.1/resty/* /usr/local/openresty/lualib/resty/
              /usr/local/openresty/bin/openresty -g "daemon off;"
        volumes:
            - /data/safeline/resources/detector:/resources/detector

这将在启动OpenResty之前安装t1k模块,以避免lua文件找不到的问题。

同时,将/data/safeline/resources/detector映射到容器内部,以建立t1k与WAF之间的通讯。

OpenResty镜像无论怎么更新,都是在compose文件上加上面的内容。

61DC7CA1-8CFB-4ED8-95EA-AFA565F81025.png

站点配置

创建站点的反向代理配置文件,例如/opt/1panel/apps/openresty/openresty/www/common/t1k.conf,并将以下内容添加到配置文件中:

access_by_lua_block {
    local t1k = require "resty.t1k"

    local t = {
        mode = "block",
        host = "unix:/resources/detector/snserver.sock",
        port = 8000,
        connect_timeout = 1000,
        send_timeout = 1000,
        read_timeout = 1000,
        req_body_size = 1024,
        keepalive_size = 256,
        keepalive_timeout = 60000,
        remote_addr = "http_x_forwarded_for: 1",
    }

    local ok, err, _ = t1k.do_access(t, true)
    if not ok then
        ngx.log(ngx.ERR, err)
    end
}

header_filter_by_lua_block {
    local t1k = require "resty.t1k"
    t1k.do_header_filter()
}

由于 1Panel 的静态站点不使用 location 块,所以无法直接使用上面的 conf。

如需对静态站点也套上WAF,可以新建t1k_static_stie_location_root.conf,添加如下内容:

location ~ / {
    index index.html index.htm index.php default.php default.htm default.html; 
    include /www/common/t1k.conf;
}

B20B9718-8993-45F7-B6CB-89FA0C4E1A7E.png

915CA82A-6B62-45F5-B3FF-7E41D2AA7BC0.png

最后,在1Panel站点的反向代理配置文件中包含以下行以完成配置:

include /www/common/t1k.conf;

C1F92976-5BFC-4D9A-8466-E6AD2C64FBA3.png

或在静态站点的配置文件中添加:

include /www/common/t1k_static_stie_location_root.conf;

C5C13FE5-D539-4589-BB7C-B5C8D6EA68AD.png

验证

访问您的站点的/shell.php页面,如果看到blocked by Chaitin SafeLine Web Application Firewall的消息并且在9443端口的管理界面中有相应的记录,则说明您已成功实现了WAF的防护。

3F37C57B-FA6E-43CE-83CF-F470D4B499E9.png

后记

尽管按照这种方法能够完美地利用 1Panel 的站点管理功能以及雷池的WAF,但在使用几天后,我发现了一些麻烦。

在撰写这篇博客时,我发现 Halo 一直在频繁报错(403),导致我无法编辑和保存博客。这可能是因为博客中包含了大量代码,被WAF误认为是恶意请求而触发了阻拦。至于雷池,目前我还没有找到如何将其加入白名单的方法,只能将所有检测功能设为“仅观察”。

在之后的使用中,我可能会根据被误拦截的情况来决定是否继续使用这种接入方法,或许需要更换其他的WAF。

2023.11.30 更新

用着先把,加上白名单又不是不能用,我很少发和修改我的博客。

另外 3.14.1 版本更新好大,yaml 文件要改很多:

点击查看YAML
networks:
  safeline-ce:
    name: safeline-ce
    driver: bridge
    ipam:
      driver: default
      config:
        - gateway: ${SUBNET_PREFIX:?SUBNET_PREFIX required}.1
          subnet: ${SUBNET_PREFIX}.0/24
    driver_opts:
      com.docker.network.bridge.name: safeline-ce

services:
  postgres:
    container_name: safeline-postgres
    restart: always
    image: postgres:15.2
    volumes:
      - ${SAFELINE_DIR}/resources/postgres/data:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      - POSTGRES_USER=safeline-ce
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?postgres password required}
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.2
    cap_drop:
      - net_raw
    command: [postgres, -c, max_connections=200]
  management:
    container_name: safeline-mgt-api
    restart: always
    image: chaitin/safeline-mgt-api:${IMAGE_TAG:?image tag required}
    volumes:
      - ${SAFELINE_DIR?safeline dir required}/resources/management:/resources/management
      - ${SAFELINE_DIR}/resources/nginx:/resources/nginx
      - ${SAFELINE_DIR}/logs:/logs
      - /etc/localtime:/etc/localtime:ro
    ports:
      - ${MGT_PORT:-9443}:1443
    environment:
      - MANAGEMENT_RESOURCES_DIR=/resources/management
      - NGINX_RESOURCES_DIR=/resources/nginx
      - DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-postgres/safeline-ce
      - MARIO_URL=http://safeline-mario:3335
      - FVM_MANAGER_URL=safeline-fvm-manager:9004
      - MANAGEMENT_LOGS_DIR=/logs/management
    dns:
      - 119.29.29.29
      - 223.5.5.5
      - 180.76.76.76
      - 1.2.4.8
      - 114.114.114.114
      - 8.8.8.8
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.4
    cap_drop:
      - net_raw
  detector:
    container_name: safeline-detector
    restart: always
    image: chaitin/safeline-detector:${IMAGE_TAG}
    volumes:
      - ${SAFELINE_DIR}/resources/detector:/resources/detector
      - ${SAFELINE_DIR}/logs/detector:/logs/detector
      - /etc/localtime:/etc/localtime:ro
    environment:
      - LOG_DIR=/logs/detector
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.5
    cap_drop:
      - net_raw
  mario:
    container_name: safeline-mario
    restart: always
    image: chaitin/safeline-mario:${IMAGE_TAG}
    volumes:
      - ${SAFELINE_DIR}/resources/mario:/resources/mario
      - ${SAFELINE_DIR}/logs/mario:/logs/mario
      - /etc/localtime:/etc/localtime:ro
    environment:
      - LOG_DIR=/logs/mario
      - GOGC=100
      - DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-postgres/safeline-ce
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.6
    cap_drop:
      - net_raw
  fvm-manager:
    container_name: safeline-fvm-manager
    restart: always
    image: chaitin/safeline-fvm-manager:${IMAGE_TAG}
    environment:
      - FVM_LOGS_DIR=/logs/management
      - DETECTOR_URL=http://safeline-detector:8001
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${SAFELINE_DIR}/logs:/logs
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.8
    cap_drop:
      - net_raw

2024.2.29 更新

networks:
  safeline-ce:
    name: safeline-ce
    driver: bridge
    ipam:
      driver: default
      config:
        - gateway: ${SUBNET_PREFIX:?SUBNET_PREFIX required}.1
          subnet: ${SUBNET_PREFIX}.0/24
    driver_opts:
      com.docker.network.bridge.name: safeline-ce

services:
  postgres:
    container_name: safeline-pg
    restart: always
    image: postgres:15.2
    volumes:
      - ${SAFELINE_DIR}/resources/postgres/data:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro
    environment:
      - POSTGRES_USER=safeline-ce
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?postgres password required}
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.2
    command: [postgres, -c, max_connections=200]
  mgt:
    container_name: safeline-mgt
    restart: always
    image: ${IMAGE_PREFIX}/safeline-mgt:${IMAGE_TAG:?image tag required}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${SAFELINE_DIR}/resources/mgt:/app/data
    ports:
      - ${MGT_PORT:-9443}:1443
    environment:
      - MGT_PG=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
    depends_on:
      - postgres
      - fvm
    dns:
      - 119.29.29.29
      - 223.5.5.5
      - 180.76.76.76
      - 1.2.4.8
      - 114.114.114.114
      - 8.8.8.8
    logging:
      options:
        max-size: "100m"
        max-file: "10"
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.4
  detect:
    container_name: safeline-detector
    restart: always
    image: ${IMAGE_PREFIX}/safeline-detector:${IMAGE_TAG}
    volumes:
      - ${SAFELINE_DIR}/resources/detector:/resources/detector
      - ${SAFELINE_DIR}/logs/detector:/logs/detector
      - /etc/localtime:/etc/localtime:ro
    environment:
      - LOG_DIR=/logs/detector
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.5
  mario:
    container_name: safeline-mario
    restart: always
    image: ${IMAGE_PREFIX}/safeline-mario:${IMAGE_TAG}
    volumes:
      - ${SAFELINE_DIR}/resources/mario:/resources/mario
      - ${SAFELINE_DIR}/logs/mario:/logs/mario
      - /etc/localtime:/etc/localtime:ro
    environment:
      - LOG_DIR=/logs/mario
      - GOGC=100
      - DATABASE_URL=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.6
  luigi:
    container_name: safeline-luigi
    restart: always
    image: ${IMAGE_PREFIX}/safeline-luigi:${IMAGE_TAG}
    environment:
      - MGT_IP=${SUBNET_PREFIX}.4
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${SAFELINE_DIR}/resources/luigi:/app/data
    depends_on:
      - detect
      - mgt
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.7
  fvm:
    container_name: safeline-fvm
    restart: always
    image: ${IMAGE_PREFIX}/safeline-fvm:${IMAGE_TAG}
    volumes:
      - /etc/localtime:/etc/localtime:ro
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.8
  bridge:
    container_name: safeline-bridge
    restart: always
    image: ${IMAGE_PREFIX}/safeline-bridge:${IMAGE_TAG}
    command:
      - /app/bridge
      - serve
      - -n
      - unix
      - -a
      - /app/run/safeline.sock
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run:/app/run
    logging:
      options:
        max-size: "100m"
        max-file: "10"
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.9
    depends_on:
      - mgt

2024.3.12 更新

目前新版本控制 t1k 插件是否拦截的方式需要修改t1k.conf

    local t = {
        mode = "block", -- block or monitor or off, default off
        host = "unix:/resources/detector/snserver.sock",

modeblock换成monitor或者off,记得要重载nginx配置,搞完记得换回来。

LICENSED UNDER CC BY-NC-SA 4.0
Comment