Documentation
¶
Overview ¶
Package rbac 角色能力矩阵机制:角色→能力集查表(Can)、只读档由矩阵推导、 授权面指纹(Snapshot)、「矩阵不得装饰化」的 lint 骨架。
只收机制,词表是宿主数据 ¶
本包**不预设任何具体角色名或能力词**——"admin"、"platform.read"、 "session.write" 这些是宿主领域词汇,经 Matrix 与 writeFace 词表注入。 这条边界是下沉提案 #175 (e) 的核心风险对面:词表一旦长进机制里,包就 落成某一个宿主的专用件。本包的类型与方法对任何词表形状同等工作。
与 permgate 的分工边界(角色面 vs 调用面) ¶
同仓的 permgate 与本包是**权限判定的两半**,不可互相替代:
- permgate 裁「**这次调用**要不要人确认」——调用面。输入是一次具体的 工具调用(工具名 + 输入内容 + 会话模式 + 规则链),输出 allow/ask/deny, 关心的是交互(弹窗/会话钥匙/白名单 break-glass)。
- rbac 裁「**这个角色**有没有这个能力」——角色面。输入是身份(角色) 与能力词,输出是静态布尔,关心的是授权真相的单源(矩阵)。
典型组合:handler 侧 requirePerm 用 rbac.Can 做能力预判(角色没这个能力 连工具都不该调到);permgate 管调到了之后的确认链(这次调用要不要人)。 角色面通过的调用仍可能被调用面拦下(permgate 的 ask/deny);反过来, permgate 放行不构成角色授权——两套判定各管各的真相。
fail-closed 缺省 ¶
- Can:未声明角色/能力词 → false(矩阵外没有能力)。
- ReadOnly:写面词表 nil/空 → 无法证明任何能力是读面 → 一律推导为 只读(写面全禁)——漏声明得到的是「全禁」,不是「全放」。不在矩阵 里的角色同理(能力集为空 = 无写面 = 只读档)。
踩坑纪律(#175 (d):随机制一起下沉的两条) ¶
- **只读档由矩阵推导,不硬编码角色名**:`role == "viewer"` 式硬编码在 矩阵演进(新增只读角色、调整能力词)时静默失真——声明面改了、执行面 没跟上,授权真相从此分叉。判据恒为「能力集不含写面能力」。
- **矩阵不得装饰化**:矩阵里每条能力词必须有执行面消费点(requirePerm / Can 调用点),否则矩阵退化成「看起来有授权体系」的装饰品——多写的 能力词没人拦、删掉的能力词没人发现。lint 骨架(Matrix.Lint)落实 三条判据:每条能力词有消费点、角色词表闭合、矩阵单调(高层 ⊇ 低层); 消费点回调与角色词表由宿主注入。
并发契约 ¶
Matrix 是宿主持有的声明面数据:本包所有方法只读入参、无共享可变状态, 并发安全。矩阵变更属装配期动作——运行期热更请整体替换 Matrix 值(与 permgate.Service.Update 的换值纪律同款),不得原地改切片元素。
零第三方依赖(纯 stdlib + slices)。
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Finding ¶
type Finding struct {
Kind FindingKind
Role string
Perm string
Detail string
}
Finding 一次 lint 的疑点报告。Role/Perm 视疑点类型可空: FindingUnconsumedPerm 填 Perm(Role 为首个授予它的角色,作定位线索)、 FindingUnknownRole 填 Role、FindingNonMonotonic 填 Role(高层)+ Perm。
type FindingKind ¶
type FindingKind string
FindingKind lint 疑点类型。
const ( // FindingUnconsumedPerm 能力词在矩阵中声明,但宿主报告执行面无消费点 // (或消费点回调未注入——无法核实视同无消费点,fail-loud:lint 不静默 // 放过任何一条能力词,「矩阵不得装饰化」判据的死法就是沉默)。 FindingUnconsumedPerm FindingKind = "perm.unconsumed" // FindingUnknownRole 矩阵角色不在宿主声明的角色词表内(词表闭合破坏) // ——十有八九是拼写错造出的新角色:它查得到、授权面却分叉了。 FindingUnknownRole FindingKind = "role.unknown" // FindingNonMonotonic 角色层级单调性破坏:高层角色的能力集缺低层角色的 // 能力(矩阵单调:高层 ⊇ 低层)。单调破坏的典型成因是往低层加了新能力 // 词而忘了同步高层——高层身份反而比低层少权限。 FindingNonMonotonic FindingKind = "matrix.nonmonotonic" )
type LintOptions ¶
type LintOptions struct {
// ConsumedBy 能力词消费点回调:宿主报告 perm 在执行面是否有真实消费点
// (requirePerm/Can 调用点注册表、handler 门禁清单……由宿主自持)。
// nil ⇒ 无法核实 ⇒ **每条**能力词报 FindingUnconsumedPerm(fail-loud,
// 见该常量注释)。回调可能被逐词调用多次,应轻量;判定口径由宿主定
// (「至少一处消费点」是推荐语义)。
ConsumedBy func(perm string) bool
// Roles 声明面的角色词表全集:矩阵中出现的角色必须都在其中(词表闭合)。
// len==0 视为未提供,闭合判据跳过(宿主没有角色词表时不要硬凑空表)。
Roles []string
// Order 角色层级,从高到低(如 admin, user, viewer):单调性判据用——
// 相邻两层做包含核对(高层 ⊇ 低层),由传递性覆盖全链。len==0 视为
// 未提供,单调判据跳过(无层级语义的扁平角色表不需要它)。层级里声明
// 而矩阵未列的角色按空能力集参与核对(高层缺位即缺能力)。
Order []string
}
LintOptions lint 骨架的宿主注入面(guardx#175:判据是机制的,事实是 宿主的——消费点在哪、角色词表长什么样,本包无从知晓)。
type Matrix ¶
Matrix 角色→能力集矩阵(guardx#175):宿主注入的声明面数据。键与值都是 宿主领域词汇(角色名、能力词),本包不预设、不归一(大小写敏感、原样 比对——词表的拼写纪律归宿主的词表闭合 lint 管)。
同一角色能力集内的重复词无语义(Can 是存在性判定,Snapshot 会去重); 但建议宿主保持声明面整洁——矩阵是要被审计与指纹化的。
func (Matrix) Can ¶
Can 查表:role 的能力集是否含 perm。
fail-closed:矩阵 nil、角色未声明、能力词未授予,一律 false——矩阵外 没有能力(包文档「fail-closed 缺省」)。不存在「默认角色」或前缀匹配: 能力词是封闭词表,词表外的字面量查不到就是查不到。
func (Matrix) Lint ¶
func (m Matrix) Lint(opts LintOptions) []Finding
Lint 「矩阵不得装饰化」lint 骨架(guardx#175):对声明面跑三条判据, 逐条产出 Finding(不 panic、不修改矩阵;返回 nil = 所提供的判据全过)。
输出顺序确定(按角色/能力词字典序),供快照比对与 CI 断言;宿主接自己的 告警/门禁面(测试里 t.Fatal、启动时日志、CI 里指纹比对皆可)。三条判据 相互独立:没提供的输入跳过对应判据(文档见 LintOptions 字段)——但 ConsumedBy 未提供不会跳过,而是逐词报疑点:装饰化判据的失败模式必须是 响亮,不能是安静。
func (Matrix) ReadOnly ¶
ReadOnly 报告 role 是否只读档:**由矩阵推导**(guardx#175 的踩坑纪律)—— 能力集内不含任何写面能力即只读,不硬编码 `role == "viewer"`(矩阵演进时 硬编码静默失真,见包文档)。
writeFace 写面能力词集合(改动状态/施加副作用的能力词)。判据方向刻意 声明**写面**而不是读面:nil/空集合 ⇒ 无法证明任何能力是读面 ⇒ 一律推导 为只读(写面全禁)——漏声明得到的是「全禁」,不是「全放」(fail-closed, 与 Can 的矩阵外无能力同一方向)。不在矩阵里的角色能力集为空 ⇒ 只读档。
推导结果的使用方(宿主把只读角色钉进只读档/写面全禁)即便误判也仍有 Can 这层兜底:ReadOnly=false 的角色对未授予的写能力词照样 Can=false。 本判定是 belt,矩阵是 suspenders——两层都取保守侧。