Kubernetes 容器非 Root 权限收敛:securityContext 配置、只读根文件系统与权限排查

枫少@KillBoy
枫少@KillBoy
枫少@KillBoy
管理员
341
文章
0
粉丝
云原生安全1 29字数 2544阅读8分28秒阅读模式
AI智能摘要
Kubernetes 工作负载默认以镜像用户启动,容器内 UID 0 与宿主机内核之间只隔命名空间与 linux Capabilities,一旦被利用就可能写入可写层、投放工具甚至扩大影响。securityContext 是收敛权限的声明式入口,但 Pod 级与容器级字段、只读根文件系统、可写路径和准入策略层层交织,稍有不慎就会让应用启动失败。如何在保持服务可用的前提下,把进程身份、能力集和根文件系统真正收紧到最小集?
— AI 生成的文章内容摘要

在托管 Kubernetes 集群里,很多工作负载仍以镜像默认用户启动,容器内进程 UID 0 与宿主机内核之间只隔命名空间和 linux Capabilities。应用一旦被利用,攻击者就可能写入容器可写层、投放工具、尝试挂载或借助多余能力扩大影响。securityContext 是把这类默认权限收敛到最小集的声明式入口,但它同时涉及 Pod 级与容器级字段、镜像内目录权限、可写路径和准入策略。本文按准备、实施、验证和回滚组织,适用于自建或托管集群,不假设特定 CNI、容器运行时或策略引擎;改造前应先在测试命名空间验证。

Kubernetes Pod 安全上下文分层与权限收敛示意

先盘点字段层级与真实依赖

securityContext 分布在 Pod 和容器两个层级。Pod 级适合放统一身份和卷组设置,容器级适合放根文件系统、提权与能力等必须逐容器声明的字段。Kubernetes 官方文档说明,容器级 securityContext 中指定的字段会覆盖 Pod 级对应字段;因此不要只改 Pod 级就认为所有容器都已收敛。

字段可设置层级作用常见误区
runAsNonRootPod / 容器要求容器以非 UID 0 启动,kubelet 会拒绝以 root 启动的容器与 runAsUser: 0 同时设置会冲突
runAsUser / runAsGroupPod / 容器指定进程 UID / GID容器级优先;UID 需与镜像内文件权限匹配
fsGroupPod调整卷的组所有权,影响 emptyDir、PVC 等只设置 runAsUser 不保证挂载卷可写
readOnlyRootFilesystem容器将容器根文件系统挂载为只读必须逐容器设置,包括 init 容器和 sidecar
allowPrivilegeEscalation容器禁止子进程获得更多权限特权容器或具有 CAP_SYS_ADMIN 时该值会被强制为 true
capabilities.drop容器丢弃 Linux 能力drop: ["ALL"] 后需要的能力要显式 add
seccompProfilePod / 容器限制系统调用集合RuntimeDefault 是常见基线,不是所有应用都无需验证

盘点时优先找出三类工作负载:镜像 USER 为 root 或未声明的;需要绑定低端口、原始套接字、修改网络配置的;启动时写 PID 文件、缓存、日志或上传目录的。前两类决定身份和能力改造范围,第三类决定只读根文件系统需要补哪些挂载。

从 Pod 级基线到容器级收敛

一个常见的改造前清单只声明镜像和端口,进程身份完全由镜像决定:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-api
  template:
    metadata:
      labels:
        app: web-api
    spec:
      containers:
        - name: app
          image: registry.example.com/web-api:1.4.0
          ports:
            - containerPort: 8080

改造后把统一身份放在 Pod 级,把根文件系统、提权和能力放在每个容器级。下面的片段省略了探针和资源限制,实际清单应保留:

    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: registry.example.com/web-api:1.4.0
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /var/cache/web-api
      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir: {}

这里的 runAsUser: 10001 不是装饰项。若镜像内应用目录只允许 root 读取,进程切换后会直接启动失败;若应用需要写 /var/run 或工作目录下的文件,只读根文件系统会返回 Read-only file system。因此改造顺序应是先构建非 root 镜像并验证目录权限,再在测试环境开启只读根文件系统,最后进入生产。

改造前后影响可以用下表对照:

项目改造前改造后变更影响
进程身份镜像默认,可能为 UID 0强制 UID 10001镜像内目录需可读,写目录需挂载
根文件系统可写层可写只读启动时写 PID、缓存、日志会失败
能力集运行时默认能力drop: ["ALL"]绑定低端口、原始套接字需显式 add
提权允许no_new_privs 生效依赖 setuid 的程序可能无法工作
系统调用运行时默认或未限制RuntimeDefault个别应用需验证或使用自定义配置

readOnlyRootFilesystem 只影响容器根文件系统,不会把挂载的 emptyDir 或 PVC 变成只读。Kubernetes 官方文档对 securityContext 字段和覆盖关系有完整说明,可参考为 Pod 或容器配置安全上下文。

只读根文件系统:先找写路径再挂载

开启只读根文件系统后,最常见的失败不是权限配置错误,而是应用仍在写根文件系统。排查时先看事件和上一次容器日志:

kubectl logs deploy/web-api --previous
kubectl get events --field-selector involvedObject.name=<pod-name> -o wide

日志中的 Read-only file system、Permission denied、Operation not permitted 分别指向只读挂载、UID/GID 权限和能力缺失。接着在测试 Pod 中验证可写路径。容器内不一定有 shell 或 touch,可以用应用自带工具,或通过 kubectl debug 临时容器排查;若准入策略禁止临时容器,就在测试命名空间用同一镜像复现。

