痛点
PodSecurityPolicy(PSP)在 Kubernetes 1.25 正式移除,但大量集群至今没有配置替代方案。结果就是:Pod 可以随意挂载 hostPath、使用特权模式、以 root 运行——任何一个容器逃逸漏洞都能直接拿下节点。
Pod Security Admission(PSA)是 K8s 内置的替代方案,无需安装第三方 admission controller,配置简单、开箱即用。本文带你用 3 步完成从"裸奔"到安全基线全覆盖。
方案概述
PSA 基于三个安全级别(Pod Security Standards):
| 级别 | 含义 | 典型场景 |
|---|---|---|
privileged |
无限制,完全信任 | kube-system、监控 DaemonSet |
baseline |
禁止已知提权手段 | 大部分业务 Pod |
restricted |
最严格,禁止 root、要求只读根文件系统 | 安全敏感服务 |
PSA 支持三种执行模式:
| 模式 | 行为 |
|---|---|
enforce |
违规 Pod 直接拒绝创建 |
audit |
违规记录到审计日志,不阻止 |
warn |
违规返回警告信息给客户端,不阻止 |
核心思路:先 warn/audit 观察,再 enforce 执行,避免一刀切把业务打挂。
实操步骤
第 1 步:用 dry-run 模式评估现有工作负载
在对 namespace 打标签之前,先用 kubectl label --dry-run=server 预判有多少 Pod 不合规:
# 对目标 namespace 模拟应用 baseline 级别的 enforce
kubectl label --dry-run=server --overwrite ns my-app \
pod-security.kubernetes.io/enforce=baseline
# 输出示例:
# Warning: existing pods in namespace "my-app" violate the new PodSecurity enforce level "baseline:latest"
# Warning: nginx-debug-xxx: hostNetwork, privileged
如果要批量扫描所有 namespace:
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
echo "=== $ns ==="
kubectl label --dry-run=server --overwrite ns "$ns" \
pod-security.kubernetes.io/enforce=baseline 2>&1 | grep -i warning
done
这一步不会做任何实际变更,纯评估。拿到结果后记录哪些 workload 需要改造。
第 2 步:分级打标签 —— warn + audit 先行
对业务 namespace 先启用 warn 和 audit,不启用 enforce。这样开发团队能在 kubectl apply 时看到警告,运维能从审计日志中统计违规情况,但不影响现有服务运行:
# 业务 namespace:warn+audit=baseline,不 enforce
kubectl label --overwrite ns my-app \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/warn-version=latest \
pod-security.kubernetes.io/audit=baseline \
pod-security.kubernetes.io/audit-version=latest
# 安全敏感 namespace:直接 enforce=restricted
kubectl label --overwrite ns payment-service \
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
运行一周后,检查审计日志中的违规记录:
# 在 API Server 审计日志中搜索 PSA 违规
grep "PodSecurity" /var/log/kubernetes/audit.log | jq '.annotations["pod-security.kubernetes.io/audit-violations"]' | sort | uniq -c | sort -rn
第 3 步:修复违规 Pod,启用 enforce
根据审计结果逐个修复。常见违规项及修复方式:
违规 1:容器以 root 运行
# 修复前(违规)
spec:
containers:
- name: app
image: myapp:latest
# 修复后
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
违规 2:挂载 hostPath
# 移除 hostPath,改用 emptyDir 或 PVC
volumes:
- name: data
emptyDir: {}
# 或
- name: data
persistentVolumeClaim:
claimName: app-data-pvc
违规 3:使用特权模式
# 对于确实需要特权的 DaemonSet(如 CNI、日志采集),
# 将其部署在 privileged 级别的 namespace(如 kube-system)
# 业务 namespace 禁止特权容器
securityContext:
privileged: false # 显式设为 false
readOnlyRootFilesystem: true
全部修复后,启用 enforce:
kubectl label --overwrite ns my-app \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest
验证是否生效:
# 尝试创建一个特权 Pod,应被拒绝
kubectl run test-privileged --image=nginx \
--overrides='{"spec":{"containers":[{"name":"nginx","image":"nginx","securityContext":{"privileged":true}}]}}' \
-n my-app
# 预期输出:
# Error from server (Forbidden): pods "test-privileged" is forbidden:
# violates PodSecurity "baseline:latest": privileged (container "nginx" must not set securityContext.privileged=true)
避坑指南
1. 别忘了 kube-system 必须豁免
kube-system 中的核心组件(kube-proxy、CNI 插件、CSI 驱动)通常需要 hostNetwork、privileged 等权限。永远不要对 kube-system 设置 baseline/restricted 的 enforce:
# kube-system 保持 privileged
kubectl label --overwrite ns kube-system \
pod-security.kubernetes.io/enforce=privileged
2. enforce-version 锁定版本避免升级翻车
不指定版本时默认用 latest,意味着 K8s 升级后安全标准可能变严,导致现有 Pod 在重建时被拒绝。生产环境建议锁定到当前集群版本:
# 锁定到 v1.30 的标准,避免升级到 1.31 时规则变化
kubectl label --overwrite ns my-app \
pod-security.kubernetes.io/enforce-version=v1.30
3. Helm/GitOps 场景要在模板中配好 securityContext
PSA 不会帮你自动修复 Pod spec,只会拒绝不合规的。如果用 Helm 部署,确保 values.yaml 中有默认安全配置:
# values.yaml
podSecurityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
总结
| 步骤 | 动作 | 风险 |
|---|---|---|
| 1 | dry-run 评估 | 零风险,纯诊断 |
| 2 | warn+audit 观察 | 不阻断,收集数据 |
| 3 | 修复后 enforce | 需逐个 namespace 灰度 |
核心原则:
- 新建 namespace 默认就打上
enforce=baseline标签(写进 GitOps 模板) - 存量 namespace 渐进式迁移,先 audit 再 enforce
- 特权组件集中到少数豁免 namespace,缩小攻击面
- 配合 OPA/Kyverno 做更细粒度的策略补充
PSA 是 K8s 安全的第一道防线,零成本、零依赖,没有理由不启用。如果你的集群还在"裸奔",今天就从第 1 步开始。