可观测平台一旦上多租户,权限就是绕不开的事。这篇拆解炬鲸 OBSERVE 的租户隔离三层设计、RBAC 资源粒度定义、权限校验性能与默认拒绝原则、共享账号和权限蔓延治理等实践做法。
可观测平台一旦上多租户,权限就是绕不开的事:日志里常常混着用户的手机号、订单号、接口返回体,租户 A 的运维如果能看到租户 B 的日志,就是一起数据泄露事故。我们早期做过一次内部渗透测试,故意构造了一个"忘了加租户过滤条件"的查询接口,结果一条 SQL 就把另一个租户的订单日志全带出来了。那次之后,我们把隔离和权限做成了平台的核心能力。这篇拆解炬鲸 OBSERVE 的租户隔离和 RBAC 是怎么设计的。
隔离不是一层能解决的。我们把数据访问切成三层:
第一层是租户。每个租户的数据在写入时就带上 tenant_id,检索、告警、链路查询全部按 tenant_id 强制过滤。关键是双校验:应用层拼 SQL 时强制拼 tenant 条件,存储层再用行级策略兜底,避免"忘了加条件"就漏数据。两层都失效才能越权,出错概率从"迟早会犯"降到"几乎不可能"。
第二层是环境。一个租户下面通常有生产、预发、测试几套环境,日志结构一样但敏感度不同。环境作为二级隔离,生产和测试之间默认不通;测试环境要导生产数据做复现,需要单独申请一个受限的导出权限。
第三层是数据流。日志里某些字段(手机号、身份证号、token)需要脱敏,只对授权角色显示原文。这一层放在展示端做,存储里还是原文,脱敏和授权分离,改动成本最低。脱敏用字段级正则规则,例如手机号匹配 1[3-9]\d{9} 替换成 138****5678,规则集中配置,改规则不用改代码。
三层叠加之后,"谁能看到什么"从一条 SQL 变成一套固定规则,出错空间小得多。
权限模型用经典 RBAC,但把"资源"定义清楚是难点。我们把资源粒度定到"数据流 + 环境 + 动作":
role: read-only-ops
permissions:
- resource: "logs"
actions: ["read", "search"]
environments: ["prod", "staging"]
- resource: "alerts"
actions: ["read", "ack"]
environments: ["prod"]
角色按职责分五类:管理员(user:manage + data:export)、运维(读写日志告警、操作采集器)、开发(只读自己服务的日志与链路)、审计员(只有 audit:read 且看不到脱敏字段原文)、访客(只看仪表盘)。策略支持按标签条件生效,比如"只有标记为 finance 的应用的日志,财务组才能看"。
权限校验放在统一中间件里做,每个数据接口声明 resource 和 action,由中间件集中校验,而不是在每个 handler 里手写判断。校验结果按"用户 + 角色"缓存,避免每次请求都回源查一次权限表;角色变更时主动失效缓存,保证改权限立即生效。
多租户最容易出事的不是模型复杂,而是"新功能忘了接权限"。新增一个导出接口、一个新查询页面,如果默认放行,就留下一个洞。我们的做法是默认拒绝:新接口上线时不显式声明权限就返回 403。
第二个坑是共享账号。多人共用一个 admin 账号,出了事分不清是谁。我们强制个人账号 + 操作审计,admin 只在初始化时用一次,之后每个人登录、改配置、导数据都留痕。审计日志独立存储、只追加、审计员只读,删不掉也改不了。
第三个坑是权限蔓延:一个运维今天申请看日志 A,明天申请看日志 B,半年后权限越滚越大。我们对临时授权设了过期时间,到期自动回收,定期出权限报表让负责人复核,把"授权"变成"借阅"而不是"永久发放"。
多租户的权限设计,本质是把"谁能看到什么"从口头约定变成机器强制。越早做,欠的技术债越少。