把散落在代码里的真实部署值统一收进 config/,代码改为引用配置, 使仓库副本/开源导出不再带出生产域名、IP、内网路径与凭据。 新增 config/:platform.env.example(模板)· load.sh(shell 加载器)· index.cjs(node 加载器)· README.md(键一览与优先级)。 真实值放 config/platform.env —— 已 .gitignore 排除,不入库、不进导出。 TS 侧新增 src/platform-paths.ts 作部署路径的唯一解析处(零副作用): platformDir/stateDir/backupDir/artifactDir/installDir/scriptPath。 config.ts 接入这些字段;内置中继种子由生产 URL 改为空(改由 DSHS_OVERLAY_BOOTSTRAP_SEEDS 提供)。修掉 5 处硬编码绝对路径, src/** 注释中性化 116 行/53 文件。 scripts/** 36 个内部运维脚本:真令牌/PG 口令/隧道目标/主机号/路径 一律改从配置取;web/wake.html 的注册域白名单改为运行时从 location.hostname 推导;test/** 夹具 119 行/13 文件改 RFC 2606/5737 保留值,并把「内置种子必须为空」固化为回归断言。 取证:tsc 0 错;npm test 373/375(唯一失败 lease 属既有); 全仓扫描(大小写不敏感)代码面涉密标识 = 0;已部署 47 并零回归 (/opt/dsh/* 未搬家,/var/lib/dshs/platform 未被误建)。
120 lines
5.6 KiB
Bash
120 lines
5.6 KiB
Bash
#!/bin/bash
|
||
# 一键复现 dsh 实例出网护栏(/etc/nftables-dsh-egress.nft + dsh-egress.service)
|
||
#
|
||
# 档案 14 · H1 阻断云元数据端点(100.100.100.200)
|
||
# 档案 14 · H4 观测实例新建外联(只记录不拦截)
|
||
# 档案 39 · H5 封锁实例**主动**访问宿主自身(loopback / eth0 / docker0 / 公网 EIP)
|
||
#
|
||
# 用法(需 root):bash scripts/install-egress-guard.sh
|
||
# 回滚:systemctl disable --now dsh-egress
|
||
#
|
||
# ⚠️ 改 H5 前必读档案 39「事故/踩坑记录」:**不能只按目的地址匹配** ——
|
||
# 实例是「先收后回」的服务端,回包目的地址同样是 127.0.0.1,
|
||
# 按 daddr 一刀切会把回包一起拒掉 → nginx 无法回源、实例整体不可用
|
||
# (判定特征:root 连实例端口 **timeout 而非 refused**)。
|
||
# 必须只匹配主动发起:TCP 用纯 SYN,UDP 用 ct state new。
|
||
set -euo pipefail
|
||
|
||
if [ "$(id -u)" != "0" ]; then
|
||
echo "需要 root" >&2
|
||
exit 1
|
||
fi
|
||
|
||
. "$(cd "$(dirname "$0")/.." && pwd)/config/load.sh"
|
||
# ⛔ 本机地址不写死在 nft 规则里:规则体里用占位符,写完文件再注入实际值。
|
||
HOST_ADDRS=""
|
||
for a in "$DSH_HOST_LAN_IP" "$DSH_HOST_PUBLIC_IP"; do
|
||
[ -n "$a" ] && HOST_ADDRS="${HOST_ADDRS:+$HOST_ADDRS, }$a"
|
||
done
|
||
|
||
echo "==> 写入 /etc/nftables-dsh-egress.nft"
|
||
cat > /etc/nftables-dsh-egress.nft <<'NFTEOF'
|
||
#!/usr/sbin/nft -f
|
||
# dsh 实例出网护栏
|
||
# 档案 14 · H1(云元数据端点)+ H4(外联观测)
|
||
# 档案 39 · H5(实例不得「主动」访问宿主自身 —— 切断宿主服务面与跨租户互通)
|
||
# 只作用于 dsh 实例 uid 段 100000-199999;root / 云监控 / Docker 不受影响。
|
||
#
|
||
# 坑 1:阿里云 DNS 是 100.100.2.136 / 100.100.2.138 —— 绝不能封 100.100.0.0/16 整段,
|
||
# 本文件只精确封元数据端点 100.100.100.200(/32)。
|
||
# 坑 2:Docker 用 iptables-nft,本表独立,不与 docker 链混用。
|
||
# 坑 3:观测要排除 53 端口 —— nft 日志不记录查询域名,DNS 行是纯噪音且量最大。
|
||
# 坑 4(档案 39,实测踩到):H5 **绝不能只按目的地址匹配**。实例是「先收后回」的服务端:
|
||
# nginx(root) → 实例端口的 SYN 合法,但实例回的 SYN-ACK 与后续数据包
|
||
# **目的地址同样是 127.0.0.1**(客户端就是本机)。若按 daddr 一刀切 reject,
|
||
# 回包会被一起拒掉 → root 连实例端口 **超时**、nginx 无法回源、实例整体不可用。
|
||
# → 必须只匹配**实例主动发起**的连接:TCP 用纯 SYN(`tcp flags & (fin|syn|rst|ack) == syn`,
|
||
# SYN-ACK 带 ack 位故不匹配),UDP 用 `ct state new`。
|
||
table ip dsh_egress {
|
||
chain output {
|
||
type filter hook output priority filter; policy accept;
|
||
|
||
# H1 · 阻断云元数据端点(先记录再拒绝)
|
||
meta skuid 100000-199999 ip daddr 100.100.100.200 log prefix "dsh-egress-BLOCK " level warn
|
||
meta skuid 100000-199999 ip daddr 100.100.100.200 counter reject
|
||
|
||
# H5 · 实例不得主动访问宿主自身(档案 39)
|
||
# 封 4 类目的地址(仅实例主动发起的连接):
|
||
# 127.0.0.0/8 → 宿主全部 loopback 服务(sshd 22/32022、nginx 80/443/888、
|
||
# 门户 3080、BT-Panel 58888/8765)+ 其他实例的 127.0.0.1 监听
|
||
# <host-lan-ip> → 宿主内网卡(eth0,同样到达 nginx/面板)
|
||
# 172.17.0.1 → docker0 网桥网关
|
||
# <server-public-ip> → 公网 EIP(回环到本机 nginx/sshd)
|
||
# 不影响:公网访问、阿里云内网 DNS(100.100.2.136/138)、pip/npm 下载、
|
||
# 实例自身监听端口、nginx 回源(root 发包 + 实例回包带 ack)
|
||
meta skuid 100000-199999 ip daddr { 127.0.0.0/8, 172.17.0.1, __HOST_ADDRS__ } \
|
||
meta l4proto tcp tcp flags & (fin|syn|rst|ack) == syn \
|
||
counter log prefix "dsh-egress-HOST " level warn limit rate 20/minute
|
||
meta skuid 100000-199999 ip daddr { 127.0.0.0/8, 172.17.0.1, __HOST_ADDRS__ } \
|
||
meta l4proto tcp tcp flags & (fin|syn|rst|ack) == syn \
|
||
counter reject with tcp reset
|
||
meta skuid 100000-199999 ip daddr { 127.0.0.0/8, 172.17.0.1, __HOST_ADDRS__ } \
|
||
meta l4proto udp ct state new counter reject
|
||
|
||
# H4 · 观测实例新建外联(先记录不拦截;排除 loopback 与 DNS 降噪)
|
||
meta skuid 100000-199999 ip daddr != 127.0.0.0/8 tcp dport != 53 ct state new counter log prefix "dsh-egress " level info
|
||
meta skuid 100000-199999 ip daddr != 127.0.0.0/8 udp dport != 53 ct state new counter log prefix "dsh-egress " level info
|
||
}
|
||
}
|
||
NFTEOF
|
||
|
||
# 注入本机地址(来自 config/platform.env);未配 ⇒ 摘掉该占位符,规则只剩回环与 docker 网桥。
|
||
if [ -n "$HOST_ADDRS" ]; then
|
||
sed -i "s|__HOST_ADDRS__|$HOST_ADDRS|g" /etc/nftables-dsh-egress.nft
|
||
else
|
||
sed -i "s|, __HOST_ADDRS__||g" /etc/nftables-dsh-egress.nft
|
||
fi
|
||
|
||
echo "==> 写入 /etc/systemd/system/dsh-egress.service"
|
||
cat > /etc/systemd/system/dsh-egress.service <<'SVCEOF'
|
||
[Unit]
|
||
Description=DSH instance egress guard (nftables)
|
||
Documentation=file:/etc/nftables-dsh-egress.nft
|
||
After=network-pre.target
|
||
Before=network.service
|
||
Wants=network-pre.target
|
||
|
||
[Service]
|
||
Type=oneshot
|
||
RemainAfterExit=yes
|
||
# 幂等:nft -f 遇到已存在的表会报 File exists,故先删(忽略不存在时的报错)
|
||
ExecStartPre=-/usr/sbin/nft delete table ip dsh_egress
|
||
ExecStart=/usr/sbin/nft -f /etc/nftables-dsh-egress.nft
|
||
ExecStop=-/usr/sbin/nft delete table ip dsh_egress
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
SVCEOF
|
||
|
||
echo "==> 语法预检"
|
||
nft -c -f /etc/nftables-dsh-egress.nft
|
||
|
||
echo "==> 启用并重载"
|
||
systemctl daemon-reload
|
||
systemctl enable dsh-egress
|
||
systemctl restart dsh-egress
|
||
|
||
echo "==> 当前规则"
|
||
nft list table ip dsh_egress
|
||
echo "OK"
|