容器安全基线正走向默认拒绝

1 人参与

容器安全这件事,这几年最大的变化,其实是思路从“默认允许、出问题再堵”慢慢转向了“默认拒绝、需要什么再开”。放在 Kubernetes 的场景里看,最直观的体现就是大家开始认真对待 securityContext 这一整套字段,而不是只把镜像跑起来就算完事。

为什么会有这种转变?说白了,很多工作负载在集群里跑的时候,进程身份、文件系统权限、能力集这些,用的都是镜像自带的默认值。镜像里没写清楚用哪个用户启动,那容器里可能就以 root 身份在跑。应用一旦被攻破,攻击者在容器里能做的事就多了:往可写层丢工具、尝试挂载、利用多余的系统能力往外扩。这些风险不是靠多装几个安全产品就能消掉的,根源在于一开始就没把权限收住。

现在比较主流的做法,是把安全基线往“最小化”方向推。比如强制容器以非 root 用户运行,把根文件系统挂成只读,丢弃掉所有默认能力再按需加回,再配上 seccomp 限制系统调用。听起来不复杂,但真做起来有不少坑。最典型的一个,是很多人以为在 Pod 级别设了 runAsNonRoot 就万事大吉,实际上容器级别的 securityContext 会覆盖 Pod 级别的设置,而且像 readOnlyRootFilesystem、allowPrivilegeEscalation 这些字段,必须逐个容器去声明,连 init 容器和 sidecar 都不能漏。

另一个容易被忽略的地方是只读根文件系统。这个字段只影响容器本身的根文件系统,不会把挂载的 emptyDir 或者 PVC 变成只读。开启之后最常见的失败,不是配置写错了,而是应用还在偷偷往根文件系统里写东西。日志里看到 Read-only file system、Permission denied、Operation not permitted,分别指向的是挂载问题、UID/GID 权限问题和能力缺失,排查方向完全不同。所以改造的顺序很重要:先构建非 root 镜像、把目录权限验证好,再开只读根文件系统,最后才上生产,别一上来就全开。

就算清单里都写对了,也不代表集群会一直守住这条线。Pod Security Admission 的 restricted 级别能覆盖非 root、禁止提权、丢弃能力这些要求,但 readOnlyRootFilesystem 通常不在它的必填字段里,需要靠策略引擎或者 CI 检查来补。比较稳妥的推进方式是先在命名空间上开 audit 和 warn 观察一阵子,看看会产生哪些告警,再用服务端 dry-run 走一遍准入链,确认不会批量阻断现有工作负载后,才把 enforce 打开。对确实需要例外的服务,也应该用独立命名空间或者策略例外来处理,并且记录负责人和到期时间,而不是长期把整个集群的强制级别降下来。

发布之后还得看运行态,不能只看 Pod 是不是 Ready。进去执行一下 id 确认 UID 不是 0,看一眼 /proc/1/status 里的 CapEff 是不是只剩显式保留的能力,再试一次根文件系统写入,确认该只读的地方只读、该可写的地方可写。如果新版本几分钟内反复重启,优先回滚,别在现场临时关掉只读或者恢复 root 身份。回滚的顺序也应该是先恢复业务,再回到测试环境定位是身份、挂载还是能力的问题。

这套东西说到底,就是把“出了事再补救”改成“一开始就不给多余权限”。对普通团队来说,改造过程确实有点繁琐,但每收一项默认权限,攻击者能利用的面就小一圈。安全基线走向默认拒绝,不是某个工具或某种策略的胜利,而是整个行业对容器信任边界的一次重新校准。

参与讨论

1 条评论
  • 行路狂

    非root和只读根文件系统这两项确实是最容易踩坑的

    回复