Kubernetes 容器 /proc 挂载与默认掩码加固:procMount 配置、验证与回滚

枫少@KillBoy
枫少@KillBoy
枫少@KillBoy
管理员
420
文章
0
粉丝
云原生安全1 4字数 2197阅读7分19秒阅读模式
AI智能摘要
Kubernetes 中容易被忽视的 /proc 默认掩码机制正悄然失效。许多工作负载因配置 procMount: Unmasked 而绕过安全防线,使容器暴露更多宿主内核信息。更危险的是,这种变更往往不触发启动报错,却为攻击者扩大侦察面提供了可乘之机。问题在于:你的集群里,哪些 Pod 正在“裸奔”?
— AI 生成的文章内容摘要

容器里的 /proc 通常不是空白目录,而是内核暴露给进程的窗口。Kubernetes 默认会给容器挂载带掩码的 /proc,把一部分敏感节点遮住,以缩小攻击面;当工作负载在 securityContext.procMount 中写入 Unmasked 时,这层默认掩码就被绕开,容器可以看到更接近宿主机的 /proc 视图。这个字段常被忽略,因为它不像 privileged 那样显眼,也不一定立即导致 Pod 启动失败。本文只讨论这一细节:如何确认工作负载是否显式放开了 /proc 挂载,如何在准入层和工作负载层收敛配置,变更后如何复核容器内可见内容,以及出问题时如何回滚。适用范围是 linux 节点上的 Kubernetes 工作负载;Windows 容器不适用这套 /proc 掩码逻辑。

容器 /proc 默认掩码与 procMount Unmasked 穿透关系示意图

先明确作用边界

procMount 是容器级 securityContext 字段,Pod Security Standards 在限制 /proc 挂载类型时,会检查 spec.containers[].securityContext.procMount、spec.initContainers[].securityContext.procMount 和 spec.ephemeralContainers[].securityContext.procMount。根据 Kubernetes 官方 Pod 安全性标准文档,baseline 与 restricted 对 /proc 挂载类型的要求是使用默认掩码,准许值为未定义、nil 或 Default。这意味着省略字段、写 nil 或显式写 Default 都落在默认掩码一侧;Unmasked 则是需要单独解释和审查的例外。

但掩码不是完整隔离。它主要减少 /proc 中敏感信息的暴露面,不能替代最小权限、只读根文件系统、seccomp、能力裁剪、运行时沙箱和节点隔离。反过来,移除 Unmasked 也不等于工作负载自动安全;它只是把一个容易被忽略的隔离细节收回到默认基线。评估时应把 procMount 和同一容器中的 privileged、hostPID、hostPath、能力集合一起看,而不是只盯一个字段。

上游准入实现并非永远静止。围绕 Unmasked 与用户命名空间条件,Kubernetes 社区有过准入调整;不同集群版本、特性门控和 PSA 配置可能表现不同。因此不要只凭记忆判断“baseline 一定拦”或“restricted 一定放行”,应在目标集群上用 dry-run 或审计模式确认实际行为。官方参考至少包括 Pod 安全性标准 和 为 Pod 或容器配置安全上下文。

审计:找出显式放开 /proc 的工作负载

第一步不是改配置,而是盘点。需要同时覆盖普通容器、初始化容器和临时容器,因为 PSS 对三类字段都有限制。使用 jq 对集群内 Pod 做一次结构化扫描,比逐一看 YAML 更可靠:

