6.2 Capabilities:权限的加减法


6.2 Capabilities:权限的加减法

本节摘要:Linux capabilities 把 root 的全能拆成几十个独立的能力位,容器安全的日常操作就是对这些位做加减法。本节审计两种引擎的默认能力集合,讲常见需求(绑定低端口、抓包、改系统时间)的最小开位方案,以及"能减则减"的实践——用 drop 清单把容器降到刚好够用的权限水位。

先把概念落地:root 不是一个人,是一堆钥匙

传统 Unix 的权限模型里 root 是开关式的:要么全能要么凡人。现实里 root 的"全能"其实是几十件具体的事——绑定特权端口(CAP_NET_BIND_SERVICE)、改文件属主(CAP_CHOWN)、抓别人的包(CAP_NET_RAW)、加载内核模块(CAP_SYS_MODULE)、改系统时钟(CAP_SYS_TIME)……capabilities 机制把这些事拆成了独立的位。容器技术的核心安全操作由此变成:默认给容器一小把钥匙,需要时精确补发,不用的坚决收回。

两个引擎在这件事上共享同一套内核机制与几乎相同的默认集合(都源自 OCI 的默认 capability 清单,十几位:CHOWN、DAC_OVERRIDE、FOWNER、SETUID、NET_BIND_SERVICE、KILL……)。真正的分野在上一章讲过的"身份层":Docker 里容器默认以 root 身份持有这把钥匙串,Podman rootless 里容器内的 root 是映射身份,某些位(比如影响宿主全局的 SYS_TIME)拿了也只是容器剧场里的道具。同样的默认清单,落到不同身份模型上,实际威力不同——这是审计两引擎时要先建立的坐标系。

加法:三个常见需求的最小开位

开位原则:一次只加一个位,加完能跑就收手。三个高频案例:

# 需求一:应用要监听 80 端口(rootless 下 1024 以下默认被拒) podman run --rm --cap-add NET_BIND_SERVICE \ nginx:alpine nginx -g 'daemon off;' # 该位授予"绑定低端口"这一件事,不给任何其他特权。 # 注意:rootless 下部分内核要求此位经由用户命名空间映射, # 具体取决于发行版的 sysctl 配置——失败了再考虑宿主侧端口转发 # 需求二:网络诊断工具需要 raw socket podman run --rm --cap-add NET_RAW --cap-add NET_ADMIN \ netshoot:latest tcpdump -i eth0 -c 5 # 诊断容器加两个网络位;限定在容器自己的网络命名空间内生效 # 需求三:容器内需要 setuid 程序正常工作(默认集合里有 SETUID, # 但有些加固过的镜像会主动丢弃) podman run --rm --cap-add SETUID myapp:hardened ... # 反面教材(Docker 教程里最常见的坏习惯): # podman run --privileged ... # privileged = 一次发全部钥匙 + 关掉多数防线。 # 除非在模拟硬件访问等特殊场景,它几乎总是"懒得审计"的代名词

减法:drop 清单的实战价值

减法比加法更常被忽视、也更该成为肌肉记忆。默认集合是"让大多数镜像能跑"的妥协值,具体到你的容器,多数位是闲置的——闲置的位就是白送的攻击面。实操示例:

# 一个纯静态网站容器,审计它实际需要什么: # 读文件(DAC 类已有)、监听高位端口(无需 NET_BIND_SERVICE)、 # 不需要 CHOWN(不落盘)、不需要 SETUID(无 su 程序) podman run --rm \ --cap-drop SETUID --cap-drop SETGID --cap-drop CHOWN \ --cap-drop NET_BIND_SERVICE --cap-drop FOWNER \ -p 8080:8080 static-site:v1 # 减掉五位后照常服务。攻击面收缩的意义: # 若镜像被植入恶意代码,它能调用的特权路径少五条 # 激进方案:all 减法 + 个别加法 podman run --rm --cap-drop ALL --cap-add NET_BIND_SERVICE \ -p 8080:80 myapp:v1 # 先清空再按需发放——审计角度最干净的写法, # 代价是镜像若有隐藏的 setuid 依赖会立刻暴露(这其实是好事: # 把隐患暴露在上线前而不是生产中)

减出来的真实故障:一次"减过头"的排查

展示一个完整排错过程,顺带理解一个高频依赖。背景:团队按"能减则减"把一个 Python 应用容器减到 --cap-drop ALL,应用启动即崩,日志只有一句 Permission denied。操作:先定位——日志没有堆栈细节,改用 strace 思路,在容器里以普通权限跑 podman run --rm --cap-drop ALL python:3.12 python -c "import socket; socket.socket().bind(('',8080))",失败;逐位试探——加回 NET_BIND_SERVICE 后 bind 成功。解读:应用配置文件里写的是 8080 端口,但它内部的 gunicorn 默认尝试先绑 80 再回退(历史遗留配置),减权限把这段"平时没人注意的回退逻辑"暴露成了硬失败。修复:修掉遗留配置,保持 cap-drop ALL。变式:很多"减权限后崩溃"的真实原因不是权限模型难懂,而是应用里藏着没人记得的历史行为——最小权限是最好的应用考古工具。

与 seccomp、SELinux 的配合

三层防线配合的典型场景:容器需要抓自己命名空间内的包。NET_RAW 位(capabilities 层)授予 raw socket 权限;seccomp 层默认放行 socket 相关调用,无需动;SELinux 层对网络操作没有文件域冲突,也无需动。三层各司其职时,改一层就够。反过来,如果加位之后仍失败,排查顺序是:先看 capabilities 位是否真的进了容器(podman exec 容器 capsh --print 可查),再看 seccomp 是否拦截了调用(报 EPERM 而位已在),最后查 SELinux 审计日志。定位工具齐全,剩下的只是习惯。

本节要点回顾

  • root 拆成了几十把钥匙:capabilities 按位授予,容器默认只拿一小串
  • 开位最小化:一个需求一个位,NET_BIND_SERVICE 是最低频的合理开位
  • drop 清单是日常肌肉记忆:闲置的位就是白送的攻击面
  • cap-drop ALL 再按需加:审计最干净,代价是把隐患提前暴露(是好事)
  • 同一默认清单,不同身份模型威力不同:rootless 里的特权位部分只是容器剧场道具
  • 排查三层顺查:位(capsh)→ seccomp(EPERM)→ SELinux(审计日志)

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U