47 lines
2.2 KiB
Bash
47 lines
2.2 KiB
Bash
#!/bin/bash
|
|
# Sandbox container init (DESIGN §7.5 #1 网络 blocklist 协议)。
|
|
#
|
|
# 启动流程:配 iptables OUTPUT blocklist → sleep infinity 等 `docker exec` 进来。
|
|
# 以 root 跑(需要 NET_ADMIN),docker exec 进来的 --user 1000 进程拿不到 cap。
|
|
#
|
|
# Stage C 协议:任一规则 apply 失败 → `set -e` 退出,容器立即终止;视为部署未完成,
|
|
# 上层 ensure() 会 raise。
|
|
set -euo pipefail
|
|
|
|
apply_blocklist() {
|
|
# §7.5 #1 红线段(任一缺失视为 Stage C 未完成):
|
|
iptables -A OUTPUT -d 169.254.0.0/16 -j DROP # cloud metadata (Capital One 2019 SSRF)
|
|
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP # IPv4 loopback (容器回头打宿主端口)
|
|
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP # 内网段 A
|
|
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP # 内网段 B(Docker 默认 bridge 网段在此)
|
|
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP # 内网段 C
|
|
iptables -A OUTPUT -d 100.64.0.0/10 -j DROP # CGNAT(云平台常用)
|
|
|
|
# IPv6 loopback ── ip6tables 在某些精简镜像 / sysctl ipv6.disable_ipv6=1 下不可用,
|
|
# 失败降级为 warn(IPv6 没起的话本来也打不通,容忍)
|
|
if ! ip6tables -A OUTPUT -d ::1 -j DROP 2>/dev/null; then
|
|
echo "[init] ip6tables ::1 rule skipped (ip6tables unavailable or v6 disabled)" >&2
|
|
fi
|
|
|
|
# ZCBOT_PG_IPS=10.x.x.x,172.x.x.x[/prefix],...
|
|
# 部署侧把 PG 实际 IP 显式 block ── 即使已落在内网三段,defense-in-depth(§7.5 #1)。
|
|
if [ -n "${ZCBOT_PG_IPS:-}" ]; then
|
|
IFS=',' read -ra _pg_ips <<< "$ZCBOT_PG_IPS"
|
|
for ip in "${_pg_ips[@]}"; do
|
|
ip_trim="$(echo "$ip" | tr -d '[:space:]')"
|
|
[ -z "$ip_trim" ] && continue
|
|
iptables -A OUTPUT -d "$ip_trim" -j DROP
|
|
echo "[init] blocked PG IP: $ip_trim"
|
|
done
|
|
fi
|
|
}
|
|
|
|
apply_blocklist
|
|
echo "[init] iptables OUTPUT blocklist applied:"
|
|
iptables -L OUTPUT -n --line-numbers
|
|
echo "[init] sleeping forever, ready for docker exec."
|
|
|
|
# tini 是 PID 1;这里把 sleep 作为 tini 的子进程,信号能正常转发(docker stop → SIGTERM
|
|
# → tini 转给 sleep → 容器干净退出)。exec 避免再 fork 一层。
|
|
exec sleep infinity
|