需编写自定义seccomp JSON策略,将socket、bind、connect、connectat、listen、accept、accept4等系统调用统一设为SCMP_ACT_ERRNO或SCMP_ACT_KILL,并严格声明architectures字段;禁用socket会阻断网络栈初始化,导致Go/Node.js等容器启动失败,须结合strace分析真实调用链并验证生效。

docker run --security-opt seccomp= 时如何禁用 socket、bind、connect 等网络调用
Docker 默认 Seccomp 策略允许所有常见网络系统调用(socket、bind、connect、listen、accept4 等),除非你显式拦截。想禁止容器发起任何网络连接,不能靠默认配置,必须写自定义 JSON 规则并设为 SCMP_ACT_ERRNO 或 SCMP_ACT_KILL。
关键点在于:网络调用不是单个函数,而是一组关联 syscall,且部分调用(如 socket)在 Go/Node.js 启动阶段就会触发,禁错一个就导致容器启动失败。
实操建议:
- 先用
strace -f -e trace=socket,bind,connect,listen,accept4 -p $(pidof your-app)在无 Seccomp 的容器里抓真实调用序列,确认哪些真被用到 - 禁用
socket就等于禁止一切网络栈初始化;若应用是 Go 编译且含net/http,它会在启动时调用socket创建监听器或 DNS 查询句柄 - 若只禁
connect而放行socket和bind,容器仍可监听端口(如运行本地 server),但无法向外发起连接——这是更常见的“隔离出向流量”场景 -
accept4和accept必须同时禁用,否则绕过检测;同理connectat是较新变体,也要一并加入规则
seccomp.json 中正确声明 socket 相关 syscall 的字段格式
常见错误是字段名拼错或漏掉架构声明,导致 Docker daemon 拒绝加载策略。例如把 default_action 写成 defaultAction,或漏掉 architectures 字段,容器会直接报错:invalid seccomp profile: unknown field "defaultAction"。
一个能实际生效的最小网络拦截片段如下(注意大小写与必填字段):
{
"default_action": "SCMP_ACT_ALLOW",
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["socket", "bind", "connect", "connectat", "listen", "accept", "accept4"],
"action": "SCMP_ACT_ERRNO"
}
]
}
说明:
-
architectures必须匹配宿主机 CPU 架构,x86_64 容器不能省略该字段,否则启动失败 -
names是数组,支持批量写多个调用名,避免重复写多条规则 - 用
SCMP_ACT_ERRNO表示返回EPERM错误,应用层可捕获处理;用SCMP_ACT_KILL会直接终止进程,适合强隔离场景 - 不要试图用
args字段过滤 IP 地址或端口——seccomp 无法解析 socket 地址结构体,参数级过滤需靠 eBPF 或 Cilium 等上层工具
Go 和 Node.js 容器禁 socket 后为何常 panic 或 hang 住
Go runtime 在启用 CGO 时,DNS 解析(getaddrinfo)底层会触发 socket + connect;Node.js 的 net 模块初始化也会调用 socket。一旦被 seccomp 拦截,Go 程序可能卡在 runtime.goexit,Node.js 则在 uv__tcp_connect 处阻塞或抛 ERR_SOCKET_BAD_TYPE。
典型现象:
- Go 容器日志停在
runtime.main,ps aux显示进程状态为D(uninterruptible sleep) - Node.js 报错:
Error: connect EPERM /tmp/socket,即使你没连 Unix socket,也是 seccomp 返回了通用错误码 - 使用
cat /proc/self/status | grep Seccomp可确认策略已加载(值为 2 表示 filter mode 生效) - 查
dmesg | tail -20会出现类似audit: type=1326 audit(1745284282.123:456): auid=4294967295 uid=0 gid=0 ses=4294967295 pid=1234 comm="app" exe="/app/bin/app" sig=31 arch=c000003e syscall=41 compat=0 ip=00007f1234567890 code=0x0—— 其中syscall=41对应socket(x86_64 上编号)
验证 socket 级别拦截是否真正生效
光看容器是否启动成功不够,得确认网络调用真的被拦住。最直接的方式是在容器内运行最小化测试命令:
进容器后执行:
# 测试 socket 创建
python3 -c "import socket; s = socket.socket(); print('socket ok')"
<h1>测试 connect(对 localhost 也有效)</h1><p>python3 -c "import socket; s = socket.socket(); s.connect(('127.0.0.1', 80)); print('connect ok')"</p>若策略生效,两条都会报 OSError: [Errno 1] Operation not permitted。注意:
- 某些语言(如 Rust 的
std::net)会 fallback 到getaddrinfo+socket+connect链式调用,任一环节被拦都失败 - Alpine 镜像因 musl libc 行为差异,有时会静默失败而不报错,建议用 Debian/Ubuntu 基础镜像做首次验证
- 如果
socket被禁但connect没禁,Python 测试会卡在connect()调用上,strace 可见connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EPERM (Operation not permitted)
真正的难点不在写规则,而在于区分「应用必需的基础网络调用」和「可安全拦截的出向行为」——比如 DNS 查询需要 socket,但你可以用 --dns 提前注入 hosts,再禁掉所有 connect;又比如有些服务只监听不外连,那就只禁 connect 和 connectat,保留其他。这需要结合 strace 日志和业务逻辑反复校准。


















