3. Secret的访问控制与RBAC

在 Kubernetes 中,Secret 本身只是“被加密存储的键值数据”,真正决定“谁能读、谁能改、谁能创建”的,是 API Server 的认证与授权链路。理解 Secret 的访问控制,核心不是记住某个 YAML 字段,而是搞清楚一条请求从“我是谁”到“我能不能做这件事”的完整判定过程。这条链路可以拆成三个层次:身份(Authentication)、授权(Authorization)、准入(Admission)。Secret 的安全问题,绝大多数出在授权层配置过宽,而不是加密本身。

身份从哪里来:ServiceAccount 与用户

Kubernetes 的请求主体分两类:普通用户(User)和服务账户(ServiceAccount,简称 SA)。普通用户由外部身份系统(证书、OIDC、Webhook)认证,Kubernetes 不存储其对象;ServiceAccount 则是集群内的 API 对象,专为 Pod 中的进程设计。当 Pod 以某个 SA 运行时,kubelet 会把该 SA 的令牌投射到容器内(现代集群使用 TokenRequest 生成短期、受众受限的令牌),Pod 内的客户端凭此令牌访问 API Server,API Server 解析出用户名形如 system:serviceaccount:<namespace>:<name>,并附带一组组名,如 system:serviceaccounts 和 system:serviceaccounts:<namespace>。

这里有一个关键点:Pod 与 Secret 的“绑定”并不是通过某个字段直接声明的,而是通过 SA 的授权间接实现的。Pod 使用哪个 SA,就继承了该 SA 被授予的权限。因此,Secret 访问控制的第一原则是:不要让工作负载默认使用高权限 SA。如果 Pod 未指定 serviceAccountName,它会使用所在命名空间的 default SA。很多集群的 default SA 被误授予了过宽权限,这是最常见的隐患之一。

授权:Role/RoleBinding 与 ClusterRole/ClusterRoleBinding

授权由 RBAC 完成,四个对象两两配对:

  • Role:命名空间级权限集合,只能授予本命名空间内资源的操作。
  • RoleBinding:把 Role 绑定到主体(User、Group、ServiceAccount),作用范围限于该命名空间。
  • ClusterRole:集群级权限集合,可授予集群级资源(如 nodes、persistentvolumes),也可授予命名空间资源但跨所有命名空间生效。
  • ClusterRoleBinding:把 ClusterRole 绑定到主体,作用范围是整个集群。

对于 Secret,RBAC 的 resources 是 secrets,verbs 常见为 get、list、watch、create、update、patch、delete。需要特别注意的是:list 和 watch 会暴露 Secret 的内容。即使没有 get,只要拥有 list,调用 kubectl get secrets -o yaml 就能拿到全部 Secret 的 data 字段。因此,最小权限原则要求:除非确有必要,不要授予 list/watch,只授予对特定 Secret 名称的 get。

下面这张图展示了从 Pod 到 Secret 的访问判定链路,以及 RBAC 四个对象在其中的位置:

Pod serviceAccountName: app-reader 容器内挂载令牌 ServiceAccount ns: production name: app-reader 身份: system:serviceaccount: production:app-reader 使用 RBAC 授权判定 Role / ClusterRole resources: secrets RoleBinding / ClusterRoleBinding subject: ServiceAccount verbs 判定 get ✓ list ✗ watch ✗ create ✗ update ✗ delete ✗ 绑定 Secret db-credentials data: base64 编码 namespace: production API Server 认证 → 授权 → 准入 GET /api/v1/namespaces/ production/secrets/db-credentials 请求 读取 最小权限原则:只授予特定 Secret 名称的 get,避免 list/watch 与通配符 * 推荐:resourceNames 限定名称 + verbs 仅 get;禁止 ClusterRoleBinding 绑定高权限 SA 常见错误:default SA 被授予 secrets 的 list 权限、使用 * 通配符、跨命名空间越权

最小权限原则的落地写法

把上面的判定链路落到 YAML,一个“只读单个 Secret”的 Role 应该这样写:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["db-credentials"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: production
  name: secret-reader-binding
subjects:
- kind: ServiceAccount
  name: app-reader
  namespace: production
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

这段配置的关键在于 resourceNames 与 verbs: ["get"] 的组合:它把权限精确限制到“production 命名空间下名为 db-credentials 的 Secret,且只能 get”。如果去掉 resourceNames,该 SA 就能读取该命名空间内所有 Secret;如果把 get 换成 list,则即使没有 get,也能通过列表接口拿到全部内容。这是 RBAC 中一个容易被忽视的“权限放大”效应。

常见配置错误与排查

实际运维中,Secret 访问控制的高频错误集中在以下几类:

  • 默认 SA 权限过大:很多团队为图方便,把 default SA 绑定到 cluster-admin 或包含 secrets: ["*"] 的 ClusterRole。一旦某个 Pod 被攻破,攻击者即可读取全集群 Secret。
  • 使用通配符:resources: ["*"] 或 verbs: ["*"] 会隐式包含 secrets 的 list/watch,权限边界完全失控。
  • ClusterRoleBinding 误用:把命名空间级需求写成 ClusterRoleBinding,导致权限扩散到所有命名空间。应优先使用 RoleBinding 绑定 ClusterRole,把作用域限制在单个命名空间。
  • 忽略 list/watch 的泄露:只检查 get 而忘记 list,导致“看似只读一个,实际能读全部”。
  • SA 令牌长期有效:旧版集群使用不过期的 Secret 令牌,一旦泄露长期可用。应启用 TokenRequest 投射短期令牌,并设置合理的过期时间。

排查时,可以用 kubectl auth can-i 做快速验证。例如,模拟某个 SA 是否能读取指定 Secret:

kubectl auth can-i get secrets/db-credentials \
  --as=system:serviceaccount:production:app-reader \
  -n production

# 检查是否意外拥有 list 权限
kubectl auth can-i list secrets \
  --as=system:serviceaccount:production:app-reader \
  -n production

如果第二条返回 yes,说明该 SA 能列出命名空间内全部 Secret,需要回到 Role 定义中移除 list 或补上 resourceNames 限制。此外,kubectl get role,rolebinding,clusterrolebinding -A -o yaml 配合 grep secrets 可以快速定位所有涉及 Secret 的授权规则,是审计时的常用手段。

建议把“Secret 读取权限”纳入 CI 检查:任何包含 secrets 且 verbs 含 list、watch 或 * 的 Role/ClusterRole,都应在合并前人工复核。把权限审计左移,比事后排查更有效。

总结来看,Secret 的访问控制不是“给 Secret 加密”就结束,而是要把身份、授权、作用域三者对齐:Pod 用最小权限的 SA,SA 通过 RoleBinding 获得限定名称的 get 权限,避免 ClusterRoleBinding 与通配符带来的权限扩散。做到这一点,Secret 才真正处于可控的访问边界之内。