docs(agent): 记录 auth_user 血泪教训 + 客户端隧道前置 + 验收须真连接

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-07-24 07:39:39 +08:00
parent 2abe4d0816
commit 344dfe9a82
3 changed files with 22 additions and 7 deletions
+11 -4
View File
@@ -114,10 +114,11 @@
<pre><code>"route": {
"rules": [
{"action": "sniff"},
{"action": "resolve"},
{"user": ["&lt;我的uuid…&gt;"], "domain": ["brain.51yanmei.com","git.51yanmei.com"],
{"auth_user": ["&lt;我的uuid…&gt;"], "domain": ["brain.51yanmei.com","git.51yanmei.com"],
"outbound": "direct"},
{"user": ["&lt;我的uuid…&gt;"], "ip_cidr": ["182.92.213.171/32"],
{"auth_user": ["&lt;我的uuid…&gt;"], "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"
}</code></pre>
<p class="small">已用本机 sing-box 1.13.13 <code>sing-box check</code> 验证形状合法(exit 0)。</p>
<div class="card bad">
<h3>血泪教训:VLESS 只认 <code>auth_user</code>,不认 <code>user</code>2026-07-23 生产验证)</h3>
<p>放行规则匹配凭证必须用 <b><code>auth_user</code></b> 而非 <code>user</code><code>sing-box check</code> 对两者<b>都语法通过</b>,但 <code>user</code> 字段对 VLESS/REALITY 入站<b>运行时根本不匹配</b>——放行规则永不命中,结果<b>连白名单用户也被兜底拒绝</b>(全员进不去)。上线时正是踩了这个:节点端 git 一直 000,把本机 uuid 换着法加进白名单都没用。本地起一对真 VLESS 实例实测才定位:<code>auth_user:["good"]</code> 生效(good 通/bad 被 block),<code>user</code> 不生效。<b>只跑 <code>sing-box check</code> 不足以验收访问控制,必须真连接跑一次。</b></p>
</div>
<p class="small">已用本机 sing-box 1.13.13 <b>真 VLESS 连接</b>验证 <code>auth_user</code> 匹配 + <code>resolve</code> 后 ip_cidr/domain 仍匹配;节点端以 git 做「白名单一进一出」验证 per-user 放行/拒绝生效。</p>
<div class="card root">
<h3>规则顺序的三条硬约束</h3>
@@ -206,7 +211,8 @@ journalctl -u pangolin-agent -n 20 # 确认已重渲染、无 ACL ERROR</c
<li>渲染:四态矩阵(ACL×WARP 开关)逐一断言 route 结构</li>
<li>顺序:<code>sniff</code> 唯一且最先;放行先于拒绝;ACL 全部先于 WARP</li>
<li>对称性:同一 target 的放行与拒绝规则,目的地条件逐字相同</li>
<li>产物合法性:渲染结果喂 <code>sing-box check</code> 通过</li>
<li>产物合法性:渲染结果喂 <code>sing-box check</code> 通过<b>注意:check 只验语法,不验 <code>auth_user</code> 运行时是否真匹配 VLESS——见 §4.2 血泪教训</b></li>
<li><b>运行时匹配(不可省)</b>:起真 VLESS 连接实测 <code>auth_user</code> 放行/拒绝生效;或节点端以某个白名单域名做「本机 uuid 一进一出」验证 per-user 生效。仅 <code>sing-box check</code> 绿=未验收。</li>
</ul>
<h3>上线验收</h3>
<ol>
@@ -235,6 +241,7 @@ journalctl -u pangolin-agent -n 20 # 确认已重渲染、无 ACL ERROR</c
<li><span class="tag warn">泄露</span> 节点配置里我的几台设备 uuid 会出现在同一条规则中(自我关联),且目的地明文可见。目的地本就在公网 DNS 里,泄露面小;但这确实<b>弱化了 <code>render.go:14</code> 的「节点只见不透明 dp_uuid」不变式</b>,属于知情接受,需在该注释处补一行说明。</li>
<li><span class="tag warn">缺闸</span> 守红线的 <code>assertNoIdentityFields</code><code>agentd/singbox_test.go</code>)只作用于 <code>state.json</code><b>从不检查渲染出的 sing-box 配置</b>。本设计不扩大这个缺口,但也没有补上——补闸另开。</li>
<li><span class="tag bad">架构限制</span> <b>443 vhost 的判定依赖 SNI,可被绕过。</b><code>brain</code>/<code>git</code>,节点侧唯一的区分手段是 sniff 出的 SNI。攻击者(持有效 dp_uuid 的其他 pangolin 用户)向 <code>182.92.213.171:443</code> 发起<b>不带 SNI</b> 的 TLS、握手后用 <code>Host: brain.51yanmei.com</code> 头访问,则不匹配任何 <code>domain</code> 规则、也不匹配 <code>ip_cidr</code>443 不在端口清单里)→ 落 <code>final:direct</code> → nginx 按 Host 路由放行。这在 sing-box 层<b>无法闭合</b>——不能整封 <code>:443</code>,否则 jiu/travel/sudoku/pay 一起死。因此下面这条 brain 鉴权不是「可选纵深」,而是本闸对 brain 的<b>前置条件</b></li>
<li><span class="tag warn">客户端前置</span> <b>私有域名必须走隧道,否则本闸无从谈起。</b>节点 ACL 只对经隧道进入 sing-box 的流量生效;若客户端把 <code>brain</code>/<code>nas</code> 直连(smartRoute 把国内 IP 分流成直连),则流量根本不到节点、直接打 ali,brain 得 403(nginx deny 非白名单源 IP)、nas 超时。故 <code>brain/nas/git/win.51yanmei.com</code> 必须在控制面 <code>PANGOLIN_PRIVATE_SPLIT_DOMAINS</code> 里(已配),且<b>客户端改动后要重连一次</b>才拿到新分流规则。上线验证时 git 走隧道正常、brain/nas 因客户端未重连仍直连——排查时先确认「域名是否真走了隧道」(curl -v 看连的是不是 pangolin 出口),再判 ACL。</li>
<li><span class="tag ok">已做(2026-07-23</span> <b>brain 已加 nginx basic auth</b> 作纵深防御:ali 的 <code>/etc/nginx/conf.d/brain.conf</code> 在原有 <code>allow 103.119.13.48; deny all</code> 之上叠加 <code>auth_basic</code><code>satisfy</code> 默认 <code>all</code> → 源 IP 白名单 <b></b> 口令二者都需满足),口令存 Bitwarden「brain basic auth」,htpasswd 仅存 apr1 哈希(明文不落 ali)。acme 通道(:80)与 <code>robots.txt</code> 免密。这样即便本节点 ACL 闸失效(agent 挂了、配置手抖)或被上面的 SNI 手法绕过,brain——唯一无自带鉴权的私有服务——仍不裸奔。</li>
</ul>