8.9 KiB
Cloudflare 锁定源站 实操指南(配合 P0 修复)
目标:让
trust_proxy = true安全生效,同时不维护 Cloudflare 庞大的 CIDR 清单。 代码侧(internal/shared/ip.go)已改为:优先信任CF-Connecting-IP,仅在 TCP 对端落在trusted_proxies(你自己的基础设施)时才解析X-Forwarded-For/X-Real-IP。 关键前提:源站必须只能从 Cloudflare 抵达,这样攻击者无法直连并伪造CF-Connecting-IP。
0. 为什么必须锁源站
上一轮的 P0 风险是:开启 trust_proxy = true 后,只要攻击者能直连到源站 IP,
他就可以在请求里塞一个 CF-Connecting-IP: 他想封的任意 IP,应用会信以为真。
后果:自动封禁、限流、IP 黑名单全部按攻击者指定的 IP 计数 → 可把正常访客(甚至你自己的 IP)打进黑名单。
两种解法都不需要 Cloudflare CIDR 白名单,只是「让攻击者根本连不到源站 / 连到也过不了 TLS」:
- 方案 A:Cloudflare Tunnel —— 源站不开放任何公网入站端口,
cloudflared主动出站连 Cloudflare, 外网流量只走 CF→隧道→本地。攻击者不知道、也连不到源站 IP。最省事,推荐。 - 方案 B:Authenticated Origin Pull(mTLS) —— 源站要求 Cloudflare 回源时出示客户端证书, 没证书的直连在 TLS 握手阶段就被拒,连 HTTP 头都发不出来。保留你现有公网 IP/端口时选它。
1. 方案 A:Cloudflare Tunnel(推荐)
1.1 原理
源站防火墙只放行运维端口(如 22),不对外开 80/443。Cloudflare 边缘与源站上的
cloudflared 之间建立一条持久出站隧道。访客的请求在 CF 边缘进入,经隧道到达本地
cloudflared,再转发给监听在 127.0.0.1 的 TamaBox。CF 在边缘做 TLS,所以回源用 HTTP 即可。
1.2 步骤
- 前提:域名已在 Cloudflare(橙色云代理),源站能主动出网(TCP 443 出站)。
- 安装 cloudflared(二选一):
- Debian/Ubuntu:
sudo apt install cloudflared(或官方 deb:curl -fsSL https://pkg.cloudflare.com/cloudflared.deb | sudo dpkg -i -) - Docker:
docker pull cloudflare/cloudflared:latest
- Debian/Ubuntu:
- 登录授权(浏览器选域):
cloudflared tunnel login - 建隧道:
cloudflared tunnel create tamabox记下输出的 tunnel ID 与 credentials 文件路径(默认~/.cloudflared/<id>.json)。 - 写配置
~/.cloudflared/config.yml:tunnel: <你的 tunnel id> credentials-file: /root/.cloudflared/<id>.json ingress: - hostname: yourdomain.com # 改成你的域名 service: http://127.0.0.1:8080 # 改成 TamaBox 实际监听地址(见 1.4) - service: http_status:404 - 绑定 DNS(自动加 CNAME 到 Cloudflare):
cloudflared tunnel route dns tamabox yourdomain.com - 常驻运行:
- systemd:
sudo cloudflared service install然后sudo systemctl enable --now cloudflared - Docker:
docker run -d --name cf-tunnel --restart unless-stopped -v ~/.cloudflared:/root/.cloudflared cloudflare/cloudflared tunnel run
- systemd:
- 收紧源站防火墙:云安全组 /
iptables把 80、443 的 0.0.0.0/0 入站全部删掉, 只保留你的运维 IP 的 22。这样即便有人探测到源站 IP 也连不上。
1.3 app.ini 配置 glue
[security]
trust_proxy = true
trusted_proxies = 127.0.0.1
说明:cloudflared 连的是本地,ResolveClientIP 看到的 TCP 对端就是 127.0.0.1;
CF-Connecting-IP 由 Cloudflare 注入并经隧道透传,应用优先采用它,无需任何 CIDR。
1.4 TamaBox 监听地址建议
conf/app.ini 的 [server] port = 80。务必让程序只绑本地:
- 若用
tamabox.go里flamego.Classic()默认0.0.0.0,请改成127.0.0.1:8080(或你 tunnel 里写的端口), 双保险——即使防火墙漏配也不会对外暴露。 - 改完重启 TamaBox。
2. 方案 B:Authenticated Origin Pull(mTLS)
2.1 原理
Cloudflare 回源时出示一张由 Cloudflare 私有 CA 签发的客户端证书;源站配置「要求并校验客户端证书」。
attackers 直接连源站 443 时,因为没有这张证书,TLS 握手直接失败,根本到不了 HTTP 层,
自然无法发送 CF-Connecting-IP。这步本身已足以挡住伪造,不需要 CIDR。
2.2 步骤
- 开启 AOP:Cloudflare 控制台 → SSL/TLS → Origin Server → Authenticated Origin Pulls → 开启。
- 下载 Cloudflare Origin CA(用于校验 CF 客户端证):同一页底部
Origin CA certificates → Create Certificate(或下载根 CA 证书
cloudflare_origin_ca.pem), 保存到源站/etc/ssl/cloudflare/cf-origin-ca.pem,权限600。 - 落地方式二选一:
- B1(nginx 在前,最常见) —— nginx 终止 TLS 并要求客户端证书:
server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/ssl/yourdom/fullchain.pem; # 源站证书(可用 CF Origin CA 签发,或自有) ssl_certificate_key /etc/ssl/yourdom/privkey.pem; ssl_client_certificate /etc/ssl/cloudflare/cf-origin-ca.pem; # Cloudflare 根 CA ssl_verify_client on; # 必须校验 Cloudflare 客户端证书 ssl_verify_depth 2; location / { proxy_pass http://127.0.0.1:8080; # TamaBox 本地端口 proxy_set_header Host $host; proxy_set_header CF-Connecting-IP $http_cf_connecting_ip; # 透传给应用(优先采用) proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }sudo nginx -t && sudo systemctl reload nginx - B2(Go 直接对外 TLS,不用 nginx) —— 让 TamaBox 监听 TLS 并强制客户端证书
(需改启动代码,示意):
srv := &http.Server{ Addr: ":443", TLSConfig: &tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: cfCACertPool, // 载入 cf-origin-ca.pem }, } srv.ServeTLS(ln, "/etc/ssl/yourdom/fullchain.pem", "/etc/ssl/yourdom/privkey.pem")
- B1(nginx 在前,最常见) —— nginx 终止 TLS 并要求客户端证书:
- 防火墙(可选加固):源站 443 只放行 Cloudflare 回源 IP。但 AOP 的 mTLS 已挡住伪造, CIDR 只是减少无效握手,可按需省略——正好契合你「不想维护大 CIDR」的诉求。
2.3 app.ini 配置 glue
- 用 nginx(B1):
trust_proxy = true,trusted_proxies = 127.0.0.1(nginx 在本地,TCP 对端127.0.0.1)。CF-Connecting-IP经 nginx 透传被应用优先采用; 即便没透传,XFF 由受信的127.0.0.1解析也能拿到真实 IP。 - 用 Go 直连 TLS(B2):
trust_proxy = true,trusted_proxies可留空 (TCP 对端就是 Cloudflare,CF-Connecting-IP直接采用)。
3. 怎么选
| 场景 | 选 |
|---|---|
| 单实例、怕配错、不想管证书/防火墙 | 方案 A(Tunnel) |
| 已有 nginx、必须保留公网 IP、希望源站也强制 TLS | 方案 B(AOP) |
4. 验证(上线前务必做)
- 直连源站公网 IP 应失败:
- A:
curl -I https://<源站IP>应超时 / 连接拒绝。 - B:
curl -I https://<源站IP>应 TLS 握手失败(sslv3 alert handshake failure之类)。
- A:
- 经域名访问正常:
curl -I https://yourdomain.com返回 200。 - 确认记录的是真实客户端 IP:用不同网络访问,查看限流/统计/日志, 不应再是 Cloudflare 边缘 IP。
- 伪造头测试(仅限你自己的 IP):
curl -H "CF-Connecting-IP: 1.2.3.4" https://yourdomain.com—— 应用应忽略该头,记录你的真实 IP。 - 自动封禁仍生效:连续触发 404 阈值,确认被封的是真实客户端 IP,不是 CF 边缘。
5. 注意事项
trust_proxy = true一旦开启,源站就必须锁定,否则比false更危险 (应用会更信任客户端头)。代码注释已写明此约束。- Tunnel 下建议 TamaBox 监听
127.0.0.1(不要0.0.0.0),双保险。 - 隧道 credentials 与证书文件权限
600,不要进 Git 仓库。 - 多实例 / 容器:把
cloudflared跑在与 TamaBox 能直连的网络(同主机或同 docker 网络)。 - 证书 / 隧道有有效期,纳入监控或自动续期(Cloudflare 证书默认较长,Tunnel 凭证基本免维护)。
6. 与代码的对应关系(便于回溯)
internal/shared/ip.go:ResolveClientIP优先CF-Connecting-IP,仅isTrustedProxy(remote)才解析 XFF。internal/conf/static.go:Security.TrustedProxies(trusted_proxies)新字段。internal/cmd/web.go:conf.Init()后调用shared.InitTrustedProxies()解析清单。conf/app.sample.ini:trust_proxy/trusted_proxies注释已更新。