rbac-proxy 边缘 RBAC 契约:在引擎之外复刻 worker-gateway 的访问控制边界 rbac-proxy 边缘 RBAC 契约在引擎之外复刻 worker-gateway 的访问控制边界【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii本文是 iii 仓库rbac-proxyworker 技术规格系列的核心篇rbac.md。它定义了rbac-proxy在引擎进程之外完整复刻引擎内置iii-worker-managerRBAC listener 的行为契约连接如何认证、每次调用如何裁决、注册如何命名空间化与门控、中间件与注册钩子如何运行、通道如何桥接以及与引擎原生实现的有意差异。读完本文你将掌握在自有引擎或远程托管引擎前部署一条与worker-gateway字节级等价 RBAC 边界的完整配置方法与底层决策原理。为什么要把 RBAC 放到引擎之外引擎内部已经内置了完整的 RBAC 面iii-worker-manager内置 worker 可开启一个或多个 WebSocket listener非首个 listener 可以携带自己的auth_function_id、middleware_function_id、expose_functions、allowed/forbidden 列表、function_registration_prefix与三个注册钩子通道挂载在每个 listener 的/ws/channels/{channel_id}下详见 engine/src/workers/worker/README.md。rbac-proxy则刻意走向相反方向把同一份 RBAC 契约重新安置到独立进程中。它开启自己的可配置 WebSocket 端口逐字使用 iii worker 协议把每个帧函数和通道透明代理到受信任的引擎 listener在中间完成认证、调用门控、注册命名空间化、触发器目标校验、中间件与注册钩子并重写内建engine::*发现函数的结果让调用方只能看到其边界允许的内容。属性引擎原生worker-gatewayrbac-proxy本规格RBAC 运行位置引擎进程内独立进程 / Pod可否前置远程或托管的、不属于你的引擎否——RBAC 是引擎/compose 配置是——它只是指向引擎 URL 的又一个 worker部署/扩缩/重启/加固是否独立于引擎否是认证 bug 的爆炸半径引擎仅代理是否把engine::*发现结果过滤到调用方部分——只有engine::functions::list/::info会话感知且仅进程内全部八个发现函数客户端侧重写见 engine-overrides.md线上协议iii worker 协议不变iii worker 协议不变关键在于这不是为了重写引擎而重写。RBAC 决策逻辑直接从引擎逐字内联vendoredis_function_allowed、通配符匹配器、基础设施 carve-out因此行为与worker-gatewaylistener 字节级一致——代理只是规则的另一个家。共存与迁移rbac-proxy与worker-gateway并不互斥。引擎运行一个不带rbac块的可信内部 listener代理是唯一公共入口也可以同时运行两者例如 gateway 服务共置的可信 workerproxy 服务外部租户层。本规格不修改引擎任何行为纯粹是对既有协议实现的 worker 代码。决定性事实代理没有引擎 Session这是决定整个设计的关键事实。引擎内部RBAC 状态存活在进程内Session结构体中engine/src/workers/worker/rbac_session.rs引擎把它穿入调用路径以及两个会话感知的发现函数engine::functions::list/::info。而通过 WebSocket 连接的 worker——rbac-proxy在引擎看来恰恰就是这样一个 worker——永远不会收到该对象。当代理在其控制/数据连接上调用engine::functions::list时引擎按代理自身的会话过滤在可信内部 listener 上它是无限制的而不是按下游调用者的会话过滤。由此产生两个推论代理必须自行推导并持有每条下游连接的边界——运行认证函数并在连接生命周期内持有得到的 allowed/forbidden/expose/prefix/context/触发器权限。代理必须在客户端执行每个决策、重写每个响应。它不能把八个发现函数中任何一个的过滤委托给引擎——包括进程内会话感知的那两个。这正是 engine-overrides.md 存在的原因。代理为每条下游连接持有的会话镜像引擎Session的字段// Held by the proxy for the lifetime of each downstream connection. struct ProxySession { session_id: Uuid, ip_address: String, allowed_functions: VecString, forbidden_functions: VecString, allowed_trigger_types: OptionVecString, // None all allowed allow_trigger_type_registration: bool, // default false allow_function_registration: bool, // default true context: Value, // forwarded to middleware hooks function_registration_prefix: OptionString, // private namespace }与之对应的引擎侧真实结构可以在 rbac_session.rs 中对照引擎Session还持有engine、config与namespaces按命名空间作用域的授权映射而代理只需持有决策所需的授权边界字段。认证升级时的 AuthInput → AuthResult当配置了rbac.auth_function_id时代理在每次 WebSocket 升级时调用一次认证函数时机在打开到引擎的上游连接之前。该函数接收由升级请求构建的AuthInput返回AuthResult。AuthInput.headers—— 升级请求的 HTTP 头例如authorization。AuthInput.query_params——Recordstring, string[]保留重复键。AuthInput.ip_address—— 代理视角的连接客户端 IP。若代理本身在负载均衡之后这是对端地址必要时在认证函数中自行处理X-Forwarded-For。若auth_function_id未设置连接以宽松默认会话放行allowed_functions: []、forbidden_functions: []、allow_trigger_type_registration: true、allow_function_registration: true、context: {}、无前缀仅由expose_functions门控——与引擎默认行为一致。面向不可信网络的代理必须设置auth_function_id。引擎侧的认证实现可见 rbac_session.rsSession::authenticate把 headers/query/ip 组装为AuthInputJSON通过engine.call(auth_fn_id, auth_input)调用auth_function_id未设置时返回allow_trigger_type_registration: true的宽松AuthResult函数调用出错或返回空结果则抛出AUTH_ERROR。失败关闭fail closed未设置宽松默认与已设置但函数不可达拼写错误、尚未注册或被删除的函数、或iii.trigger错误是两回事。后者一律视为失败绝不视为放行认证函数不可解析/报错 →拒绝升级error 帧 Close如同其抛异常。中间件不可解析/报错 → 向调用方返回InvocationResult{ error }拒绝调用绝不静默转发给目标。注册钩子不可解析/报错 →拒绝该注册。这是安全边界唯一正确的默认值损坏的策略函数应拒绝而不是开门。认证函数是操作者注册的普通引擎函数任意 SDK 均可。worker-rbac.mdx 第 2 节 的 TypeScript/Python/Rust 示例可直接套用——它们连接到引擎的内部listener而非代理端口。拒绝帧的精确线上形态{type:error,error:{code,message}} Close且在WorkerRegistered之前、绝不开上游连接由 protocol-interception.md 规定。expose_functions通配符与 metadata 过滤器rbac.expose_functions是一个过滤器列表函数只要被任一过滤器匹配即被暴露。空列表通过过滤器暴露不了任何东西——但allowed_functions规则 2和基础设施 carve-out规则 3仍会接纳其成员所以空expose_functions不是全拒而是只暴露认证结果显式允许的部分加 carve-out。两种过滤器形态可在同一列表中混用rbac: expose_functions: - match(api::*) # wildcard, anchored at both ends, * any run of chars - match(*::public) - metadata: # all keys must match (AND); filters are ORd public: true - metadata: tier: free name: match(*public*)代理内联了引擎的匹配器语义完全一致源码见 engine/src/workers/worker/rbac_config.rs 的wildcard_match实现通配符——*匹配任意字符序列模式两端锚定、大小写敏感单独的*匹配一切。源码中的单元测试验证了前缀engine::*、后缀*::public、包含*public*、中段api::*::read与整体匹配语义。Metadata——每个键做精确 JSON 相等或对字符串值使用嵌套的match(...)没有 metadata 的函数永远不会匹配 metadata 过滤器。源码中MetadataValue::matches对非字符串值返回 false见 rbac_config.rs。Metadata 匹配要求代理知晓函数的已注册 metadata。它通过控制连接上的engine::functions::list每个条目携带metadata获知见 engine-overrides.md § Catalog binding caches。值得一提的是FunctionFilter的解析器会拒绝未知键如拼错的namesapce避免规则被静默丢弃造成授权误配置rbac_config.rs。访问解析顺序来自下游连接的每个InvokeFunction帧都会走引擎完全相同的决策流源码实现为 rbac_config.rs 的is_function_allowed由代理执行function_id在forbidden_functions中 →拒绝。function_id在allowed_functions中 →允许。function_id在基础设施 carve-out 中 →允许。任一expose_functions过滤器匹配 →允许。否则 →拒绝。基础设施 carve-outcarve-out 逐字内联自引擎是契约的一部分主版本内只增不减engine::channels::create engine::log::info engine::baggage::get engine::workers::register engine::log::warn engine::baggage::set engine::log::error engine::baggage::get_all engine::log::debug engine::log::trace这十个 id 保证连接建立、通道创建、日志与上下文传播不受操作者expose_functions影响常量定义见 rbac_config.rs。八个发现函数engine::functions::list/info、engine::workers::list/info、engine::triggers::list/info、engine::registered-triggers::list/info不在carve-out 中——它们像其他函数一样被expose_functions门控且可达时其结果会被重写到调用方的边界内见 engine-overrides.md。源码中的决策流注释rbac_config.rs还记录了一个容易被忽视的细节forbidden_functions是全局的跨命名空间生效且压过 carve-out而allowed_functions与expose_functions规则默认只在default命名空间生效——这是防止一个租户的授权为所有暴露同名函数的租户应答的泄漏而设计。拒绝帧FORBIDDEN 的线上形态拒绝时代理不转发该帧而是直接把引擎的FORBIDDEN回复合成回下游连接。SDK 依据code而非message判断拒绝因此匹配code: FORBIDDEN是让挂起调用像面对真实引擎一样 reject 的关键同时为保持一致复刻引擎的 message 与补救分支{ type: invocationresult, invocation_id: echoed, or a fabricated UUID for a void action — see below, function_id: denied id, // engine message shape: function id not allowed (remediation) // remediation remove from rbac.forbidden_functions when the id was explicitly // forbidden (rule 1), else add to rbac.expose_functions (rule 5). error: { code: FORBIDDEN, message: function id not allowed (add to rbac.expose_functions) } }引擎在拒绝时总是发送该回复——即使是voidaction帧携带null的invocation_id引擎会伪造一个 UUID见引擎 invocation 路径。代理照此处理为被拒绝的void调用伪造 UUID 而不是静默丢弃worker 对该伪造 id 没有挂起条目因此无害同时保持代理与引擎拒绝路径字节兼容。中间件契约配置middleware_function_id后每个已允许、非engine::的调用都被路由到中间件而不是转发给引擎。中间件接收MiddlewareFunctionInput其返回值即下游调用方收到的InvocationResult。中间件契约一次性精确说明它在引擎代码与文档中读起来有歧义中间件函数接收MiddlewareFunctionInput它返回的任何值都作为结果返回给调用方。若中间件希望目标函数真正运行它必须自行调用iii.trigger(function_id, payload)。engine::*调用完全绕过中间件因此连接建立、通道创建与发现覆盖永远不会被包裹。这与引擎行为一致引擎 invocation 路径中is_engine_owned_builtin判定引擎注册的engine::*内建函数跳过中间件而 worker 注册的engine::foo——拥有function_owners条目——仍会经过中间件决定是否绕过的依据是所有权而非engine::前缀把 worker 函数命名为engine::foo无法绕过中间件。MiddlewareFunctionInput字段类型描述function_idstringworker 想调用的函数。payloadobjectworker 发送的载荷。actionTriggerAction 或省略路由动作enqueue、void若有。contextobject本会话的AuthResult.context无认证函数时为{}。中间件运行在代理的控制连接上iii.trigger正如引擎通过engine.call运行它。保持中间件幂等——重试不得重复计费或重复记日志。触发器注册 RBAC通过代理连接的 worker 可以注册触发器类型与触发器但受访问控制约束。触发器类型注册RegisterTriggerTypeAuthResult的两个字段门控新触发器类型allowed_trigger_types—— worker 可为哪些触发器类型绑定触发器。省略 ⇒ 全部允许。allow_trigger_type_registration—— worker 是否可注册新的触发器类型。默认false。代理在转发RegisterTriggerType前同时执行这两项检查然后运行可选的on_trigger_type_registration钩子。触发器实例注册RegisterTrigger绑定触发器是告诉引擎当触发器触发时调用function_id。该分发是引擎内部的——它不会回流到代理的InvokeFunction门——因此代理必须在注册时验证该会话是否被允许引发那次调用。在allowed_trigger_types与可选的on_trigger_registration钩子两者都看到 worker 提交的裸 id之后代理解析引擎目标使用与InvokeFunction相同的 own-vs-foreign 前缀解析——而非RegisterFunction使用的盲目{prefix}::{id}前置。检查目标访问若解析出的 id 是本会话在本连接上注册的函数 →允许把触发器绑定到 worker 自己的处理器否则对解析出的 id 执行访问解析顺序 →允许或拒绝。拒绝时回复TriggerRegistrationResult{ error: { code: REGISTRATION_DENIED, message } }补救措辞与调用拒绝相同function id not allowed (add to rbac.expose_functions)除非规则 1 禁止了它。允许时转发RegisterTriggerfunction_id设为解析出的引擎 id。注册钩子仍是附加策略审计、更窄的允许列表、配置校验的所在。目标函数访问检查始终开启——它不是可选的不要求配置on_trigger_registration。注册钩子为细粒度控制可配置在每次注册之前调用的钩子函数。每个钩子接收注册详情加AuthResult.context返回带有可能被映射的字段的结果对象以允许或抛异常以拒绝。结果中省略的字段保留原值。rbac: auth_function_id: my-project::auth-function on_function_registration_function_id: my-project::on-function-reg on_trigger_registration_function_id: my-project::on-trigger-reg on_trigger_type_registration_function_id: my-project::on-trigger-type-reg expose_functions: - match(api::*)钩子触发于输入结果省略 ⇒ 不变on_function_registration_function_idRegisterFunction{ function_id, description?, metadata?, context }{ function_id?, description?, metadata? }on_trigger_registration_function_idRegisterTrigger{ trigger_id, trigger_type, function_id, config, context }{ trigger_id?, trigger_type?, function_id?, config? }on_trigger_type_registration_function_idRegisterTriggerType{ trigger_type_id, description, context }{ trigger_type_id?, description? }worker-rbac.mdx 第 6 节 的 TypeScript/Python/Rust 钩子示例可原样迁移。失败语义与引擎一致抛异常或返回非对象的钩子拒绝注册。代理需决定是把拒绝作为TriggerRegistrationResult{ error }浮出给 worker用于触发器/触发器类型注册还是镜像引擎偶尔的静默丢弃。建议对触发器与触发器类型注册始终发送TriggerRegistrationResult{ error: { code: REGISTRATION_DENIED, message } }信息量严格更多且 SDK 已能处理该帧而按引擎方式静默丢弃被拒绝的RegisterFunction帧函数注册没有 ack 帧。钩子输入顺序钩子看到的是裸id、前缀解析与目标访问检查之前、function_registration_prefix的交互与精确的线上重写规则见 protocol-interception.md § Registration frames。注意引擎侧也是裸 id → 钩子 → 前缀的顺序——引擎的iii-worker-managerREADME 中在function_registration_prefix之后的表述是文档 bug代理以代码为准。函数注册前缀会话私有命名空间当设置了AuthResult.function_registration_prefix时会话获得私有命名空间。由于此处 RBAC 边界是代理而非引擎内部 listener前缀由代理掌管收到 worker 的RegisterFunction时代理在转发给引擎前把id重写为{prefix}::{id}。收到RegisterTrigger时用与InvokeFunction相同的 own-vs-foreign 规则解析引用的function_id见触发器注册 RBAC而非RegisterFunction使用的盲目前置。引擎把InvokeFunction分发回worker 时触发器触发、或另一调用方调用了该 worker 的函数代理在转发前剥离function_id上的{prefix}::让 worker SDK 找到本地注册的裸处理器。worker 永远看不到前缀。这是引擎透明命名空间行为注册时 apply、分发时 strip在代理侧的搬迁。匹配/解析规则——expose_functions针对哪个 id 运行、代理如何解析 worker 调用其自身前缀函数——见 protocol-interception.md § Prefix resolution并浮现在发现结果中engine-overrides.md § Prefix in results。Channels通道桥通道在代理端口上的工作方式与引擎上完全相同。代理在自己的 RBAC 端口挂载/ws/channels/{channel_id}并把帧桥接到引擎的/ws/channels/{channel_id}。engine::channels::create在基础设施 carve-out 中所以即使expose_functions为空worker 也能创建通道。StreamChannelRef是{ channel_id, access_key, direction }不携带 host因此无需引用重写——SDK 根据 worker 连接到的地址代理端口构建通道 URL代理再转发给引擎。完整机制见 protocol-interception.md § Channel bridge代理把?key与?dir查询串原样透传给{engine_url}/ws/channels/{channel_id}帧按 1:1 双向转发Text↔Text、Binary↔Binary、Close↔Close通道 socket不做二次认证——引擎按access_key能力令牌独立校验坏 key 在升级前返回 404拨号引擎失败时以1011internal error关闭下游通道 socket。完整配置示例代理的配置存放在configurationworker 的 idrbac-proxy下不是config.yaml。以下为操作者通过configuration::set或 seed 的等价 YAMLhost: 0.0.0.0 port: 49200 # the public RBAC port (req #2) engine_url: ws://127.0.0.1:49134 # the trusted internal engine listener expose_worker_internals: false # strip pid/ip/metrics from engine::workers::* results middleware_function_id: my-project::middleware-function rbac: auth_function_id: my-project::auth-function on_function_registration_function_id: my-project::on-function-reg on_trigger_registration_function_id: my-project::on-trigger-reg on_trigger_type_registration_function_id: my-project::on-trigger-type-reg expose_functions: - match(api::*) - match(*::public) - metadata: public: true配置 schema、默认值与热重载行为port/host/engine_url变更会重新绑定 listener其余变更在下一个连接生效绑定失败保留 last-good 监听器与配置见 rbac-proxy.md § Configuration。WorkerConfig的 Rust 定义#[serde(deny_unknown_fields)]、host默认0.0.0.0、port默认、engine_url默认ws://127.0.0.1:49134与引擎WorkerManagerConfig/devexpgateway:块的字段集刻意一致使操作者的心智模型可迁移、与worker-gateway互转只是配置复制。Types 参考所有类型与引擎 RBAC 类型线上兼容SDK 已导出。AuthInput字段类型描述headersRecordstring, string升级请求的 HTTP 头。query_paramsRecordstring, string[]查询参数保留重复键。ip_addressstring代理视角的连接客户端 IP。AuthResult字段类型默认描述allowed_functionsstring[][]在expose_functions之外额外允许。forbidden_functionsstring[][]即使已暴露也拒绝。最高优先级。allowed_trigger_typesstring[]或省略省略全部worker 可绑定的触发器类型。allow_trigger_type_registrationbooleanfalse是否可注册新触发器类型。allow_function_registrationbooleantrue是否可注册新函数。function_registration_prefixstring或省略省略私有命名空间前缀。contextobject{}转发给中间件与钩子。MiddlewareFunctionInput字段类型描述function_idstring正在被调用的函数。payloadobject调用方载荷。actionTriggerAction 或省略enqueue/void若有。contextobject会话认证上下文。注册钩子 I/OOnFunctionRegistrationInput{ function_id, description?, metadata?, context }→OnFunctionRegistrationResult{ function_id?, description?, metadata? }。OnTriggerRegistrationInput{ trigger_id, trigger_type, function_id, config, context }→OnTriggerRegistrationResult{ trigger_id?, trigger_type?, function_id?, config? }。OnTriggerTypeRegistrationInput{ trigger_type_id, description, context }→OnTriggerTypeRegistrationResult{ trigger_type_id?, description? }。所有结果字段均可选省略一个即保留原值。抛异常即拒绝注册。与引擎的有意分歧代理追求与worker-gatewaylistener 的对等但少数行为刻意不同。它们集中列在这里以免规格中其他位置的对等主张被读作绝对。每一项都是可选的——追求严格对等的部署可以关闭它。分歧引擎行为代理行为原因前缀下的自调用带前缀的 worker 调用自己裸名foo得到NOT_FOUND——引擎从不重新给调用目标加前缀protocol-interception.md § Prefix resolution。代理把裸 id 解析到会话自己的{prefix}::foo调用成功。worker 应能调用自己注册的东西。退出方式为严格对等禁用 own-id 重新加前缀。被拒发现的错误码engine::functions::info区分FORBIDDEN被拒与NOT_FOUND缺失泄露存在性。默认引擎对等FORBIDDEN部署可选用把被拒折叠为NOT_FOUND以同时隐藏存在性engine-overrides.md。面向敌意多租户发现的加固。注册拒绝帧对触发器/触发器类型注册的 RBAC/钩子拒绝引擎静默返回无TriggerRegistrationResult。代理回复TriggerRegistrationResult{ error: { code: REGISTRATION_DENIED, … } }让 worker 知道原因注册钩子。静默丢弃难以调试SDK 已处理该帧。退出方式为严格对等静默丢弃。触发器目标访问RegisterTrigger只由触发器类型权限与可选钩子门控引擎不验证 worker 是否可以调用被绑定的function_id。转发前代理解析目标 id 并运行访问解析顺序自己注册的函数豁免。触发器触发会绕过调用门无此检查worker 可把触发器绑定到它不能直接调用的函数。其余一切——访问解析顺序、carve-out、AuthResult语义、中间件契约、RegisterFunction与入站分发上的前缀 apply/strip——都通过内联引擎决策代码而逐字节与引擎一致。安全清单只有代理的 RBAC 端口应面向不可信网络。引擎内部 listenerengine_url及其他引擎端口保持内部仅代理与可信共置 worker 可达。用防火墙规则/网络策略强制执行。面向不可信网络的代理务必设置auth_function_id。没有认证函数时仅expose_functions门控每个连接都被放行。倾向窄expose_functions模式而非match(*)。出现新引擎命名空间时审计该列表。forbidden_functions是硬拒绝操作者expose_functions无法覆盖的按用户/按角色拒绝列表。中间件是请求校验、限流与审计日志的位置。RegisterTrigger始终校验被绑定的function_id与会话访问边界见触发器注册 RBAC。不要仅依赖可选注册钩子来阻止触发器到特权函数的绑定。通过代理连接注册的触发器与函数作用域限定在该引擎连接下游 worker 断开时被清理——代理在下游关闭时关闭上游 WS引擎既有的每连接清理完成其余工作。延伸阅读README.md —— 本规格系列的入口代理的架构、两种连接平面、请求生命周期与既有实现先例consoleworker、iii-worker-manager。protocol-interception.md —— 线上协议逐帧拦截/重写规则、前缀机制、拒绝帧与通道桥。engine-overrides.md —— 八个发现函数的结果重写表、目录/绑定缓存与 worker 内部泄漏策略。rbac-proxy.md —— worker 自身的文件布局、启动顺序、配置 schema、热重载与测试。worker-rbac.mdx —— 操作者向的原始 how-to其结构本契约完整保留。rbac_config.rs 与 rbac_session.rs —— 被内联的引擎决策代码的源头。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考