容器里的 root 到底有多大权限?--cap-drop 和 --cap-add 具体改变什么?为什么非 root 用户也能绑定 80 端口?这篇解码容器的 capability 集合,并逐个操作验证。
CapEff(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap CapBnd(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap
解码 /proc/self/status 的 CapEff:chown、dac_override、fowner、fsetid、kill、setgid、setuid、setpcap、net_bind_service、net_raw、sys_chroot、mknod、audit_write、setfcap。没有 sys_admin 这类危险的。
默认:uid=0 chown 文件给 uid 1234:成功 绑定 80 端口:成功 创建 ICMP raw socket:成功 mknod 设备文件:成功 读别的用户 0600 的文件:成功 --cap-drop ALL:uid=0 chown 文件给 uid 1234:EPERM 绑定 80 端口:成功 创建 ICMP raw socket:EPERM mknod 设备文件:EPERM 读别的用户 0600 的文件:EACCES --cap-drop ALL --cap-add CHOWN:uid=0 chown 文件给 uid 1234:成功 绑定 80 端口:成功 创建 ICMP raw socket:EPERM mknod 设备文件:EPERM 读别的用户 0600 的文件:EACCES --cap-drop NET_RAW:uid=0 chown 文件给 uid 1234:成功 绑定 80 端口:成功 创建 ICMP raw socket:EPERM mknod 设备文件:成功 读别的用户 0600 的文件:成功 --cap-drop MKNOD:uid=0 chown 文件给 uid 1234:成功 绑定 80 端口:成功 创建 ICMP raw socket:成功 mknod 设备文件:EPERM 读别的用户 0600 的文件:成功
每一项 capability 对应一类操作:丢掉 NET_RAW 只影响 ICMP raw socket,丢掉 MKNOD 只影响 mknod,--cap-drop ALL 后 root 连读别的用户的 0600 文件都不行(缺 DAC_OVERRIDE)。--cap-drop ALL 再 --cap-add 需要的那几个,是最小权限的做法;但应用实际要用到哪些,要自己试。
默认:uid=1000 chown 文件给 uid 1234:EPERM 绑定 80 端口:成功 创建 ICMP raw socket:EPERM mknod 设备文件:EPERM 读别的用户 0600 的文件:EACCES --user 1000 --cap-drop NET_BIND_SERVICE:uid=1000 chown 文件给 uid 1234:EPERM 绑定 80 端口:成功 创建 ICMP raw socket:EPERM mknod 设备文件:EPERM 读别的用户 0600 的文件:EACCES --user 1000 --sysctl net.ipv4.ip_unprivileged_port_start=1024:uid=1000 chown 文件给 uid 1234:EPERM 绑定 80 端口:EACCES 创建 ICMP raw socket:EPERM mknod 设备文件:EPERM 读别的用户 0600 的文件:EACCES uid 1000 的 capability:CapEff(0 个): / CapBnd(14 个):chown dac_override fowner fsetid kill setgid setuid setpcap net_bind_service net_raw sys_chroot mknod audit_write setfcap --cap-add SYS_ADMIN 后 CapEff 个数:CapEff(15 个)
普通用户默认就能绑定 80,即使丢掉 NET_BIND_SERVICE:Docker 在容器的网络命名空间里把 net.ipv4.ip_unprivileged_port_start 放开了(我没有读出它的默认值,但加 --sysctl net.ipv4.ip_unprivileged_port_start=1024 之后绑定 80 就是 EACCES,说明确实是这个参数在起作用)。