第 13 章:权限决定控制器的破坏半径
先列实际调用,再授予能力
控制器需要读写主资源、更新状态、维护 Deployment 和 Service、管理模拟外部登记。它并不需要读取任意 Secret,也不需要修改其他命名空间的网关。权限设计应从这些实际调用倒推,避免用 cluster-admin 把所有错误隐藏起来。
Namespaced CRD 不会自动把控制器限制在某个命名空间。程序如果持有集群管理员 kubeconfig,仍能操作其他命名空间。本项目 CLI 要求显式 kubeconfig 和 namespace,使实验目标清楚;真正的隔离由服务器验证凭据及 RBAC,而不是靠命令行参数自觉约束。
教学程序在集群外运行,kind 测试管理员负责建 CRD、命名空间和测试角色。受限权限实验则使用 service account 身份的 impersonation 请求,由真实 API server 执行授权判断。它没有测试 Pod 内投射令牌轮换,也不代表已经完成生产部署凭据方案。
主资源与子资源权限
读取和观察 GatewayInstance 需要 get、list、watch;添加 finalizer 通过主资源 Update 需要相应写权限;状态更新走 gatewayinstances/status。owner reference 使用 BlockOwnerDeletion 时,还需要满足相关权限检查。权限条目必须与实际 API 路径一致,不能只写一个主资源名字就假定所有子资源自动允许。
Deployment 需要 get、create、update,Service 和登记需要相应查询与维护能力。当前删除 Deployment、Service 主要由垃圾回收完成,控制器不必为了方便获得任意工作负载删除权限。某些安装或升级工具确实需要更多能力,应把安装者与运行时控制器角色分开。
CRD 本身是集群范围对象。普通实例协调者无需修改它,版本升级应由专门发布流程完成。把 CRD 更新权限交给日常控制器,既扩大误操作范围,也使 API 兼容变更难以审计。
在允许与拒绝两侧验证
真实权限测试创建只绑定到测试命名空间的 Role 和 RoleBinding。受限客户端必须完成本命名空间的正常协调,包括 status 和 finalizer 操作。随后使用同一身份访问另一个命名空间的 GatewayInstance,必须得到 Forbidden;读取未授权 Secrets 也必须被拒绝。
只测拒绝是不够的:一个完全没有权限的客户端当然会拒绝所有操作,但它也无法工作。只测成功也不够:管理员身份能够成功,并不能证明最小权限设计成立。正向能力和负向边界应同时存在,才能说明授权范围与实际需要相符。
这里的 impersonation 由管理员客户端发起,用于测试服务器授权规则。真实部署通常使用绑定到 Pod 的 service account token;两者认证路径不同,因此我们只将结果表述为 RBAC 规则验证,不声称验证了全部认证和令牌生命周期。
防止状态和配置泄露
控制器错误中可能包含外部连接信息,status 又通常可以被较多人读取。不要把令牌、完整 kubeconfig 或带秘密参数的 URL 写入条件。即使程序没有主动读取 Secret,也可能在错误链里意外输出请求地址和头部,需要对日志接口进行独立审查。
message 在本项目中是公开 HTTP 正文,不适合存放秘密。若未来增加后端凭据引用,应让 API 接受 Secret 引用而非原文,并明确谁能够创建或更新该引用。引用一个 Secret 名称也可能造成权限委托,需要防止低权限用户借高权限控制器访问别的租户秘密。
范围限制与扩展部署
从单命名空间扩展到多租户控制器时,需要同时调整 informer 范围、RoleBinding、资源引用规则和审计。只把 namespace 参数改成全部,可能让原本安全的假设失效。跨命名空间 owner reference、共享外部身份和统一队列公平性都需要重新设计。
练习是移除 status 更新权限,运行真实测试并观察返回路径。子资源可能已经创建,但状态写入失败,队列会重试。不要通过吞掉错误让测试通过;应确认错误可以定位到具体权限,并保持已创建资源在后续恢复权限后继续收敛。
权限变更也需要回归
升级控制器后增加一种资源调用,应先在受限角色测试中得到明确拒绝,再添加最小规则并证明通过。这样能确认新权限对应实际需要,而不是顺手加入通配符。删除旧能力时也应回收权限,防止角色随版本持续膨胀。
不要把一次Forbidden简单解释为账号无效。认证回答身份是谁,授权回答该身份能做什么。日志与操作手册保留这一区别,可以减少为了修复一条缺失规则而替换管理员凭据的冲动。