Kubernetes如同容器世界里的交通警察,当你在集群里同时运行银行系统和休闲小游戏时,得确保不会出现"特权容器越狱后给游戏充值"的尴尬场面。今天咱们就深入聊聊Pod安全策略(PSP)和SecurityContext这对安全组合拳(技术栈:Kubernetes 1.25 + containerd运行时)。


一、Pod安全策略PSP:集群级的入场安检

虽然PSP在1.25版本已废弃(由PSA替代),但仍有超过30%的生产集群仍在使用。我们先看经典场景:

apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: baseline-restrictive
spec:
  privileged: false                  # 关掉特权模式的口子
  allowPrivilegeEscalation: false    # 防止权限升级
  runAsUser:
    rule: MustRunAsNonRoot           # 掐灭root用户的火苗
  seLinux:
    rule: RunAsAny                   # 允许不同团队的SELinux策略
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - configMap                        # 允许的业务配置类型
  - secret
  - emptyDir

绑定到特定服务账号后,这个策略会像机场安检员一样检查所有Pod:

# 创建名为game-server的服务账号
kubectl create serviceaccount game-sa

# 通过ClusterRoleBinding绑定策略
kubectl create clusterrolebinding psp-game \
  --clusterrole=psp:baseline-restrictive \
  --serviceaccount=default:game-sa

这时如果用户用此账号启动特权Pod:

apiVersion: v1
kind: Pod
metadata:
  name: test-breaker
spec:
  serviceAccountName: game-sa
  containers:
  - name: hacker
    image: nginx
    securityContext:
      privileged: true  # 触发死亡红线

等待你的将是类似这样的报错:Pod "test-breaker" is forbidden: violates PodSecurityPolicy "baseline-restrictive": privileged


二、PodSecurityContext:给每个Pod上的电子镣铐

如果说PSP是集群级防御,那么SecurityContext就是每个Pod自带的智能手铐。看这个三层防护配置:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-gateway
spec:
  replicas: 3
  template:
    spec:
      securityContext:               # 第一层:Pod全局设定
        runAsUser: 1000              
        runAsGroup: 3000
        fsGroup: 2000                # 文件系统组权限
      containers:
      - name: payment-processor
        image: alipay:v2.3
        securityContext:             # 第二层:容器级加强
          allowPrivilegeEscalation: false
          capabilities:
            add: ["NET_ADMIN"]       # 仅开放必要网络权限
            drop: ["ALL"]
        volumeMounts:
        - name: tmp-vol
          mountPath: /tmp
      initContainers:
      - name: vault-init
        image: vault-configurator
        securityContext:             # 第三层:初始化容器特殊处理
          runAsUser: 0               # 允许临时使用root初始化
          readOnlyRootFilesystem: true

这个配置的巧妙之处在于:

  1. 用fsGroup=2000确保日志文件可写入
  2. 主容器丢弃所有Linux能力仅保留网络管理
  3. init容器虽短暂获取root但限制文件系统只读

三、必须知道的关联技术

Seccomp配置实例(Kubernetes 1.19+默认启用):

securityContext:
  seccompProfile:
    type: RuntimeDefault            # 使用运行时默认配置

配合PSP的安全策略使用,能拦截99%的非常规系统调用

AppArmor实战步骤

  1. 在节点加载配置文件:
apparmor_parser -q <<EOF
#include <tunables/global>

profile payment-app flags=(attach_disconnected) {
  # 禁止二进制文件修改
  deny /usr/bin/** wl,
}
EOF
  1. Pod配置注解激活:
metadata:
  annotations:
    container.apparmor.security.beta.kubernetes.io/main: localhost/payment-app

四、六大应用场景实战分析

场景1:金融系统的"金库"防护

# 财务处理服务配置
securityContext:
  runAsNonRoot: true
  capabilities:
    drop: ["SETUID", "SETGID"]    # 防止提权操作
  seccompProfile:
    type: Localhost
    localhostProfile: finance-seccomp.json  # 定制财务专用策略

场景2:AI训练集群的GPU控制

securityContext:
  runAsUser: 1001
  capabilities:
    add: ["SYS_RAWIO"]            # GPU直通需要裸I/O权限
  privileged: false
  allowPrivilegeEscalation: false

五、技术选型的双刃剑

优势对比表

维度 PSP优势 SecurityContext优势
生效粒度 集群级统一管控 Pod级灵活调整
维护成本 集中管理更方便 无需额外RBAC配置
防御纵深 拦截恶意部署动作 防止容器运行时突破
兼容性 支持旧版本集群 适配所有Kubernetes版本

典型缺陷警报

  1. PSP启用后如果忘记设置默认策略,会导致所有Pod创建失败(报错Forbidden)
  2. SecurityContext的fsGroup改动可能导致已有文件权限紊乱
  3. 启用readOnlyRootFilesystem后未挂载emptyDir会导致日志写入失败

六、采坑指南与最佳实践

致命陷阱一:用户ID冲突 某电商曾因多个服务都使用runAsUser=1000导致共享存储的权限混乱。正确做法是通过类似下面的自动化分配:

# 基于namespace生成用户ID范围
USER_ID=$((20000 + $(kubectl get ns $NAMESPACE -o jsonpath='{.metadata.uid}' | tr -dc '0-9' | cut -c1-4)))

审计技巧

# 快速定位安全隐患Pod
kubectl get pods --all-namespaces -o json | \
  jq '.items[] | select(.spec.securityContext.privileged == true) | .metadata.name'

七、总结

通过PSP和SecurityContext的组合拳,我们可以实现从集群入口到容器内部的立体防御。但安全就像洋葱,永远需要叠加其他措施(网络策略、镜像扫描等)。在即将全面转向PSA的过渡期,建议同时配置:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
spec:
  paramKind:
    group: apps.example.com
    kind: PodSecurityDefaults
  rules:
  - condition: "!object.metadata.namespace.matchLabels['security-level'] == 'high'"
    expression: "!object.spec.containers.securityContext.privileged"