安全不是一个开关,而是一组互相补位的边界
本项目同时使用:
- ServiceAccount 表达工作负载身份;
- RBAC 限制 API 权限;
- leader election 保证多个 Operator 副本只有一个主动协调;
- Pod Security Admission 限制 Pod 配置;
- securityContext 限制容器进程权限;
- NetworkPolicy 限制网络路径;
- Secret 隔离敏感数据。
这些机制没有哪个能单独解决全部问题。RBAC 管 API,不负责网络;NetworkPolicy 管流量,不负责文件权限;非 root 容器也不能弥补过大的 API 权限。
ServiceAccount:Pod 在集群中的机器身份
用户通过 kubeconfig 访问 API Server,Pod 通常通过 ServiceAccount 身份访问。
本项目有两类 ServiceAccount:
| ServiceAccount | Namespace | 用途 |
|---|---|---|
subpool-operator | subpool-system | Operator 调用 Kubernetes API |
subpool | 每个 Pool Namespace | runtime Pod 的身份 |
mihomo-gateway | subpool-gateway | Shared Mihomo Pod 身份 |
为工作负载单独创建 ServiceAccount,比使用 Namespace 的 default ServiceAccount 更容易审计,也便于精确绑定权限。
为什么 runtime 禁止自动挂载 API token
项目在 ServiceAccount 和 Pod 两处都设置:
| |
CPA 与 cpa-manager 不需要调用 Kubernetes API,因此没有必要把 ServiceAccount token 放进容器。
如果应用被攻破,攻击者即使进入容器,也少了一份可直接拿来访问 API Server 的凭据。这是最小权限原则中非常实用的一步:不用的身份能力不要发放。
Mihomo 同样禁用 token 自动挂载,因为代理进程也不需要访问 Kubernetes API。
RBAC 的四个问题
一条 RBAC rule 可以拆成四个问题:
| |
例如 Operator 需要管理 StatefulSet:
| |
get 读取单个对象,list 读取集合,watch 订阅变化,create/update/patch/delete 才会修改状态。
Role 与 ClusterRole 的区别
Role 只在一个 Namespace 中定义权限。ClusterRole 可以表达 cluster-scoped 资源权限,也可以被不同 Namespace 的 RoleBinding 引用。
SubPool Operator 需要:
- 监听 cluster-scoped
SubPool; - 创建 cluster-scoped Namespace;
- 在任意派生 Pool Namespace 创建资源;
- 读取共享 Gateway、EndpointSlice;
- 更新 SubPool 的 status 和 finalizers。
因此它的主要管理权限必须通过 ClusterRole 和 ClusterRoleBinding 提供。单一 Namespace 内的 Role 无法完成跨 Namespace 生命周期管理。
权限大并不意味着可以省略边界。控制器代码仍严格检查 owner reference,不凭名字接管对象。RBAC 是“技术上能做什么”,所有权校验是“业务上允许对谁做”。
status 与 finalizers 为什么是独立子资源权限
RBAC 中单独出现:
| |
Kubernetes 把 status 和 finalizer 更新视为独立 API 路径。控制器需要更新现实状态和删除收尾标记,但普通业务使用者未必需要这些权限。
把它们单独授权,能避免“只想允许用户编辑 spec,却顺手允许伪造 Ready 状态或移除 finalizer”。
可以用 auth can-i 验证权限
查看当前身份能否执行某动作:
| |
模拟 ServiceAccount:
| |
它适合验证 RBAC,不证明控制器业务逻辑一定正确。
两个 Operator Pod 为什么不会同时 reconcile
Operator Deployment 设置 replicas 为 2,同时启用 leader election:
| |
多个 controller-manager 实例通过 Lease 对象竞争领导权。获得 Lease 的实例执行 controller 工作,其他实例保持待命;leader 退出或续租失败后,另一个实例接管。
这解决的是控制面单实例故障:
| |
它不让两个实例并行处理同一控制器以提高吞吐,更不会把 runtime StatefulSet 变成双副本。
leader election 为什么单独使用 namespaced Role
Lease 位于 subpool-system,所以项目用该 Namespace 中的 Role 授权对 coordination.k8s.io/leases 的读写,并授权创建或 patch Event。
这部分不需要 cluster-wide 权限。把 leader election 权限限制在固定 Namespace,减少无关权限范围。
PDB 与 leader election 是互补关系
Operator 有两个 replica,PDB minAvailable: 1。二者分工如下:
| 机制 | 作用 |
|---|---|
| replicas: 2 | 保持一个待命实例 |
| leader election | 确保只有一个实例主动协调 |
| PDB | 自愿中断时尽量保留至少一个可用 Pod |
| readinessProbe | 判断某个 Operator Pod 是否可用 |
缺少 leader election,两个副本可能同时写对象;缺少第二个副本,leader election 没有接管者;缺少 PDB,维护工具可能同时驱逐两个 Pod。
即使三者都具备,Node 同时故障、API Server 不可用等场景仍可能中断控制面。
容器为什么使用 UID 65532
Operator 的最终镜像基于 distroless/static:nonroot,并声明:
| |
Deployment 也要求 non-root,并固定 UID/GID 65532。双重声明使镜像默认行为与 Kubernetes 运行约束一致。
Operator 容器还使用:
| |
这些配置分别限制提权、Linux capabilities、根文件系统写入和系统调用面。
runtime 容器也以 non-root 运行、drop all capabilities,并通过 PVC 挂载提供需要写入的路径。
Pod Security Admission 的 Namespace 级防线
每个 Pool Namespace 带 restricted 级别标签:
| |
enforce 会拒绝不符合 restricted 标准的 Pod;audit 记录违规;warn 给请求者提示。
它可以阻止常见危险配置,例如特权容器、危险 capabilities 或不满足 non-root 等要求。具体规则随 Kubernetes Pod Security Standards 定义,应在集群升级时做兼容验证。
liveness、readiness 与 RBAC 无关
Operator 的 /healthz 和 /readyz 只说明进程健康,不会自动验证 ClusterRole 是否包含全部业务权限。
一个 Operator Pod 可以 Ready,但 reconcile 时不断收到 Forbidden。因此故障时要同时看:
| |
安全设计中的几个诚实边界
- ClusterIP 不等于只有受认证用户能访问;它只是集群内入口类型。
- Secret 不等于加密保险箱;访问 Secret 的 RBAC 必须严格。
- non-root 不等于容器没有漏洞;仍要维护镜像和依赖。
- NetworkPolicy 不限制 Kubernetes API 权限;RBAC 也不限制普通 TCP 流量。
- leader election 不等于业务数据高可用。
- PDB 不阻止所有故障。
成熟的安全不是给系统贴上“secure”标签,而是每层都明确允许什么、拒绝什么,以及这层机制覆盖不到什么。