分类: Kubernetes与云原生

  • 深入 K8S 越权陷阱排查:hostPath 逃逸与高危 RBAC 滥用引发的提权实战及 PSS 纵深防御体系构建

    K8S 默认的“全信”模型是集群最大的定时炸弹。在 K8S v1.25 彻底废弃 PSP 后,滥用 hostPath 与过度绑定 cluster-admin 是引发容器逃逸的罪魁祸首。本文通过复盘一次利用 hostPID 与根目录挂载引发的提权事件,给出基于内置 Pod Security Admission (PSA) 实施 Restricted 基线,结合动态 Admission Webhook 与 RBAC 最小权限收敛的纵深防御落地代码。

    事故现场:从一个不起眼的 dev-pod 到宿主机失陷

    某次排查集群 worker 节点 Load Average 异常飙升的问题时,在节点级监控(Node Exporter)发现 CPU I/O Wait 居高不下。登录宿主机执行 ps aux,赫然发现一个非预期的 bash 进程正以 root 身份执行内网扫描脚本。

    顺藤摸瓜,通过 crictl ps 配合进程 PID,定位到这是一个运行在 dev-workspace 命名空间下的调试 Pod。导出该 Pod 的 YAML 后,发现研发为了图方便,写出了极其典型的“自杀式”配置(K8S v1.26 环境):

    apiVersion: v1
    kind: Pod
    metadata:
      name: dev-debug-tool
      namespace: dev-workspace
    spec:
      hostPID: true       # 致命点 1:共享宿主机 PID 命名空间
      hostNetwork: true   # 致命点 2:共享宿主机网络栈
      containers:
      - name: debugger
        image: ubuntu:22.04
        command: ["sleep", "infinity"]
        securityContext:
          privileged: true # 致命点 3:开启特权模式
        volumeMounts:
        - mountPath: /host
          name: host-root
      volumes:
      - name: host-root
        hostPath:
          path: /       # 致命点 4:挂载宿主机根目录
    

    攻击者利用研发泄露的 Kubeconfig(甚至只是挂载了拥有高权限 ServiceAccount Token 的默认路径),通过 kubectl exec 进入这个容器。随后的一行命令,直接完成了从容器到宿主机的逃逸:

    # 容器内执行,直接获取宿主机 root shell
    chroot /host /bin/bash
    

    或者更底层的 nsenter 逃逸(利用 hostPID):

    # 找到宿主机 init 进程 PID 1,穿透 namespace
    nsenter -t 1 -m -u -i -n -p su
    

    为什么控制面 RBAC 拦不住数据面容器逃逸?

    在 K8S 的权限体系中,很多工程师存在一个严重的认知误区:认为 RBAC 配置正确,系统就绝对安全。

    底层原理在于,K8S 的安全边界分为控制面(Control Plane)和数据面(Data Plane/Node Node)。RBAC 仅控制你能不能调用 API Server 的接口(例如 create pods),但它完全不关心你创建的这个 Pod 内部到底长什么样。

    如果一个低权限的开发者,被授予了在某个 Namespace 下创建 Pod 的权限,而集群层面没有配置 Admission Controller 来校验 Pod 的 spec.securityContext,那么他就可以随时创建一个挂载 /etc/kubernetes/pki 或 /var/run/docker.sock 的提权 Pod。只要 Pod 调度到节点并由 Kubelet 启动,Linux Kernel 的 Namespace 和 Cgroups 隔离就会被 hostPath 和 privileged 彻底撕裂。爆炸半径瞬间从单个 Namespace 扩大到了整个物理节点甚至整个集群。

    纵深防御实战:构建不可逾越的安全基线

    为了填补 RBAC 与运行时安全之间的鸿沟,必须在 API Server 的请求持久化到 etcd 之前进行拦截。防御性架构需要分三步走。

    1. 启用 Pod Security Admission (PSA) 强制隔离

    自 K8S v1.25 起,PodSecurityPolicy (PSP) 被移除,取而代之的是内置的 Pod Security Admission。它预置了三种标准:Privileged、Baseline、Restricted。

    对于业务 Namespace,直接打上 Restricted 标签,强制实施最高安全级别,同时开启 warn 和 audit 以保证可观测性:

    # 对 dev-workspace 开启严格模式
    kubectl label --overwrite ns dev-workspace \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/enforce-version=latest \
      pod-security.kubernetes.io/warn=restricted \
      pod-security.kubernetes.io/warn-version=latest \
      pod-security.kubernetes.io/audit=restricted \
      pod-security.kubernetes.io/audit-version=latest
    

    此时,如果再次尝试提交上文的高危 YAML,API Server 会直接在 Admission 阶段返回 403 阻断:

    Error from server (Forbidden): error when creating "bad-pod.yaml": pods "dev-debug-tool" is forbidden: violates PodSecurity "restricted:latest": host namespaces (hostNetwork=true, hostPID=true), privileged (container "debugger" must not set securityContext.privileged=true), hostPath volumes (volume "host-root") ...
    

    底层拦截逻辑:kube-apiserver 内部的 PodSecurity admission plugin 会在 Mutating 阶段后、Validating 阶段前评估 Pod Spec。它硬编码了 Kubernetes Pod Security Standards 的校验逻辑,直接在内存中比对字段,性能开销极小。

    2. 引入 OPA Gatekeeper 补齐 PSA 盲区

    PSA 虽然好用,但粒度太粗。比如它无法限制镜像仓库来源,无法强制要求配置 resources.limits。此时需要引入外部的 Admission Webhook。

    部署 OPA Gatekeeper(v3.13+),编写 Rego 策略,确保所有镜像必须来自私有可信镜像库(如 harbor.internal.com),拦截供应链投毒:

    # K8s Constraint Template (Gatekeeper)
    apiVersion: templates.gatekeeper.sh/v1
    kind: ConstraintTemplate
    metadata:
      name: k8sallowedrepos
    spec:
      crd:
        spec:
          names:
            kind: K8sAllowedRepos
          validation:
            openAPIV3Schema:
              type: object
              properties:
                repos:
                  type: array
                  items:
                    type: string
      targets:
        - target: admission.k8s.gatekeeper.sh
          rego: |
            package k8sallowedrepos
            violation[{"msg": msg}] {
              container := input.review.object.spec.containers[_]
              satisfied := [good | repo = input.parameters.repos[_] ; good = startswith(container.image, repo)]
              not any(satisfied)
              msg := sprintf("Container image %v comes from an untrusted registry", [container.image])
            }
    

    绑定约束:

    apiVersion: constraints.gatekeeper.sh/v1beta1
    kind: K8sAllowedRepos
    metadata:
      name: repo-is-harbor
    spec:
      match:
        kinds:
          - apiGroups: [""]
            kinds: ["Pod"]
      parameters:
        repos:
          - "harbor.internal.com/"
    

    3. RBAC 权限收敛与自动化审计

    切断了提权路径后,最后一步是收敛 ServiceAccount 权限。很多时候,Pod 逃逸后能横向移动,是因为默认挂载了高权限的 Token。

    防御性配置标准:

    1. 业务 Namespace 的 default ServiceAccount 必须禁用 Token 自动挂载。

    2. 业务 Pod 需要访问 API Server 时,创建专属 SA,并遵循最小权限绑定。

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: app-backend-sa
      namespace: dev-workspace
    automountServiceAccountToken: false # 核心防御:切断默认 Token 投递
    

    配合精细化 Role,仅授予业务绝对需要的权限(例如仅读取本命名空间下的特定 ConfigMap):

    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      namespace: dev-workspace
      name: config-reader
    rules:
    - apiGroups: [""]
      resources: ["configmaps"]
      resourceNames: ["app-config"] # 限制到具体资源名称,而非整个 configmaps
      verbs: ["get", "watch"]
    

    常见问题

    Q1:开启 PSS Restricted 模式后,Ingress Controller、CNI 等必需使用 hostNetwork 的系统级组件起不来怎么办? 在集群维度的 AdmissionConfiguration 配置文件中配置豁免(Exemptions)。可以将 kube-system、ingress-nginx 等核心系统组件所在的 Namespace,或者特定的特权 RuntimeClass 加入白名单。切忌在全局层面降低 PSS 的安全等级。

    Q2:对于存量极速迭代的业务,如何不中断业务平滑推行 PSS? 分段推行。首先通过 kubectl label ns pod-security.kubernetes.io/audit=restricted pod-security.kubernetes.io/warn=restricted 开启审计与告警模式,enforce 维持在 baseline。收集 API Server 的 Metrics (apiserver_admission_pod_security_audit_duration_seconds 及相关 Audit log),抓出违规业务,推动研发整改(如去掉不必要的 runAsRoot),确认无拦截日志后再开启 enforce=restricted。

    Q3:研发确实需要 exec 进容器进行抓包调试,如何满足需求同时防止其起高危 Pod? 利用 K8S v1.25+ 稳定的 Ephemeral Containers(临时容器)特性。研发无需创建静态的高危调试 Pod,只需使用 kubectl debug -it --image=nicolaka/netshoot --target=。管理员通过 RBAC 严格限制 pods/exec 和 pods/debug 的权限,并由 PSA 确保临时容器同样受到 Restricted 策略约束。