kubectl get pods -A -o json | jq -r '
  .items[] as $p
  | (($p.spec.containers // [])
     + ($p.spec.initContainers // [])
     + ($p.spec.ephemeralContainers // []))
  | .[]
  | select(.securityContext.procMount != null)
  | [$p.metadata.namespace, $p.metadata.name, .name,
     .securityContext.procMount] | @tsv
'

输出中 Unmasked 是重点,Default 也应记录,因为它说明该工作负载曾被显式配置过,可能伴随其他安全上下文调整。若集群规模较大,可把结果按命名空间、工作负载类型和负责人聚合,优先处理面向外部流量、处理凭据或挂载敏感卷的负载。

还要检查准入层的现状:

kubectl get ns --show-labels | grep pod-security

如果命名空间只有 warn 或 audit 标签,说明违规配置可能已被记录但未被阻止;如果只有 enforce,则新创建或重建的 Pod 会被拦截。注意 PSA 不会追溯修改已经运行的 Pod,收敛配置后必须通过重建让新 Pod 生效。

收敛:准入层与工作负载层同时处理

准入层适合建立统一底线。对普通业务命名空间,可先以审计模式观察,再切到阻止模式:

apiVersion: v1
kind: Namespace
metadata:
  name: prod-app
  labels:
    pod-security.kubernetes.io/audit: baseline
    pod-security.kubernetes.io/warn: baseline
    pod-security.kubernetes.io/enforce: baseline

生产环境建议把版本标签固定到与集群匹配的 PSS 版本,而不是长期依赖浮动默认值;切换 enforce 前先用 --dry-run=server 验证现有清单是否会被拒绝。若命名空间内确有需要 Unmasked 的例外,不应直接把整个命名空间降到 privileged,而应评估单独命名空间、豁免策略和审批记录,把例外范围压到最小。

工作负载层则移除不必要的 Unmasked。若字段本来省略,保持省略即可;若需要显式表达意图,可写 Default:

spec:
  containers:
    - name: app
      securityContext:
        procMount: Default
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]

这里的关键不是把 Default 当成新的“安全开关”,而是让清单明确表达使用默认掩码。变更前先保存原始清单,例如:

kubectl get deployment app -n prod-app -o yaml > app-before-procmount.yaml

如果工作负载模板由 Helm、Kustomize 或平台控制器生成,要改模板而不是只改运行中的 Pod,否则下一次发布会把 Unmasked 带回来。

验证:看准入结果,也看容器内 /proc

变更后至少做三层验证。第一层是准入与对象状态:确认新 Pod 已被创建,且容器字段中不再出现 Unmasked。

kubectl get pod <pod-name> -n prod-app -o json | jq -r '
  (.spec.containers + (.spec.initContainers // []))
  | .[] | [.name, (.securityContext.procMount // "unset")] | @tsv
'

第二层是容器内可见性。默认掩码的具体实现由容器运行时决定,不要假设某个路径在所有运行时下表现完全一致;应针对运行时的已知基线做对照检查。下面的命令用于观察 /proc 中若干常见敏感节点是否仍以普通可读文件形态暴露:

kubectl exec -n prod-app <pod-name> -- sh -c '
  for p in /proc/kcore /proc/keys /proc/timer_list /proc/sched_debug; do
    if [ -e "$p" ]; then
      ls -l "$p" 2>&1
      head -c 64 "$p" 2>&1 | od -An -tx1 | head -n 1
    else
      echo "missing: $p"
    fi
  done
'

判定时不要只看“命令是否报错”。默认掩码可能把路径绑定覆盖为只读或空设备,表现为可访问但内容异常;也可能由权限位拒绝读取。更稳妥的做法是记录变更前和变更后的输出,与运行时基线比较。若应用依赖 /proc/self、/proc/cpuinfo、/proc/meminfo 等常规节点,通常不受影响;若应用读取 /proc/kcore、/proc/keys 之类节点,则应先确认这是业务必需还是历史遗留。

第三层是应用与监控观察。变更后检查 Pod 是否反复重启、应用日志是否出现权限错误、性能采集或诊断 sidecar 是否失效。把准入拒绝事件、Pod 重建时间和应用错误率放在同一个观察窗口内,避免把应用自身故障误判为 /proc 收敛导致。

异常排查与回滚

常见异常可以用下表快速定位:

现象可能原因处理方向
新 Pod 被准入拒绝enforce 已切到 baseline,清单仍含 Unmasked修正模板并重新发布;不要直接降级整个命名空间
Pod 正常启动但诊断工具失效工具依赖被掩码的 /proc 节点确认是否业务必需;改用受控诊断 Pod 或专用节点工具
应用读取 /proc 报权限错误默认掩码或能力裁剪生效对照变更前后输出,确认具体路径和运行时行为
回滚后仍被拒绝PSA 仍处于 enforce,旧清单含 Unmasked临时调整准入策略并记录审批,完成回滚后恢复

回滚应优先回到变更前的工作负载清单,而不是在运行中的 Pod 上手工修补:

kubectl apply -f app-before-procmount.yaml
kubectl rollout status deployment/app -n prod-app

如果使用 Deployment,也可以使用 kubectl rollout undo deployment/app -n prod-app,但前提是历史版本确实保留了变更前配置。回滚后要再次检查 procMount 字段和容器内 /proc 可见性,确认应用恢复。

如果准入层阻止了回滚,说明策略正在按预期工作。此时不应长期关闭 enforce;可在受控时间窗内把命名空间切到 warn/audit,或对单独命名空间使用豁免,并记录原因、范围、审批人和恢复时间。回滚只是恢复业务可用性,不是取消加固目标;恢复后仍应查清应用为什么依赖 Unmasked,再决定是改造应用、调整运行时基线,还是保留一个有明确边界的例外。

最后提醒两个限制:这套讨论仅适用于 linux 节点;procMount 的行为和默认掩码内容最终由容器运行时与内核共同决定。把它纳入发布前检查和准入策略,比一次性“扫一遍集群”更有持续价值。

procMount 收敛后的验证与回滚双轨流程示意图

 
枫少@KillBoy
    • 梦回江南
      梦回江南 0

      这个字段真容易忽略

    匿名

    发表评论

    匿名网友

    拖动滑块以完成验证