# 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 步骤 1. **前提**:域名已在 Cloudflare(橙色云代理),源站能主动出网(TCP 443 出站)。 2. **安装 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` 3. **登录授权**(浏览器选域):`cloudflared tunnel login` 4. **建隧道**:`cloudflared tunnel create tamabox` 记下输出的 tunnel ID 与 credentials 文件路径(默认 `~/.cloudflared/.json`)。 5. **写配置** `~/.cloudflared/config.yml`: ```yaml tunnel: <你的 tunnel id> credentials-file: /root/.cloudflared/.json ingress: - hostname: yourdomain.com # 改成你的域名 service: http://127.0.0.1:8080 # 改成 TamaBox 实际监听地址(见 1.4) - service: http_status:404 ``` 6. **绑定 DNS**(自动加 CNAME 到 Cloudflare):`cloudflared tunnel route dns tamabox yourdomain.com` 7. **常驻运行**: - 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` 8. **收紧源站防火墙**:云安全组 / `iptables` 把 **80、443 的 0.0.0.0/0 入站全部删掉**, 只保留你的运维 IP 的 22。这样即便有人探测到源站 IP 也连不上。 ### 1.3 app.ini 配置 glue ```ini [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 步骤 1. **开启 AOP**:Cloudflare 控制台 → **SSL/TLS → Origin Server → Authenticated Origin Pulls** → 开启。 2. **下载 Cloudflare Origin CA**(用于校验 CF 客户端证):同一页底部 **Origin CA certificates → Create Certificate**(或下载根 CA 证书 `cloudflare_origin_ca.pem`), 保存到源站 `/etc/ssl/cloudflare/cf-origin-ca.pem`,权限 `600`。 3. **落地方式二选一**: - **B1(nginx 在前,最常见)** —— nginx 终止 TLS 并要求客户端证书: ```nginx 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 并强制客户端证书 (需改启动代码,示意): ```go 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") ``` 4. **防火墙(可选加固)**:源站 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. 验证(上线前务必做) 1. **直连源站公网 IP 应失败**: - A:`curl -I https://<源站IP>` 应超时 / 连接拒绝。 - B:`curl -I https://<源站IP>` 应 TLS 握手失败(`sslv3 alert handshake failure` 之类)。 2. **经域名访问正常**:`curl -I https://yourdomain.com` 返回 200。 3. **确认记录的是真实客户端 IP**:用不同网络访问,查看限流/统计/日志, 不应再是 Cloudflare 边缘 IP。 4. **伪造头测试(仅限你自己的 IP)**: `curl -H "CF-Connecting-IP: 1.2.3.4" https://yourdomain.com` —— 应用应忽略该头,记录你的真实 IP。 5. **自动封禁仍生效**:连续触发 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` 注释已更新。