1Panel + 雷池 WAF 旁路接入 2.0 —— v2 + 8.0.0-LTS 部署

1Panel + 雷池 WAF 旁路接入 2.0 —— v2 + 8.0.0-LTS 部署

在保持 WAF 旁路架构稳定运行两年后,我决定升级到 1Panel v2 与雷池 8.0.0-LTS。本文记录了升级的动机与部署过程,并分享其中的关键步骤与经验。

前情提要:

https://www.zway.top/archives/1panel-jie-ru-lei-chi-waf-fei-fan-xiang-dai-li

升级动机

在前文https://www.zway.top/archives/1panel-jie-ru-lei-chi-waf-fei-fan-xiang-dai-li 写完后,我一直保持着这WAF旁路架构,即使WAF炸了也不影响我网站及应用的访问,近两年的时间也验证了这一架构的健壮性。

在前文完成2年后,1Panel v2发布了,引入了多节点管理、更完善的 Docker 管理功能,以及支持 OpenResty 自行编译等特性。在1Panel v2的早期版本中,更新日志中包含了大量的bug修复,数量太多以至于没有动力从v1升级到v2,甚至在 2.0.5 版本中存在高危的无需权限 RCE 漏洞。此时v1处于lts版本,运行稳定且更新更慢减少打扰,所以保持了一段时间不升级到v2。随着 2.0.8 的发布,其 Release 日志中的 Bug 修复明显减少,Issues 中的反馈频率也有所降低,同时我也有1Panel的永久授权,不用白不用,最终在 2.0.9 发布前进行了升级。

在早期使用雷池 WAF 时,并不十分省心,在我文章的更新记录中可以看出,雷池 WAF 经常更新版本,不仅修改 Docker Compose 文件和 .env 配置,有时还会调整 Socket 文件位置,升级过程中耗费大量时间用于排查问题。准备升级1Panel时,我也顺带看了下雷池的更新日志,发现我之前使用的 latest-stream 版本已久未更新。在更新日志中发现了其有发布多个lts版本,因此决定将雷池升级到 LTS 版本。lts版本有多个,看了用户相关反馈,新的lts可能削了首页看板/防护日志等关键功能,不能追新版本,选择了一个优化了防护引擎且保留功能的 8.0.0-LTS 版本。

新版本关键变更

1Panel v2

官方亮点介绍:

https://github.com/1Panel-dev/1Panel/releases/tag/v2.0.0

  • 新增多机管理能力

  • 支持自定义仓库,离线环境也能安装应用

  • 新增文件对传功能

  • 网站管理功能全面重构

  • 新增网站负载均衡功能

  • 新增脚本库功能

  • 容器页面重构

  • 快照机制重构

以上我都不太需要,我认为有价值的点:

  • Docker 管理功能更全面

  • OpenResty支持自己编译

雷池 WAF 8.0.0-LTS

摘取官方我认为有用的更新点:

https://help.waf-ce.chaitin.cn/node/0197787e-b70e-756e-b9d5-69d0a707144a

  • 引擎(7.4.0)

    • 新增

      • 大幅优化漏洞检测模块,支持更多漏洞

      • 新增大量加强规则

    • 优化

      • HTTP 协议解析逻辑

      • SQL 注入检测逻辑

      • Java 代码注入检测逻辑

      • PHP 代码注入检测逻辑

      • JSON 解析解码逻辑

      • 斜杠反转义解码逻辑

    • 修复

      • 攻击检测部分接口偶现失效问题

      • 部分 XFF 空值的场景中,检测异常的问题

    • 重构

      • JSP 代码检测逻辑,支持检测更多绕过

      • Java 反序列化检测逻辑

  • 防护应用支持配置 SSE 流式响应(8.0.0)

  • 修复 SDK 旁路接入时 QPS 不显示的问题(8.0.0)

旁路接入架构

旁路接入实现步骤

安装1Panel

1Panel按官方文档安装或升级即可,无特殊操作。

安装完成后,在 1Panel 应用商店安装最新版本的 OpenResty,安装后确认网站能正常访问。

部署雷池

.env文件

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

