最近飞牛 NAS 爆出了一个 0Day 漏洞,很多开启了公网访问(包括但不限于 DDNS、公网 IP、FN Connect)的飞牛用户都中招了,不得不格盘重装系统。
由于我一开始就不太信任飞牛团队以及我自己,所以除了 FN Connect 之外,飞牛的漏洞被披露后,我马上升级了最新版本,并关闭了 FN Connect,我没有提供任何直接的公网访问飞牛控制面板的方式 —— 想要从家庭局域网以外连接回去,必须使用我的 Singbox 服务。详情见2024 家庭网络设备大升级 网络篇
不过,我确实有一些其他的网络服务出于一些原因需要暴露在公网。这些服务中,有一些是我手搓的,天生免疫大部分的针对 Web 应用的网络攻击,另外一些则是有一定用户基础的开源项目的自托管实例。这类服务就像飞牛一样,容易成为黑客广撒网的攻击目标。
这些使用 Cloudflare Tunnel 暴露在公网的服务都有 Cloudflare 提供最基础的 Web 安全防护功能,但是防患于未然,我决定给这些比较脆弱的服务再多加上一层保护,这里我选择的是个人免费使用的雷池 WAF (Web Application Firewall)。
安装雷池
为了充分享受 DIY 的好处,我建议手动安装雷池。为了让雷池适配我的家庭内网配置,我修改后的 Docker Compose 文件如下:
networks:
apps: #我手动创建的 Docker 网络,所有对外服务都需要加入这个网络
external: true
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: ${IMAGE_PREFIX}/safeline-postgres${ARCH_SUFFIX}: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
- MGT_PROXY=${MGT_PROXY}
depends_on:
- postgres
- fvm
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
restart: always
image: ${IMAGE_PREFIX}/safeline-tengine${REGION}${ARCH_SUFFIX}:${IMAGE_TAG}
volumes:
- /etc/localtime:/etc/localtime:ro
# 注意!!!我删除了这里的 /etc/resolv.conf 挂载
# 因为 tengine 加入了 apps 网络,必须使用 Docker 内置的 DNS
# - /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
# 这里使用 apps 网络替代主机模式网络,避免占用主机 80/443 端口
#network_mode: host
networks:
- apps
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
- mgt
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
使用任何你喜欢的方式启动这个 docker-compose 文件,部署雷池的工作就完成了大半。
对接 Cloudflare Tunnel
Cloudflare Tunnel 是一个免费的内网穿透工具,可以将来自公网的请求通过 Cloudflare 网络导向没有公网地址的 Web 服务。要使用 Cloudflare Tunnel 可以参考这里的文档。
我这里简单介绍一下在内网部署 Cloudflared 的 docker-compose 文件:
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: always
pull_policy: always
command: tunnel --no-autoupdate run --token $TUNNEL_TOKEN
networks:
- apps # 注意!!!需要让 cloudflared 也加入 apps 网络,否则无法将流量导向雷池
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
volumes:
- ./cloudflared-data:/home/nonroot/.cloudflared
networks:
apps:
external: true完成 Cloudflare Tunnel 的部署后,还需要更新一下雷池的配置,让雷池可以读取 Cloudflare 传递过来的客户端 IP。
首先打开雷池「防护应用」页面:

点击右上角的「高级配置」,修改源 IP 获取方式为「从 HTTP Header 中获取」,并将 Header 设置为 「CF-Connection-IP」。

添加公网应用
假设我们已经通过飞牛的 Docker 管理工具部署了一个 openlist,并且该服务也加入了 apps 网络。我们可以先打开雷池的「防护应用」页面,在右上角点击「添加应用」:

这里有几点需要注意,域名须填写用于在公网访问的域名,但上游服务器要使用 apps 内网中的 docker service name。
这里不需要启用 HTTPS,因为 cloudflared 跟雷池的网关都运行在受控制的环境下,启用 HTTPS 没有太多好处,反而配置跟运维会变得复杂。
添加雷池的应用后,就需要去 Cloudflare 的面板添加一条从公网导向局域网的路由:

进入 Tunnel 的编辑页面,切换到 「Published application routes」页面,点击「Add a published appIication route」。

填写子域名后将服务类型设置为 HTTP,并设置 URL 为 tengine:80 (这就是雷池网关在 apps 网络中的地址)。这样,运行在内网的应用就可以比较安全的面向公网提供服务了。
最大的安全隐患
说实话,允许公网访问就是最大的安全隐患,要消除这个隐患,最简单有效的做法就是使用 VPN 并关闭任何通过公网直接访问的途径。
如果你跟我一样都有不得不对公网提供服务的需求,那么配置一些简单的防火墙安全规则,就可以有效防御来自公网的攻击。
发表回复