From 344dfe9a82f00836b0e49ec2deaeee2aa067da88 Mon Sep 17 00:00:00 2001 From: wangjia <809946525@qq.com> Date: Fri, 24 Jul 2026 07:39:39 +0800 Subject: [PATCH] =?UTF-8?q?docs(agent):=20=E8=AE=B0=E5=BD=95=20auth=5Fuser?= =?UTF-8?q?=20=E8=A1=80=E6=B3=AA=E6=95=99=E8=AE=AD=20+=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E9=9A=A7=E9=81=93=E5=89=8D=E7=BD=AE=20+=20?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E9=A1=BB=E7=9C=9F=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 --- docs/private-dest-acl-design.html | 15 +++++++++++---- docs/private-dest-acl-plan.html | 12 +++++++++--- .../plans/2026-07-23-private-dest-acl.md | 2 ++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/private-dest-acl-design.html b/docs/private-dest-acl-design.html index 059364c..49069c0 100644 --- a/docs/private-dest-acl-design.html +++ b/docs/private-dest-acl-design.html @@ -114,10 +114,11 @@
"route": {
   "rules": [
     {"action": "sniff"},
+    {"action": "resolve"},
 
-    {"user": ["<我的uuid…>"], "domain": ["brain.51yanmei.com","git.51yanmei.com"],
+    {"auth_user": ["<我的uuid…>"], "domain": ["brain.51yanmei.com","git.51yanmei.com"],
      "outbound": "direct"},
-    {"user": ["<我的uuid…>"], "ip_cidr": ["182.92.213.171/32"],
+    {"auth_user": ["<我的uuid…>"], "ip_cidr": ["182.92.213.171/32"],
      "port": [5001,3389,10022,10023], "outbound": "direct"},
 
     {"domain": ["brain.51yanmei.com","git.51yanmei.com"], "action": "reject"},
@@ -128,7 +129,11 @@
   ],
   "final": "direct"
 }
-

已用本机 sing-box 1.13.13 sing-box check 验证形状合法(exit 0)。

+
+

血泪教训:VLESS 只认 auth_user,不认 user(2026-07-23 生产验证)

+

放行规则匹配凭证必须用 auth_user 而非 usersing-box check 对两者都语法通过,但 user 字段对 VLESS/REALITY 入站运行时根本不匹配——放行规则永不命中,结果连白名单用户也被兜底拒绝(全员进不去)。上线时正是踩了这个:节点端 git 一直 000,把本机 uuid 换着法加进白名单都没用。本地起一对真 VLESS 实例实测才定位:auth_user:["good"] 生效(good 通/bad 被 block),user 不生效。只跑 sing-box check 不足以验收访问控制,必须真连接跑一次。

+
+

已用本机 sing-box 1.13.13 真 VLESS 连接验证 auth_user 匹配 + resolve 后 ip_cidr/domain 仍匹配;节点端以 git 做「白名单一进一出」验证 per-user 放行/拒绝生效。

规则顺序的三条硬约束

@@ -206,7 +211,8 @@ journalctl -u pangolin-agent -n 20 # 确认已重渲染、无 ACL ERROR渲染:四态矩阵(ACL×WARP 开关)逐一断言 route 结构
  • 顺序:sniff 唯一且最先;放行先于拒绝;ACL 全部先于 WARP
  • 对称性:同一 target 的放行与拒绝规则,目的地条件逐字相同
  • -
  • 产物合法性:渲染结果喂 sing-box check 通过
  • +
  • 产物合法性:渲染结果喂 sing-box check 通过(注意:check 只验语法,不验 auth_user 运行时是否真匹配 VLESS——见 §4.2 血泪教训
  • +
  • 运行时匹配(不可省):起真 VLESS 连接实测 auth_user 放行/拒绝生效;或节点端以某个白名单域名做「本机 uuid 一进一出」验证 per-user 生效。仅 sing-box check 绿=未验收。
  • 上线验收

      @@ -235,6 +241,7 @@ journalctl -u pangolin-agent -n 20 # 确认已重渲染、无 ACL ERROR泄露 节点配置里我的几台设备 uuid 会出现在同一条规则中(自我关联),且目的地明文可见。目的地本就在公网 DNS 里,泄露面小;但这确实弱化了 render.go:14 的「节点只见不透明 dp_uuid」不变式,属于知情接受,需在该注释处补一行说明。
    1. 缺闸 守红线的 assertNoIdentityFieldsagentd/singbox_test.go)只作用于 state.json从不检查渲染出的 sing-box 配置。本设计不扩大这个缺口,但也没有补上——补闸另开。
    2. 架构限制 443 vhost 的判定依赖 SNI,可被绕过。brain/git,节点侧唯一的区分手段是 sniff 出的 SNI。攻击者(持有效 dp_uuid 的其他 pangolin 用户)向 182.92.213.171:443 发起不带 SNI 的 TLS、握手后用 Host: brain.51yanmei.com 头访问,则不匹配任何 domain 规则、也不匹配 ip_cidr(443 不在端口清单里)→ 落 final:direct → nginx 按 Host 路由放行。这在 sing-box 层无法闭合——不能整封 :443,否则 jiu/travel/sudoku/pay 一起死。因此下面这条 brain 鉴权不是「可选纵深」,而是本闸对 brain 的前置条件
    3. +
    4. 客户端前置 私有域名必须走隧道,否则本闸无从谈起。节点 ACL 只对经隧道进入 sing-box 的流量生效;若客户端把 brain/nas 直连(smartRoute 把国内 IP 分流成直连),则流量根本不到节点、直接打 ali,brain 得 403(nginx deny 非白名单源 IP)、nas 超时。故 brain/nas/git/win.51yanmei.com 必须在控制面 PANGOLIN_PRIVATE_SPLIT_DOMAINS 里(已配),且客户端改动后要重连一次才拿到新分流规则。上线验证时 git 走隧道正常、brain/nas 因客户端未重连仍直连——排查时先确认「域名是否真走了隧道」(curl -v 看连的是不是 pangolin 出口),再判 ACL。
    5. 已做(2026-07-23) brain 已加 nginx basic auth 作纵深防御:ali 的 /etc/nginx/conf.d/brain.conf 在原有 allow 103.119.13.48; deny all 之上叠加 auth_basicsatisfy 默认 all → 源 IP 白名单 口令二者都需满足),口令存 Bitwarden「brain basic auth」,htpasswd 仅存 apr1 哈希(明文不落 ali)。acme 通道(:80)与 robots.txt 免密。这样即便本节点 ACL 闸失效(agent 挂了、配置手抖)或被上面的 SNI 手法绕过,brain——唯一无自带鉴权的私有服务——仍不裸奔。
    6. diff --git a/docs/private-dest-acl-plan.html b/docs/private-dest-acl-plan.html index dcbda86..d17414e 100644 --- a/docs/private-dest-acl-plan.html +++ b/docs/private-dest-acl-plan.html @@ -59,6 +59,7 @@
    7. 每个 Task 结束必须 cd server && go build ./... && go test ./internal/agentd/... 全绿再提交。
    8. Commit message 前缀用 feat(agent): / test(agent): / refactor(agent):
    9. +

      执行后修订(2026-07-23,提交 2abe4d0)· 血泪教训:放行规则匹配凭证必须用 auth_user 而非本计划各处写的 usersing-box check 对两者都语法通过,但 user 字段对 VLESS/REALITY 入站运行时不匹配,导致放行规则永不命中、连白名单用户也被兜底拒绝(全员进不去)。生产上线时踩中,本地起真 VLESS 连接实测才定位。凡本计划(Task 2 rules()、Task 3 测试)出现 r["user"] = uuids 或断言 r["user"] 之处,一律应为 auth_user 且验收不能只跑 sing-box check——必须真连接跑一次(节点端「白名单 uuid 一进一出」即可)。


      Task 1: ACL 配置类型、加载与 fail-closed 的 active()

      Files:

      @@ -1068,14 +1069,19 @@ git commit -m "docs(agent): 私有目的地 ACL 配置样例与常驻校验
      1. /etc/pangolin-agent/acl.json(权限 0600,属主 pangolin),systemctl reload pangolin-agent
      2. journalctl -u pangolin-agent -n 30 确认已重渲染、无 [acl] ERROR / [acl] ALERT
      3. -
      4. sudo python3 -c "import json;print(json.load(open('/etc/sing-box/config.json'))['route'])" 确认规则顺序为 sniff → 放行 → 拒绝 → warp。
      5. +
      6. sudo python3 -c "import json;print(json.load(open('/etc/sing-box/config.json'))['route'])" 确认规则顺序为 sniff → resolve → 放行 → 拒绝 → warp(resolve 见 I3:堵住 ip_cidr 目的地的域名形式绕过)。
      7. +
      +

      config.json 内容正常不等于线上生效——SIGHUP 走的是 sing-box 自身校验,若新配置被 sing-box 拒绝,它会保留旧实例继续跑,agent 侧仍记"渲染成功"。reload 后必须额外确认:

      +
        +
      • systemctl is-active sing-boxactive
      • +
      • journalctl -u sing-box -n5 | grep -v FATAL(有 FATAL 说明 sing-box 拒绝了新配置,旧实例还在跑,config.json 上的内容其实没生效)。
      • 我的设备:brain 首页 200、DSM 5001 可登录、ssh nas-r 通。
      • -
      • 另一账号的设备:以上全部被拒(连接被 reject,不是超时)。
      • +
      • 另一账号的设备:以上全部被拒(连接被 reject,不是超时)。分别用域名和裸 IP 两种形式各测一遍(如 curl https://brain.51yanmei.comcurl --resolve brain.51yanmei.com:443:103.119.13.48 https://brain.51yanmei.com;DSM 同理分别用 nas.51yanmei.com:5001182.92.213.171:5001)——I3 表明目的地表达形式(域名 vs IP)会走到不同的 sing-box 匹配路径,只测一种形式验证不到位。
      • 公开站不受影响:jiu / travel / sudoku / pay 在两个账号下均正常。
      • WARP 未被破坏:reddit 仍走 warp 出口。
      • fail-closed 实证:把 acl.json 改坏 → systemctl reload pangolin-agent → 规则仍在、日志有 ERROR;恢复文件。
      • 不断线实证:reload 期间另一台设备保持连接不掉。
      • -
    +

    不在本计划范围内