kubectl exec deploy/web-api -- sh -c 'touch /tmp/health && echo tmp-writable'
kubectl exec deploy/web-api -- sh -c 'touch /app/root-write-test 2>&1 || echo root-readonly'

第一条成功、第二条失败,通常说明根文件系统已只读且 /tmp 已正确挂载。常见写路径和挂载建议如下:

路径用途建议卷类型注意
/tmp临时文件、运行时解压emptyDirPod 删除后数据丢失
/var/cache/<app>应用缓存emptyDir需要评估缓存重建成本
/var/run 或 /runPID、socketemptyDir多容器共享时注意生命周期
上传目录用户文件PVC 或对象存储emptyDir 不适合持久化
日志目录文件日志优先 stdout/stderr必须落盘时再挂载并接入采集
只读根文件系统与可写卷挂载关系示意

挂载时还要检查卷权限。fsGroup 可以让支持的文件系统卷获得组所有权,但并非所有存储驱动和访问模式都表现一致;对 PVC 应先在小规模副本验证写入,再扩大范围。不要把整个应用目录、/etc 或 /usr 挂成可写来绕过启动错误,这会抵消只读根文件系统的大部分收益。更合适的做法是构建镜像时预生成配置、把可变数据移到挂载卷,或让应用把日志写到标准输出。

准入阻断测试:先审计再强制

配置写进清单不等于集群会持续阻止错误配置。Pod Security Admission 可以通过命名空间标签执行基线,但 restricted 级别主要覆盖非 root、禁止提权、丢弃全部能力、限制卷类型和 seccomp 等要求;readOnlyRootFilesystem 通常不在其必填字段内,需要 Kyverno、Gatekeeper 或 CI 检查补足。Kubernetes 官方 Pod Security Standards 列出了各等级的字段范围,部署前应逐项核对。

建议先用 audit 和 warn 观察,不要直接在生产命名空间启用 enforce:

kubectl label namespace web-prod 
  pod-security.kubernetes.io/audit=restricted 
  pod-security.kubernetes.io/warn=restricted 
  --overwrite

然后用服务端 dry-run 走一遍准入链:

kubectl apply --dry-run=server -f deploy-web-api.yaml

准入测试至少覆盖以下用例:

测试项操作期望结果
root 身份提交未设置 runAsNonRoot 且镜像默认 root 的 Pod被拒绝或产生 restricted 警告
显式 UID 0设置 runAsUser: 0 与 runAsNonRoot: true被拒绝或 Pod 无法启动
允许提权不设置 allowPrivilegeEscalation: falserestricted 下被拒绝
保留能力capabilities.add 中加入 SYS_ADMIN被拒绝
只读根文件系统提交未设置 readOnlyRootFilesystem 的容器PSA 可能放行,自定义策略应阻断
合法基线使用本文改造后的清单通过并产生预期警告

测试通过后再切换 enforce=restricted。切换前要确认现有工作负载不会因 hostPath、hostNetwork、hostPort 或未丢弃能力被批量阻断。对必须保留的例外,应使用策略引擎的例外机制或独立命名空间,并记录负责人和到期时间,而不是长期降低整个集群的强制级别。

验证、监控与紧急回滚

发布后先验证运行态,而不是只看 Pod 是否 Ready:

kubectl exec deploy/web-api -- id
kubectl exec deploy/web-api -- grep '^Cap' /proc/1/status
kubectl rollout history deployment/web-api

id 应返回非 0 UID;CapEff 应只包含显式保留的能力,drop: ["ALL"] 且未 add 时通常为零。再执行一次根文件系统写入测试,确认 /app 不可写、挂载目录可写。对多容器 Pod,init 容器和 sidecar 也要单独检查,因为 readOnlyRootFilesystem、allowPrivilegeEscalation 和 capabilities 都是容器级字段。

监控观察点应覆盖启动失败、重启和权限错误。部署后重点关注 CreateContainerConfigError、CrashLoopBackOff、就绪探针失败,以及日志中的 Permission denied、Read-only file system、Operation not permitted。如果新版本在几分钟内反复重启,优先回滚,不要在现场直接关闭只读根文件系统或恢复 root 身份。

回滚路径应提前写清楚:

场景处理方式注意
应用写根文件系统失败补挂载 emptyDir 或 PVC 后重新发布不要挂载整个 /
UID/GID 权限不匹配调整镜像目录属主,或用 fsGroup 验证卷权限先在小规模副本验证
能力缺失导致启动失败仅 add 必要能力并记录理由避免重新 add SYS_ADMIN
准入策略阻断发布查事件和策略报告,必要时临时加例外例外设置到期时间并走审批
生产故障扩大kubectl rollout undo deployment/web-api 或 Helm/GitOps 回退回滚后保留失败清单用于复盘

紧急回滚不是把安全配置全部删除。更稳妥的顺序是:先回滚到上一版本恢复业务,再在测试环境定位是身份、挂载还是能力问题;如果必须临时放宽准入,只对故障命名空间降低 enforce 级别,并设置明确的恢复时间。复盘时把遗漏的写路径、镜像目录权限和策略例外补进基线清单,避免同类问题在下一个服务重复出现。

 
枫少@KillBoy
    • 灵感源泉
      灵感源泉 1

      这个改造顺序挺清晰的,先镜像后只读再准入

    匿名

    发表评论

    匿名网友

    拖动滑块以完成验证