饮墨

子安饮墨馀三斗,留与卿儿作赋来

Kubernetes Pod Security Admission 实战:3 步替换 PodSecurityPolicy,落地 Pod 安全基线

痛点

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 步开始。

您还没有登录,请登录后发表评论。