SAFELINE_DIR=/data/safeline
POSTGRES_PASSWORD=随机一个密码
MGT_PORT=9443
RELEASE=-lts
CHANNEL=-lts
REGION=
IMAGE_PREFIX=chaitin
# 如果从dockerhub拉不下来可以换下面这个
# IMAGE_PREFIX=swr.cn-east-3.myhuaweicloud.com/chaitin-safeline
IMAGE_TAG=8.0.0-lts
SUBNET_PREFIX=192.168.199
ARCH_SUFFIX=

配置文件的格式说明如下:

  • SAFELINE_DIR: 雷池安装目录,例如 /data/safeline,请根据实际情况调整

  • POSTGRES_PASSWORD: 雷池数据库的密码,随机生成一个

  • MGT_PORT: 雷池控制台的端口

  • RELEASE: 更新通道,已配置为 lts 版本更新通道

  • IMAGE_PREFIX: 雷池镜像源的前缀,建议根据服务器地理位置选择合适的源

  • IMAGE_TAG: 要安装的雷池版本, 格式为版本号-lts

  • SUBNET_PREFIX: 雷池内部网络的网段,我这里为了防止ip冲突用了一个C段IP,可按实际情况设置

Docker Compose文件

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

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=600]
    healthcheck:
      test: pg_isready -U safeline-ce -d safeline-ce
  mgt:
    container_name: safeline-mgt
    restart: always
    image: ${IMAGE_PREFIX}/safeline-mgt${REGION}${ARCH_SUFFIX}:${IMAGE_TAG:?image tag required}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${SAFELINE_DIR}/resources/mgt:/app/data
      - ${SAFELINE_DIR}/logs/nginx:/app/log/nginx:z
      - ${SAFELINE_DIR}/resources/sock:/app/sock
      - /var/run:/app/run
    ports:
      - ${MGT_PORT:-9443}:1443
    healthcheck:
      test: curl -k -f https://localhost:1443/api/open/health
    environment:
      - MGT_PG=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
    depends_on:
      postgres:
        condition: service_healthy
      fvm:
        condition: service_started
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.4
  detect:
    container_name: safeline-detector
    restart: always
    image: ${IMAGE_PREFIX}/safeline-detector${REGION}${ARCH_SUFFIX}:${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
  tengine:
    container_name: safeline-tengine
    image: ${IMAGE_PREFIX}/safeline-tengine${REGION}${ARCH_SUFFIX}:${IMAGE_TAG}
    restart: "no"
    entrypoint:
      - sh
      - -c
      - |
       (sleep 600; echo "[INFO] 10 minutes passed. Stopping tengine..." >&2; kill -TERM 1) & \
       exec entrypoint.sh nginx -g 'daemon off;'
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/resolv.conf:/etc/resolv.conf:ro
      - ${SAFELINE_DIR}/resources/nginx:/etc/nginx
      - ${SAFELINE_DIR}/resources/detector:/resources/detector
      - ${SAFELINE_DIR}/resources/chaos:/resources/chaos
      - ${SAFELINE_DIR}/logs/nginx:/var/log/nginx:z
      - ${SAFELINE_DIR}/resources/cache:/usr/local/nginx/cache
      - ${SAFELINE_DIR}/resources/sock:/app/sock
    environment:
      - TCD_MGT_API=https://${SUBNET_PREFIX}.4:1443/api/open/publish/server
      - TCD_SNSERVER=${SUBNET_PREFIX}.5:8000
      # deprecated
      - SNSERVER_ADDR=${SUBNET_PREFIX}.5:8000
      - CHAOS_ADDR=${SUBNET_PREFIX}.10
    ulimits:
      nofile: 131072
    network_mode: host
  luigi:
    container_name: safeline-luigi
    restart: always
    image: ${IMAGE_PREFIX}/safeline-luigi${REGION}${ARCH_SUFFIX}:${IMAGE_TAG}
    environment:
      - MGT_IP=${SUBNET_PREFIX}.4
      - LUIGI_PG=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ${SAFELINE_DIR}/resources/luigi:/app/data
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    depends_on:
      detect:
        condition: service_started
      mgt:
        condition: service_healthy
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.7
  fvm:
    container_name: safeline-fvm
    restart: always
    image: ${IMAGE_PREFIX}/safeline-fvm${REGION}${ARCH_SUFFIX}:${IMAGE_TAG}
    volumes:
      - /etc/localtime:/etc/localtime:ro
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.8
  chaos:
    container_name: safeline-chaos
    restart: always
    image: ${IMAGE_PREFIX}/safeline-chaos${REGION}${ARCH_SUFFIX}:${IMAGE_TAG}
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "10"
    environment:
      - DB_ADDR=postgres://safeline-ce:${POSTGRES_PASSWORD}@safeline-pg/safeline-ce?sslmode=disable
    volumes:
      - ${SAFELINE_DIR}/resources/sock:/app/sock
      - ${SAFELINE_DIR}/resources/chaos:/app/chaos
    networks:
      safeline-ce:
        ipv4_address: ${SUBNET_PREFIX}.10

基于官方原版Compose文件,我修改了如下内容:

  • 完善depends_on配置,等待前置容器完全启动/正常工作后才启动下一个容器

  • safeline-tengine容器10分钟后自动退出,避免系统资源浪费(本部署方式无需tengine)

  • postgres数据库采用官方镜像

因使用lts版本,理论上以后docker compose文件不会有大变动了。

启动雷池

启动前确保如下2个文件已正确保存,.env文件可能会隐藏。

/data/safeline
├── docker-compose.yaml
└── .env

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

docker compose up -d

启动后理论上文件夹下的内容:

/data/safeline
├── 1panel.env
├── docker-compose.yaml
├── .env
├── logs
└── resources

编译并配置openresty应用

t1k 是雷池提供的用于 OpenResty 旁路接入的 Lua SDK。基于 1Panel v2 可自行编译 OpenResty 的特性,可以在启动容器前就安装好SDK,相比以往在容器启动后安装,能显著提升启动速度。

打开网站-网站-设置-模块-创建,输入以下配置。

  • 名称:随便起一个名。

  • 参数--with-compat ,理论上无需参数,但 1Panel 此处必须填写一个,只能重申一次启动动态模块兼容性。

  • 脚本echo -e "\nluarocks install lua-resty-t1k\n" >> /tmp/default.sh ,目的是在安装 LuaRocks 后,通过 /tmp/default.sh 安装 lua-resty-t1k 。

确认后打开开关并点击构建,等待构建完成。

配置openresty旁路接入雷池

挂载套接字文件目录

点击应用商店-已安装-openresty-参数-高级设置-编辑compose文件,打开compose文件编辑框。

在volumes下增加一行 - /data/safeline/resources/detector:/resources/detector

保存并重建openresty,建议重新编译一次以确保生效。

站点配置

创建通用配置文件

在1Panel网站目录下创建common目录,网站目录可在应用-OpenResty-参数中查看,例如我的在 /opt/1panel/www/common

创建文件并添加相应内容:

t1k.conf /opt/1panel/www/common/t1k.conf

access_by_lua_block {
    local t1k = require "resty.t1k"

    local t = {
        mode = "block", -- block or monitor or off, default off
        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
    
    dofile("/usr/local/openresty/1pwaf/waf.lua")
}

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

t1k_static_stie_location_root.conf /opt/1panel/www/common/t1k.conf/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;
}

此配置提供了 Lua 脚本处理模板:请求先通过雷池检测,再经过 1Panel 自身的检测(网站监控 + WAF),这样能让雷池和1Panel的WAF共同工作。

网站接入雷池

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

问题与解决方案

Halo正常接口误报

自定义规则中订阅在线规则就行。

尾巴

升级这事儿,说到底就是个平衡。系统跑得稳是第一位的,但偶尔尝点新鲜也能让架构更顺手。1Panel 从 v1 换到 v2,雷池上 LTS,其实也就是在“别瞎折腾”和“别太保守”之间找个舒服的点。

折腾多了人会累,版本更新也追不完。挑准时机,动一次就够了,剩下的时间还是留给生活比较实在。

LICENSED UNDER CC BY-NC-SA 4.0
评论