背景与问题界定

某 SaaS 平台在安全攻防演练中被第三方安全团队成功侵入——攻击者通过一个被攻陷的 CI Runner Pod,利用其绑定的 cluster-admin ServiceAccount 在 2 分钟内创建了后门 DaemonSet,持久化了集群的控制权。事后复盘发现三个致命问题:超过 30 个 ServiceAccount 绑定了 cluster-admin ClusterRole 但实际上只需要 namespace 级别的读写权限;所有 Pod 都以 privileged: true 运行导致容器可以直接访问宿主机内核;没有任何 Admission Controller 对 Pod 的安全性配置进行校验。Kubernetes 的安全模型是典型的"默认准许"模式——默认情况下任何 Pod 都可以做任何事,安全必须通过显式配置来实现。

目标拆解与工程约束

  1. 最小权限 RBAC 设计:所有 ServiceAccount 必须遵循"只为完成工作所需的最小权限"原则,彻底消除 cluster-admin 的过度授权,使用 Role/ClusterRole 的细粒度 verbs 控制(get vs list vs watch vs create vs update vs delete)。
  2. Pod 安全合规:所有 Pod 必须遵循 Pod Security Standards(PSS)的 “baseline” 级别以上的安全策略——禁止特权容器、禁止 hostPath 挂载、禁止 hostNetwork、限制 Linux Capabilities。
  3. 运行时安全检测:在 Pod 运行时层面通过 RuntimeClass 和 seccomp/AppArmor 限制容器的系统调用能力,结合 Falco 进行异常行为检测,构建运行时安全的"最后一公里"。
  4. 密钥管理安全:避免通过 Secret 存储敏感信息导致的 Base64 编码假象(只要具备 etcd 读权限即可解密),集成 External Secrets Operator 或 Vault Provider 实现密钥的外部化管理。

方案设计

RBAC 重构采用"三层角色体系":平台级(ClusterRole)、Namespace 级(Role)、应用级(ServiceAccount+RoleBinding)。废除了所有非必要的 cluster-admin 绑定,设计了四类标准化角色:

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: developer-basic
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
  resources: ["ingresses"]
  verbs: ["get", "list", "watch"]

CI/CD 管线使用独立的 ci-deployer ServiceAccount,仅允许在特定 namespace 对 Deployment/Service 执行 update 和 patch 操作,不允许删除资源或访问 Secret。

Pod Security 方面,启用 Kubernetes 原生的 Pod Security Admission(内置 Admission Controller,替代废弃的 PSP)。通过 namespace 级别的 Label 声明合规级别:

apiVersion: v1
kind: Namespace
metadata:
  name: production-payment
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: baseline

restricted 级别严格禁止 allowPrivilegeEscalationprivileged 容器和主机网络访问。对于无法满足 restricted 的 Legacy 应用(如需要 NET_RAW 的抓包工具),使用 baseline 级别加 exceptions 来逐步迁移。

运行时安全方面,部署 Falco 作为异常检测引擎,配置 syscall 规则库监控敏感行为:

# falco_rules.local.yaml
- rule: Shell in Container
  desc: Detect shell execution in containers
  condition: >
    spawned_process and container
    and proc.name in (bash, sh, zsh, dash)
  output: "Shell opened in container (user=%user.name container_name=%container.name shell=%proc.name cmdline=%proc.cmdline)"
  priority: WARNING

外部密钥管理使用 External Secrets Operator,通过 CRD 定义从 AWS Secrets Manager 同步到 K8s Secret:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
spec:
  refreshInterval: "3600"
  secretStoreRef:
    name: aws-secretsmanager
    kind: SecretStore
  target:
    name: db-credentials
  data:
  - secretKey: DB_PASSWORD
    remoteRef:
      key: /prod/payment/database
      property: password

实施路径与关键决策

  • 第一步:审计当前集群的 RBAC 配置——使用 kubectl auth can-i --list --as=system:serviceaccount:namespace:sa-name 逐一检查每个 ServiceAccount 的实际权限,将不必要的 cluster-admin 降级为最小 Role。
  • 第二步:在测试 namespace 灰度启用 Pod Security Admission,使用 audit 级别观察违规 Pod,记录但不拦截。生产 namespace 先启用 warn 级别,逐步过渡到 enforce: baseline
  • 第三步:部署 Falco 以 DaemonSet 模式运行,配置 syslog 输出到集中日志系统,设置告警规则——容器内打开 Shell、挂载 Docker Socket、执行内核模块等高风险行为立即告警。
  • 第四步:安装 External Secrets Operator,将生产环境的数据库密码、API Key 从 Secret 对象迁移到 AWS Secrets Manager,配置自动续期策略保证密钥轮换零影响。

验证指标与可持续迭代

安全加固的量化指标:cluster-admin 绑定的 ServiceAccount 数量从 32 个降至 0(全部废除);符合 restricted 级别的 Pod 占比从 5% 提升到 85%(剩余 15% 是系统组件);Falco 高严重性告警从日均 200+(几乎都是误报)优化到日均 < 5 条(调优排除规则后);Secret 外部化覆盖率从 0 提升到 100%。

工程落地思考

Kubernetes 安全加固最痛苦的不是技术实现,而是"说服开发团队接受权限限制"。开发习惯在 Pod 里 apt-get update && apt-get install vim 来 Debug,但 restricted 策略禁止了包管理。解决方案是提供 Debug Sidecar 和 kubectl debug 的临时特权 Pod(带 TTL 自动销毁),让开发在不影响生产安全的前提下完成排查。安全是约束也是保护,好的安全体系应该在开发体验和安全合规之间找到平衡点——而不是用安全的名义把开发锁在门外。