#!/usr/bin/env bash # TamaBox Cloudflare Tunnel 部署安全自检脚本 # # 架构前提: # 访客 → Cloudflare 边缘(a.b.com 自定义主机名证书 / a.a.com 主域) # → cloudflared Tunnel(源站仅出站连接)→ 127.0.0.1:5877 (TamaBox) # # 用法: # 第一部分(在源站服务器上执行): ./security-selfcheck.sh local # 第二部分(在你自己的电脑上执行):SERVER_IP=源站公网IP CF_DOMAIN=a.a.com CUSTOM_DOMAIN=a.b.com ./security-selfcheck.sh remote # 两部分都跑: ./security-selfcheck.sh all # set -u MODE="${1:-all}" SERVER_IP="${SERVER_IP:-}" CF_DOMAIN="${CF_DOMAIN:-a.a.com}" CUSTOM_DOMAIN="${CUSTOM_DOMAIN:-a.b.com}" APP_PORT="${APP_PORT:-5877}" section() { printf "\n========== %s ==========\n" "$1"; } ok() { echo "[✓] $1"; } warn() { echo "[!] $1"; } bad() { echo "[✗] $1"; } # 用 bash /dev/tcp 探测 TCP 端口是否可达(3 秒超时) port_open() { (timeout 3 bash -c "echo > /dev/tcp/$1/$2") 2>/dev/null } # ---------------------------------------------------------------- # 第一部分:源站本地检查 # ---------------------------------------------------------------- local_checks() { section "1. 应用与数据库端口绑定检查(核心)" BIND=$(ss -tlnp 2>/dev/null | grep ":${APP_PORT} " || true) echo "$BIND" if echo "$BIND" | grep -qE "127\.0\.0\.1:${APP_PORT}|\[::1\]:${APP_PORT}"; then ok "TamaBox 只绑定在 127.0.0.1:${APP_PORT},外部无法直连(正确)" elif [ -z "$BIND" ]; then warn "端口 ${APP_PORT} 未监听——确认 TamaBox 是否在运行" else bad "TamaBox 绑定在 0.0.0.0/*:${APP_PORT}(监听所有网卡)!" echo " 修复:conf/app.ini [server] 段设置 listen = 127.0.0.1:${APP_PORT} 并重启" fi section "2. PostgreSQL / Redis 是否暴露公网" for p in 5432 6379; do BIND=$(ss -tlnp 2>/dev/null | grep ":${p} " || true) if echo "$BIND" | grep -qE "0\.0\.0\.0:${p}|\*:${p}"; then bad "端口 ${p} 绑定在所有网卡!必须改为 127.0.0.1 或加防火墙(云数据库直接暴露=高危)" else ok "端口 ${p} 未对外监听(${BIND:-未运行})" fi done section "3. cloudflared 状态与 ingress 配置" if systemctl is-active --quiet cloudflared 2>/dev/null || pgrep -x cloudflared >/dev/null 2>&1; then ok "cloudflared 正在运行" else bad "cloudflared 未运行——Tunnel 断开,所有域名不可达" fi CFG="" for f in /etc/cloudflared/config.yml /root/.cloudflared/config.yml /home/*/.cloudflared/config.yml; do [ -f "$f" ] && CFG="$f" && break done if [ -n "$CFG" ]; then echo "配置文件:$CFG" grep -nE "hostname|service|originRequest" "$CFG" || true if grep -qE "service:\s*http://127\.0\.0\.1:${APP_PORT}|localhost:${APP_PORT}" "$CFG"; then ok "ingress service 指向 127.0.0.1:${APP_PORT}(正确)" else warn "未在 config.yml 中找到指向 127.0.0.1:${APP_PORT} 的 service,请人工核对 ingress 规则" fi else warn "未找到 config.yml(若用 token 模式 + 远端 ingress,请到 Cloudflare 面板核对 Public Hostname 的 service 地址)" fi section "4. 本地请求验证(模拟 cloudflared 的视角)" RESP=$(curl -s -m 5 -o /dev/null -w "%{http_code}" "http://127.0.0.1:${APP_PORT}/" || true) if [ "$RESP" = "200" ] || [ "$RESP" = "302" ] || [ "$RESP" = "303" ]; then ok "本地 HTTP 请求返回 $RESP,应用正常" else warn "本地请求返回 ${RESP:-无响应},请人工确认" fi section "5. 防火墙状态(仅供参考)" if command -v ufw >/dev/null 2>&1; then ufw status verbose 2>/dev/null | head -15 || true elif command -v firewall-cmd >/dev/null 2>&1; then firewall-cmd --list-all 2>/dev/null || true else iptables -L INPUT -n --line-numbers 2>/dev/null | head -15 || true fi warn "请人工确认:除 SSH(22) 外,80/443/5877 等端口不应有公网入站放行规则" } # ---------------------------------------------------------------- # 第二部分:外部探测(在自己的电脑上执行) # ---------------------------------------------------------------- remote_checks() { if [ -z "$SERVER_IP" ]; then bad "未设置 SERVER_IP 环境变量,跳过直连探测(用法见文件头注释)" else section "6. 直连源站 IP 探测(核心:防 IP+端口直连)" for p in "$APP_PORT" 80 443 5432 6379; do if port_open "$SERVER_IP" "$p"; then case "$p" in 22) warn "端口 22 开放(SSH,正常,但建议改密钥登录/限来源 IP)" ;; *) bad "端口 ${p} 从公网可达!存在被直连/攻击面,去服务器防火墙封掉它" ;; esac else ok "端口 ${p} 从公网不可达" fi done fi section "7. 经域名的正常访问验证" for d in "$CF_DOMAIN" "$CUSTOM_DOMAIN"; do CODE=$(curl -s -m 10 -o /dev/null -w "%{http_code}" "https://${d}/" || true) RAY=$(curl -sI -m 10 "https://${d}/" | grep -i "^cf-ray:" || true) if [ -n "$RAY" ]; then ok "https://${d}/ 返回 ${CODE:-?},且带 ${RAY%%$'\r'} → 确认流量经过 Cloudflare 边缘" elif [ "$CODE" = "200" ] || [ "$CODE" = "302" ] || [ "$CODE" = "303" ]; then warn "https://${d}/ 返回 ${CODE} 但没有 cf-ray 头 → 流量可能没走 Cloudflare(自定义主机名未生效?)" else bad "https://${d}/ 不可达(${CODE:-无响应})" fi done section "8. 伪造头穿透测试(验证 Cloudflare 会重写伪造头)" # 若伪造头被 CF 覆盖,应用侧记录的仍是真实 IP;此测试主要用于演示,无法从响应直接观察, # 需配合 TamaBox 侧日志/封禁记录核对(见第 9 项说明)。 curl -s -m 10 -o /dev/null \ -H "CF-Connecting-IP: 203.0.113.66" \ -H "X-Forwarded-For: 203.0.113.66" \ "https://${CUSTOM_DOMAIN}/" || true ok "已发送带伪造 CF-Connecting-IP 的请求(CF 边缘会将其重写为你的真实 IP)" section "9. HTTP 明文与 HSTS 检查" LOC=$(curl -sI -m 10 "http://${CUSTOM_DOMAIN}/" | grep -iE "^(HTTP|location):" | tr -d '\r') echo "$LOC" HSTS=$(curl -sI -m 10 "https://${CUSTOM_DOMAIN}/" | grep -i "strict-transport-security" | tr -d '\r') if [ -n "$HSTS" ]; then ok "$HSTS"; else warn "未返回 HSTS 头(若浏览器直访 http 可能被中间人,建议确认 securityHeaders 已生效)"; fi } case "$MODE" in local) local_checks ;; remote) remote_checks ;; all) local_checks; remote_checks ;; *) echo "用法: $0 {local|remote|all}"; exit 1 ;; esac section "自检完成" echo "逐项核对上面的 [✓]/[!]/[✗],所有 [✗] 都需要修复后再跑一遍。"