feat: 【监控平台】引入 iam_engine 鉴权框架并接入 v3/v4 provider - #12031
Draft
qiushui175 wants to merge 53 commits into
Draft
Conversation
|
请在 PR 中添加项目标签,例如: |
|
请在 PR 中添加类型标签,例如: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: 【监控平台】引入 iam_engine 鉴权框架并接入 v3/v4 provider
一图看懂
graph TB subgraph "业务代码层" A1["Permission (Facade)"] A2["DRF 权限类"] A3["Other"] end subgraph "iam_engine 框架层" B1["IAMFramework"] B2["BypassRule 豁免链"] B3["CompositionPolicy 组合策略"] B4["SchemaRegistry 元数据"] B5["PolicyExpression AST"] end subgraph "Provider 层" C1["V3PermissionProvider"] C2["V4PermissionProvider"] CDK["MonitorV3Codec / MonitorV4Codec"] CDR["MonitorResourceResolver"] end subgraph "IAM 平台" D1[("IAM v3 平台")] D2[("IAM v4 平台")] end A1 --> B1 A2 --> B1 A3 --> B1 B1 --> B2 --> B3 B3 --> C1 B3 --> C2 C1 -.uses.-> CDK C2 -.uses.-> CDK C1 -.uses.-> CDR C2 -.uses.-> CDR C1 --> D1 C2 --> D2一、背景与目标
1.1 现状痛点
老版本鉴权(本次改造前的 master)存在以下问题:
Permission类直接持有iam.IAM客户端,实例化、鉴权、策略查询、迁移等逻辑全都堆在同一个类里;bkmonitor/iam/drf.py/packages/monitor_web/grafana/permissions.py等消费方各自Permission().is_allowed(...),SDK 细节泄漏到业务层。Op.OR/Op.AND/{rt}.id/_bk_iam_path_)、资源 ID 拼接(ancestors + "/"格式)散落在bkmonitor/iam/permission.py、bkmonitor/iam/resource.py、packages/monitor_web/iam/views.py各处;v4 平台走不同的模型,硬要接入必然是重写。bkmonitor/iam/action.py的ActionEnum、bkmonitor/iam/resource.py的ResourceEnum、support-files/iam/*.json手写模型、monitor_web/iam/views.py的 SDK Provider register。任一处漏改都会导致"能鉴权但拉不到资源"、"平台注册了但代码里查不到" 之类的错位。Permission.is_allowed里有一段token / skip_check / ActionIdMap的豁免逻辑;bkmonitor/iam/drf.py里的IAMPermission/BusinessActionPermission各自重复了一份豁免;has_any_dashboard_permission直接绕开Permission,另调一次 SDK。同一个"用户是否有权限"的问题,被四五种不同的路径回答,语义随处漂移。
monitor_web/iam/resources.py等业务代码里直接处理 v3 平台返回的 dict AST(识别Op.OR/Op.IN/{rt}.id/_bk_iam_path_),策略结构一旦变化业务侧全部要跟着改。1.2 改造目标
Permission/drf.py/filter_data_by_permission/insert_permission_field等对外符号保留旧签名与语义。PermissionProvider实现,IAM_FRAMEWORK.PROVIDERS一行切换。bkmonitor/iam/definitions/作为 SSOT,SchemaRegistry冻结加载后,v3/v4 provider、迁移工具、平台反向回调、insert_permission_field全部从这里派生;新增一个 action 只需改一处。IAMFramework门面 +PermissionProvider契约;接入新 IAM 平台 = 一个PermissionProvider子类 + 四个方言层抽象方法;PolicyExpressionAST 让"反向列举可见资源"在不同后端上共用同一套心智模型;BypassRule+check_iam_preflight收敛豁免语义。二、总体架构
2.1 分层设计
iam_engine采用六层结构,每一层只依赖下方(或同层的核心 core):iam_engine/schema/ActionDef/ResourceTypeDef/RoleDef/SchemaRegistryiam_engine/policy/PolicyExpression/Op/DictEvaluatoriam_engine/provider/PermissionProvider/CompositionPolicy/NameCodec/ResourceResolver/ProviderRouteriam_engine/migration/MigrationLoader/MigrationPlanner/MigrationRecorderiam_engine/crosscutting/BypassRuleiam_engine/django/IamEngineConfig/load_framework/get_framework以及框架之外、业务层面的适配:
iam_engine/callback/iam_engine/core/Subject/ResourceInstance/AuthRequest等)、异常、IAMFramework门面、utilsbkmonitor/iam/iam_v3/V3PermissionProvider/V3Client/V3Migrator)bkmonitor/iam/iam_v4/V4PermissionProvider/V4Client/V4Migrator/ callback)bkmonitor/iam/adapters/MonitorV3Codec/MonitorV4Codec/MonitorResourceResolverbkmonitor/iam/definitions/Actions/ResourceTypes/Rolesbkmonitor/iam/permission.pyPermissionFacade(保留旧对外签名)bkmonitor/iam/drf.pyinsert_permission_field/filter_data_by_permission2.2 关键抽象
以下八个概念构成整个框架的骨架,理解它们再看代码基本无障碍:
IAMFramework(core/framework.py):中心门面,装配SchemaRegistry+Provider列表 +CompositionPolicy+BypassRule列表;对外暴露is_allowed/batch_by_resource/batch_by_action/filter_visible_resources/query_policies/get_apply_url/get_apply_data/grant_creator_action等方法。PermissionProvider(provider/base.py):所有 IAM 平台接入的唯一扩展契约,模板方法模式。基类完成"业务命名 ↔ 平台方言"编解码、批量分片、并发;子类只实现_is_allowed_dialect/_batch_by_resource_dialect_page/_batch_by_action_dialect_page/_get_apply_url_dialect四个方言层抽象方法即可接入新平台。CompositionPolicy(provider/composition/base.py):多 provider 组合策略:single:只用一个 provider(默认);any_of:任一 provider 允许即允许(用于灰度切换 / 迁移期兼容);all_of:全部 provider 允许才允许(用于双写校验期);primary:以 primary 为主,fallback 为辅(primary 不可用时降级)。NameCodec(provider/codec.py):业务命名 ↔ 平台方言的编解码器。同一个 "view_business" 业务动作,v3 可能编码为view_business,v4 编码为view_business_v4;同一个 "space" 资源类型,v3 是space,v4 可能是bk_biz。所有编解码约定由MonitorV3Codec/MonitorV4Codec集中管理。ResourceResolver(provider/resolver.py):资源实例补全器。业务只给type + id,resolver 查数据库补上name和ancestor_chain。v3/v4 共用 MonitorResourceResolver。SchemaRegistry(schema/registry.py):冻结的元数据注册表,通过IAM_FRAMEWORK.ACTIONS/RESOURCE_TYPES/ROLES三个 dotted path 从业务定义加载;跨 provider 共享,使用业务规范化命名。PolicyExpressionAST(policy/expression.py):provider-neutral 的策略表达式抽象。v3 从平台拿到的 dict AST 会被翻译成PolicyExpression,v4 未来会从get_authorized_resources结果反构。字面量 ID 一律是业务命名。BypassRule(crosscutting/bypass.py):横切豁免规则链。目前的实现(SettingsSkipRule等)由IAM_FRAMEWORK.BYPASS_RULES装配;Permission.check_iam_preflight保留了 token 分享和 skip_check 两条旧豁免逻辑,两者互补。2.3 请求生命周期
以最常见的
Permission().is_allowed(action, resources)为例,一次单条鉴权的完整链路:sequenceDiagram autonumber participant Biz as 业务代码 participant Perm as Permission Facade participant Pre as check_iam_preflight participant FW as IAMFramework.is_allowed participant Route as ProviderRouter participant Bypass as BypassRule 链 participant Comp as CompositionPolicy participant Prov as V3/V4 Provider participant Codec as MonitorCodec participant Res as ResourceResolver participant Client as V3Client / V4Client participant Plat as IAM 平台 Biz->>Perm: is_allowed(action, resources) Perm->>Pre: check_iam_preflight(request, action) alt token 豁免 / skip_check Pre-->>Biz: return True (提前放行) end Perm->>FW: AuthRequest(subject, action_id, resource) FW->>Route: is_allowed(request) Route->>Bypass: 逐条评估 alt 命中 Bypass Bypass-->>Route: allowed=True Route-->>Biz: True end Route->>Comp: is_allowed(request) Comp->>Prov: is_allowed(request)(可能多个 provider) Prov->>Res: resolve(resource) → 补 name / ancestor_chain Prov->>Codec: encode_action / encode_resource Prov->>Client: direct_auth(subject_id, action_id, resource) Client->>Plat: HTTP 调用 Plat-->>Client: {allowed: bool} Client-->>Prov: bool Prov-->>Comp: bool Comp-->>Route: 组合结果(single 直接返回,any_of 需多个 provider) Route-->>FW: bool FW-->>Perm: bool Perm-->>Biz: bool关键说明:
check_iam_preflight(业务侧:token 分享、settings.SKIP_IAM_PERMISSION_CHECK)与BypassRule链(框架侧)并存;前者复刻旧版Permission.is_allowed的豁免语义,后者是框架层的横切扩展点。两者互不冲突,前者先执行。is_allowed(业务命名) →_is_allowed_dialect(方言命名)。基类完成 codec 编解码、批量分片、并发,子类只处理"打一次平台 API"。2.4 目录布局
改造后
bkmonitor/bkmonitor/iam/与相关模块的目录结构:三、核心模块详解
3.1 iam_engine.schema — 元数据中心
设计意图:把"业务定义什么资源类型、什么操作、什么角色"从 provider 中抽出来,作为跨 provider 共享的只读元数据。SchemaRegistry 一次装配、全局冻结,任何一处需要"这个 action 关联什么资源类型"都从这里查,不再散落。
关键接口:
ActionDef(id, name, resource_type, description, extensions):业务动作定义。extensions["v3"]["type"]存放旧版action.type,兼容 property 从这里取。ResourceTypeDef(id, name, ancestor, extensions):资源类型定义。ancestor指向父资源类型 ID(如apm_application.ancestor = "space")。RoleDef(id, name, actions, extensions):角色定义(v4 RBAC 概念)。SchemaRegistry:get_action(id) -> ActionDefget_resource_type(id) -> ResourceTypeDefall_actions() / all_resource_types() / all_roles()visibility_of(action_id) -> set[str]:反查该 action 关联哪些资源类型(用于 UI 侧渲染)。装配方式:
IAM_FRAMEWORK.ACTIONS/RESOURCE_TYPES/ROLES三个 dotted path 指向业务侧定义模块(见 bkmonitor/iam/definitions/),Django 启动时load_framework()通过 schema/loaders.py 加载并构造 SchemaRegistry。3.2 iam_engine.policy — 策略表达式
设计意图:把 v3 平台的 dict-formatted 策略 AST(
{"op": "OR", "content": [{"op": "eq", "field": "space.id", "value": "2"}, ...]})翻译成 provider-neutral 的 AST,从而让"策略求值"和"策略消费"这两件事都可以脱离 v3 SDK 独立进行。关键接口:
PolicyExpression:AST 节点。含op(Op 枚举)、field、value、content(子节点)。提供构造快捷方法:PolicyExpression.any()(Op.ANY)、PolicyExpression.none()(Op.NONE)、PolicyExpression(op=Op.IN, field=..., value=(...))。Op枚举:EQ/NE/IN/NOT_IN/LT/GT/AND/OR/ANY/NONE共 10 种。DictEvaluator:本地求值器。evaluator.evaluate(expression, context_dict) -> bool,供filter_visible_resources场景在本地一次批量判定所有候选资源。跨 provider 意义:v4 provider 未来若要实现
query_policy,需要用get_authorized_resources返回列表反构PolicyExpression(用Op.IN或Op.ANY表达),这样filter_visible_resources的本地求值代码就可以 v3/v4 共用。3.3 iam_engine.provider — 权限平台接入契约
设计意图:接入新平台 = 新增一个
PermissionProvider子类 + 实现四个方言层方法,框架其余部分零改动。基类通过模板方法把"编解码、分片、并发、异常统一"这些通用工作固化,子类只负责"打一次平台 API"。关键接口(PermissionProvider):
is_allowed/batch_by_resource/batch_by_action/get_apply_url/filter_visible_resources/has_any_permission/query_policy/query_policy_by_actions/get_apply_data/grant_creator_action。_is_allowed_dialect/_batch_by_resource_dialect_page/_batch_by_action_dialect_page/_get_apply_url_dialect。plan_migration(scope="system"|"full")/apply_migration(plan, dry_run, allow_destructive)/health_check()。schema和**options,options完全由 provider 自己解析(推荐用 dataclass 声明契约类,如V3Options/V4Options)。关键接口(CompositionPolicy):
SinglePolicy:只有一个 provider 时的直通(默认)。AnyOfPolicy:任一 provider 返回 True 即 True,用于灰度切换期。AllOfPolicy:全部 provider 返回 True 才 True,用于双写校验期。PrimaryPolicy:primary 为主,fallback 为辅(primary 不可用时降级)。关键接口(ProviderRouter):
BypassRule 链+CompositionPolicy,作为IAMFramework内部的鉴权入口。关键接口(NameCodec):
encode_action(business_id) -> dialect_id/decode_action(dialect_id) -> business_id。encode_resource_type/encode_resource_id同理。IdentityCodec= 业务命名与方言完全一致;MonitorV3Codec/MonitorV4Codec定义平台差异。3.4 iam_engine.crosscutting — 豁免规则链
设计意图:把"跳过鉴权"这类跨请求、跨 provider 的横切逻辑抽出来,避免每次修改都要改
Permission类。关键接口:
BypassRule:抽象基类,evaluate(request) -> bool | None(None 表示不介入,False 表示不豁免,True 表示豁免)。SettingsSkipRule(读settings.SKIP_IAM_PERMISSION_CHECK)等。IAM_FRAMEWORK.BYPASS_RULES = ["bkmonitor.iam.rules.SettingsSkipRule", ...]。说明:老版本
Permission.is_allowed里的 token 分享豁免、ActionIdMap场景豁免因为强绑定 Djangorequest.token/ApiAuthToken表结构,没有迁到框架层的BypassRule,而是保留在 permission.py: check_iam_preflight,与BypassRule并存。两者互补:前者是业务侧、Django 请求相关的豁免;后者是框架侧、请求无关的豁免。3.5 iam_engine.migration — 迁移体系
设计意图:把"把本地 definitions 同步到 IAM 平台"这件事变成类似 django-migrations 的可复现流程——生成迁移文件、按顺序应用、记录已应用版本,支持 dry-run 和破坏性开关。
关键接口:
MigrationLoader:从磁盘加载迁移文件(Python 模块),按依赖排序。MigrationPlanner:结合 recorder 计算"还有哪些迁移待应用"。MigrationRecorder(接口)+DjangoMigrationRecorder(Django 实现):记录已应用的迁移到数据库表。MigrationPlan(provider_name, changes):可执行的变更集合。Change(kind, change_type, entity_id, ...):单一变更(kind=system/action/resource_type/role;change_type=CREATE/UPDATE/DELETE)。MigrationReport(applied, failed, skipped, ...):apply 后的结构化返回,方便打印和审计。执行流程:
plan_migration(schema)生成本地期望的Change列表 → provider 内部apply_migration()查远端 + reconcile(CREATE 遇已存在 → skip,DELETE 遇不存在 → skip 等)+ 执行。破坏性开关:
--allow-destructive/MIGRATION.allow_destructive控制是否允许 DELETE 类变更;默认禁止,含 DELETE 的 plan 会被整体 skip 并告警。3.6 iam_engine.callback — 回调分发
设计意图:v4 平台反向调我们(iam-callback)需要拉资源列表 / 拉资源详情,业务侧需要注册"grafana_dashboard 类型的资源怎么拉"。老版本 v3 是靠
DjangoBasicResourceApiDispatcher.register("grafana_dashboard", GrafanaDashboardProvider())一处一处 register;新版通过装饰器模式的注册中心统一收敛。关键接口:
@register_list_instance("grafana_dashboard")/@register_fetch_instance_info/@register_search_instance:装饰器,把业务侧 handler 注册进 registry。CallbackService:分发中心。持有 provider 的 codec,接到平台调用后按(callback_type, resource_type)路由到对应 handler。V4PermissionProvider.__init__里importlib.import_module(callback_module)触发所有@register_xxx装饰器运行。3.7 iam_engine.django — Django 集成
设计意图:让整个框架作为一个
INSTALLED_APPS里的 Django app 生效——启动时装配IAMFramework,业务侧通过get_framework()拿单例,命令行提供iam_engine_migrate等 management command。关键接口:
IamEngineConfig:AppConfig,ready()里调load_framework()。load_framework():读settings.IAM_FRAMEWORK→ 构造FrameworkConfig→ 用import_class解析 dotted path → 实例化所有 provider → 构造IAMFramework→ 存入进程级单例。get_framework() -> IAMFramework:业务侧访问入口。Management commands:
iam_engine_migrate:执行迁移(--provider/--dry-run/--skip-system/--allow-destructive/--directory)。iam_engine_makemigrations:根据本地 definitions 与迁移目录已有文件的差异,生成新迁移文件。iam_generate_config:把 provider 的 system_info 转成平台注册所需的 config(供运维手动录入 IAM 平台后台)。manual(默认):只有手动iam_engine_migrate命令触发,与老版本_migrate_iam钩子摘除后行为一致。semi_auto:挂post_migrate信号,跟随manage.py migrate部署脚本触发;破坏性变更由MIGRATION.allow_destructive显式控制。3.8 iam_v3 实现
设计意图:把老版本散落在
bkmonitor/iam/permission.py/bkmonitor/iam/resource.py/bkmonitor/iam/compatible.py里的 v3 逻辑全部收敛到bkmonitor/iam/iam_v3/子包,作为PermissionProvider的一个具体实现;对外表现与老版本 v3 行为完全一致(跨版本对齐由 test_permission_interface.py 保证)。关键类:
iam.IAM(IAM SDK),覆盖_do_policy_query/_do_policy_query_by_actions,注入两项 v3 特有逻辑:view_business→ 双查旧view_business_v1)做 OR 合并;由enable_v1_compat构造参数控制。ACTION_COMPATIBLE_ALIASES = {"new_dashboard": ["manage_dashboard", "manage_datasource"]},仅在 policy_query 层生效(is_allowed 直连平台走原动作)。filter_visible_resources(用query_policy拿 AST +DictEvaluator本地求值,保留{rt}.idIN/EQ 快速路径) +has_any_permission(AST 非空即真近似判定)+plan_migration/apply_migration(委托V3Migrator)。SchemaRegistry中的 action / resource_type / role 定义翻译成 v3 平台的add-action/add-resource-type等 API 调用;reconcile 已有资源避免重复 create。space↔space,view_business↔view_business),少量特殊映射走 codec 兜底。3.9 iam_v4 实现
设计意图:v4 平台是全新的 RBAC 模型,从 API 契约到资源模型都与 v3 不同。本次实现不复用任何 v3 代码,从零构造一个原生 HTTP 客户端 + provider + migrator + callback,通过
PermissionProvider契约统一表现,让上层调用无感知。关键类:
direct_auth/direct_auth_by_resources/direct_auth_by_actions:单条 / 批量鉴权。generate_perm_apply_url:生成申请页 URL。get_authorized_resources:拉用户已授权资源列表(供 filter_visible_resources 用)。add_authorization:授权(供grant_creator_action用)。bk_tenant_id参数写入X-Bk-Tenant-IdHTTP 头。_CACHED_TOKEN存 v4 auth token(供 callback 校验用)。subject.tenant_id从内部dict[tenant → V4Client]拿客户端;实现方言层 +filter_visible_resources(顶层资源反向列举走get_authorized_resources,实例级走正向批量鉴权)。plan_migration/apply_migration签名一致),只是底层调 v4 平台的 upsert/delete API。views.py:DRF View 接收 v4 平台的资源枚举 / 详情查询请求。auth.py: IamCallbackAuthentication:用 v4 平台下发的 auth_token 校验(每小时刷新)。3.10 adapters — v3/v4 共用适配层
设计意图:
Codec和ResourceResolver是"业务侧"的,不该放到iam_engine(框架应该 domain-neutral);也不该重复放到iam_v3/iam_v4(避免两地维护)。所以单独抽bkmonitor/iam/adapters/目录,作为两个 provider 共用的业务适配层。关键类:
resource.type分派到_resolve_space/_resolve_apm/_resolve_grafana/_resolve_rum;补name与ancestor_chain。3.11 definitions — 业务侧元数据
设计意图:把"监控平台有哪些业务动作、资源类型、角色"作为单一定义源(single source of truth),schema 层从这里加载,framework 层从这里派生,v3 / v4 provider 迁移工具也是从这里读取生成注册包。
关键文件:
Actions.VIEW_BUSINESS/MANAGE_APM_APPLICATION/NEW_DASHBOARD/ ... 共 60+ 个动作。ResourceTypes.SPACE/APM_APPLICATION/GRAFANA_DASHBOARD/RUM_APPLICATION。Roles.BUSINESS_OPERATOR/BUSINESS_MEMBER/ ...)。新增一个动作 = 在
actions.py加一个ActionDef成员;然后跑iam_engine_makemigrations生成迁移文件;再跑iam_engine_migrate应用到平台。全流程与 django-migrations 类似。四、上层入口收口
4.1 Permission Facade
位置:bkmonitor/bkmonitor/iam/permission.py
设计意图:
Permission是历史遗留的对外唯一入口,散落在整个 bkmonitor 代码库有几百处Permission().is_allowed(...)/Permission().filter_space_list_by_action(...)之类的调用。改造的第一原则是这些调用一行都不改——所以Permission保留成一个薄 facade,构造签名、方法签名、异常语义全部与老版本对齐;内部实现完全委托IAMFramework。关键改动:
__init__(username, bk_tenant_id, request)self.iam_client = self.get_iam_client()self._fw = get_framework();旧iam_client字段废弃is_allowed(action, resources=None)check_iam_preflight→self._fw.is_allowed(AuthRequest(...))is_allowed_by_biz(bk_biz_id, action)fw.is_allowed+ 自动构造 space resourcebatch_is_allowed(actions, resources)batch_by_resource批量调用filter_space_list_by_action(action)fw.filter_visible_resources(subject, action_id, candidates)grant_creator_action(resource_type, resource_id, ...)get_apply_url(action_ids, resources)fw.get_apply_url(ApplyURLRequest(...))get_iam_client(bk_tenant_id)V3Client;仅供两处 v3 平台集成点使用(反向回调 dispatcher + V1 遗留迁移工具),禁止新增调用方list_actions()NotImplementedError,端点被调用会 500,见 §8.2豁免逻辑收敛:老版本
is_allowed/batch_is_allowed里各写一份 token / skip_check 豁免;新版本抽出check_iam_preflight(request, action)与check_iam_batch_preflight(request, actions)两个纯函数,Permission与drf.py共用;语义与老版本逐行对齐(修复了老版本的生成器恒真 bug 和ActionIdMapKeyError 风险)。4.2 DRF 权限类与批量装饰器
位置:bkmonitor/bkmonitor/iam/drf.py
设计意图:DRF 权限类(
IAMPermission/BusinessActionPermission/InstanceActionPermission/MCPPermission/InstanceActionForDataPermission)是所有 REST API 的鉴权入口,签名不能变;同时insert_permission_field/filter_data_by_permission是响应注入 / 数据过滤的两个装饰器/函数,业务侧到处在用。老版本这几处各自Permission().is_allowed/Permission().batch_is_allowed,本次统一委托get_framework()。关键改动:
_fw_check_any(request, action_refs, resources):所有权限类的公共实现。逐个 action 先走check_iam_preflight前置豁免,再fw.is_allowed(...);全部拒绝时fw.get_apply_url生成 apply_url 并抛PermissionDeniedError(context, data={"apply_url": ...})——与老版本抛法一致。IAMPermission(actions, resources):构造签名保留;has_permission内部走_fw_check_any。BusinessActionPermission(actions):从 request 解析出bk_biz_id,构造FwResource(type="space", id=bk_biz_id);然后走_fw_check_any。InstanceActionPermission(actions, resource_type_id, iam_instance_id_key, get_instance_id):从 request / view kwargs 解析实例 ID;然后走_fw_check_any。insert_permission_field(actions, resource_meta, ...):装饰器,在响应 dict 上注入permission字段。内部:check_iam_batch_preflight→fw.batch_by_resource(BatchByResourceRequest(...))按每个 action 拉一次批量鉴权,回填到每一项的permissiondict。filter_data_by_permission(bk_tenant_id, data, actions, resource_meta, mode="any"|"all"|"insert", ...):三种模式:"any":任一 action 通过即保留数据;"all":全部 action 通过才保留;"insert":不过滤,只注入permission字段。iam.Resource 完全消除:老版本
drf.py里到处iam.Resource(...)构造对象;新版本内部只用FwResource(type=..., id=...);切换 v3/v4 时drf.py一行不动。兼容参数保留:
insert_permission_field/filter_data_by_permission的instance_create_func/batch_create参数保留(外部调用方可能还在传),当前实现不再使用,注释已明确标注,属于未来 tech debt。4.3 Grafana 鉴权
位置:bkmonitor/packages/monitor_web/grafana/permissions.py
设计意图:Grafana 集成有两类权限判定——空间级角色(Editor/Viewer)与 dashboard 实例级权限。老版本走"绕开 Permission 类,直接调 SDK";新版本全部收口到框架。
关键改动:
get_user_role(bk_biz_id) -> GrafanaRole:判定当前用户在指定业务空间下的 Grafana 角色(Editor / Viewer / None)。内部走Permission.is_allowed_by_biz(bk_biz_id, ActionEnum.MANAGE_DASHBOARD / VIEW_DASHBOARD),异常捕获收敛为(AuthAPIError, ProviderError, ProviderUnavailable)(pr1 修复:老版本except AuthAPIError在 v4 下失效)。has_any_dashboard_permission(user, bk_biz_id) -> bool:判定用户是否有该业务下任意 dashboard 的权限。内部走fw.has_any_permission(subject, ActionEnum.VIEW_SINGLE_DASHBOARD.id)。GrafanaReadPermission:DRF 权限类,允许"角色为空但有实例级权限"的用户进入 Grafana UI;下游filter_visible_resources二次过滤保证数据不泄露。filter_visible_dashboards(user, bk_biz_id, dashboards) -> list:批量过滤可见 dashboard;走fw.filter_visible_resources(subject, action_id, candidates),v3 单次策略拉取 + 本地求值,v4 顶层反向列举 + 批量鉴权。4.4 兼容层保留
位置:bkmonitor/iam/init.py、iam/resource.py、iam/action.py
设计意图:老版本外部有大量
from bkmonitor.iam import ResourceEnum、from bkmonitor.iam.resource import Business, ApmApplication, ...、from bkmonitor.iam.action import ActionEnum, ActionMeta等 import。这些符号即使新框架完全不用,也必须保留为兼容包装,避免 CI 全站 ImportError。保留清单:
ResourceEnum.BUSINESS / APM_APPLICATION / GRAFANA_DASHBOARD / RUM_APPLICATIONBusiness/ApmApplication/ ... 类本身Business/ApmApplication/GrafanaDashboard/RumApplicationResourceTypeDef派生;create_instance(id)返回FwResourceActionEnum.VIEW_BUSINESS / ...Actions.VIEW_BUSINESS通过 property 兼容ActionMeta.type/.related_resource_types通过 property 提供MINI_ACTION_IDSget_action_by_id(id)已删除:
ActionMeta.selection_mode/parent_resource/related_instance_selections等只有老版本 iam SDK 才用到的字段(新框架里由 codec + resolver 承担对应职责,业务层不再需要)。五、兼容性 & 迁移
5.1 上层调用兼容清单
以下所有对外符号在 v3 → v4 切换过程中签名保持不变,业务代码不需要跟随改动:
Permission()/Permission(username, bk_tenant_id)/Permission(request=...)Permission.is_allowed(action, resources=None)fw.is_allowedPermission.is_allowed_by_biz(bk_biz_id, action)fw.is_allowed+ 自动构造 space resourcePermission.batch_is_allowed(actions, resources)fw.batch_by_resourcePermission.filter_space_list_by_action(action)fw.filter_visible_resourcesPermission.grant_creator_action(resource_type, resource_id, ...)Permission.get_apply_url(action_ids, resources)fw.get_apply_urlPermission.get_iam_client(bk_tenant_id)Permission.list_actions()NotImplementedError,端点被调用会 500,见 §8.2bkmonitor.iam.drf.IAMPermission等 5 个权限类_fw_check_anyinsert_permission_field(...)/filter_data_by_permission(...)instance_create_func/batch_create)ResourceEnum/Business/ActionEnum/ActionMeta/MINI_ACTION_IDScheck_allowed/check_allowed_by_action_ids/get_authority_detail/get_authority_apply_info等 REST 端点admin.permission.query_user_permissionsRPC 端点fw.query_policies_by_actions+ 权限树解析5.2 数据兼容
check_iam_preflighthas_permission里用ExternalPermission.resources作候选还原(上轮已修复)system_id/action_id/resource_type_id完全不变;support-files/iam/*.json手写模型保留但不再作为初始化来源,未来靠iam_engine_migrateV4Migrator从 definitions 派生并 push;system_id / action_id / resource_type_id (避免二套元数据)5.3 迁移工具
三个命令:
python manage.py iam_engine_migrate:执行迁移。--provider {v3,v4}:只操作指定 provider;不指定则全部。--dry-run:只打印计划,不执行。--skip-system:跳过系统注册前置步骤(只跑迁移文件)。--allow-destructive:允许破坏性变更(DELETE / 方言 id 变更重建);默认取MIGRATION.allow_destructive配置值。--directory:迁移文件目录(默认从MIGRATION.directory读取)。python manage.py iam_engine_makemigrations --name <变更描述>:根据本地definitions/与迁移目录已有文件的差异,生成新的迁移文件(provider-neutral,单一目录,不区分 v3 / v4)。--name <slug>:必填,迁移描述名(如add_incident_actions),最终文件名000X_<slug>.py。--directory:可选,覆盖默认目录(默认从MIGRATION.directory读取)。--dry-run:只打印将要生成的内容,不写文件。python manage.py iam_generate_config --provider {v3,v4}:把 provider 的get_system_info()导出成平台注册所需的 JSON / dict 配置。两种执行模式:
manualiam_engine_migrate命令--allow-destructive参数(默认取自MIGRATION.allow_destructive)semi_autopost_migrate信号,跟随manage.py migrate部署脚本触发MIGRATION.allow_destructive配置项_migrate_iam钩子对齐破坏性开关矩阵:
manualFalse(配置)+ 不加 CLI flagmanualsemi_autoFalsemanage.py migrate触发,DELETE skip 告警semi_autoTruemanage.py migrate触发,允许 DELETE5.4 部署 checklist
方式 A:手动部署(
MIGRATION.mode = "manual")适用场景:私有化 / 生产环境,希望人工控制迁移时机与破坏性操作。
首次上线(保持 v3-only,与老版本平级):
bkmonitor/iam/iam_migrations/下的迁移文件已随本次改动一起提交(迁移文件是框架级共享的,v3 / v4 provider 下发时共用同一份)。IAM_FRAMEWORK.PROVIDERS保持 v3-only(默认配置),MIGRATION.mode = "manual"。python manage.py iam_engine_migrate --provider v3 --dry-run查看计划(不加--provider则默认对配置中所有 provider 依次执行);python manage.py iam_engine_migrate --provider v3真实执行;--allow-destructive才会真正下发(若已在MIGRATION.allow_destructive = True配置中开启,则默认允许,可省略 flag)。Permission.is_allowed/ DRF 权限类 / Grafana / kernel_api RPC / v3 反向回调 五条链路各手验一次。切换到 v4:
definitions/又有新增/修改时才需要重新iam_engine_makemigrations --name <desc>并提交。IAM_FRAMEWORK.PROVIDERS替换为 v4 provider 配置块。iam_engine_migrate --provider v4 --dry-run查看变更计划。iam_engine_migrate --provider v4(如需 DELETE 追加--allow-destructive)。方式 B:半自动部署(
MIGRATION.mode = "semi_auto")适用场景:CI / 容器化部署,希望
manage.py migrate触发时顺带把 IAM 元数据也带上。首次上线(保持 v3-only):
IAM_FRAMEWORK.PROVIDERS保持 v3-only;MIGRATION.mode = "semi_auto";MIGRATION.allow_destructive按环境策略配置(生产建议False,测试环境可True)。python manage.py migrate会通过post_migrate信号自动触发iam_engine_migrate:allow_destructive=False:DELETE 操作只打告警日志、不执行(等价于 dry-run 掉 DELETE 部分,其余变更正常下发);allow_destructive=True:DELETE 操作也会真实执行。切换到 v4:
definitions/又有新增/修改时才需要重新iam_engine_makemigrations --name <desc>并提交。IAM_FRAMEWORK.PROVIDERS加入 v4;MIGRATION.mode仍保持semi_auto。manage.py migrate会自动对配置中所有 provider 依次执行迁移;DELETE 行为由allow_destructive控制。六、配置示例与术语表
6.1
settings.IAM_FRAMEWORK配置项字段说明(对应 iam_engine/core/config.py 的
FrameworkConfigdataclass):ACTIONSActions类,含所有 ActionDef 成员)RESOURCE_TYPESROLESPROVIDERSlist[dict]COMPOSITION.policysingle/any_of/all_of/primarysingleCOMPOSITION.options{}max_workers/strict_errors/fallback_on_error等)MIGRATION.modemanual/semi_automanualMIGRATION.directory""MIGRATION.allow_destructiveFalseBYPASS_RULES[]每个 provider 配置块结构:
classbkmonitor.iam.iam_v3.provider.V3PermissionProvider/bkmonitor.iam.iam_v4.provider.V4PermissionProvider)options__init__的 kwargs;结构由 provider 自己决定(V3Options/V4Options)6.2 完整配置示例
v3-only(当前默认配置):
v4-only(切换到 v4 后的配置):
组合模式(v3 → v4 灰度期):
6.3 术语表
view_business在 v3 是view_business,v4 可能是view_business_v4;codec 负责在鉴权调用出/入站时双向对齐。type + id,resolver 查数据库补name/ancestor_chain。single/any_of/all_of/primary。_bk_iam_path_)。BypassRule链表达(框架层)+check_iam_preflight表达(业务层)。ActionDef/ResourceTypeDef/RoleDef的集合,跨 provider 共享。query_policy结果传递、filter_visible_resources的本地求值。plan_migration生成 Change 列表,apply_migration查远端 + reconcile + 执行。--allow-destructive/MIGRATION.allow_destructive显式开启。7 测试
本次为 iam_engine 鉴权框架重构补充的单元测试,覆盖框架核心(schema / codec / 配置 / 迁移)、v3 / v4 provider、对外兼容层(Permission / DRF 权限类 / ResourceEnum)以及 Grafana 鉴权等模块,均为本地单元测试(mock 或本地 DB),不依赖真实 IAM 平台。以下 13 个测试文件共 325 个用例已全部跑通(325 passed / 0 failed):
bkmonitor.iam.action对外接口兼容性(ActionEnum / ActionMeta)与新旧 codec 对照涉及真实 IAM 平台的测试(远端只读 / 迁移变更类)需在配置好测试服务器与
.env环境变量的环境执行,未纳入本次本地跑测范围。八、已知问题
8.1 v4 平台能力缺口
以下两处属于"v4 平台目前不提供对应能力,只能保守处理"的已知折衷,v4 正式上线前需要与平台侧对齐或专项决策:
has_any_permission保守放行 True:位置 iam_v4/provider.py。v4 平台无"用户是否对某 action 存在任意授权"的低成本查询接口,provider 只能返回 True,由下游filter_visible_resources二次过滤兜底。数据不会泄露,但 UI 会呈现"入口可见但内容空"。query_policy/query_policy_by_actions未实现:位置 iam_v4/provider.py。v4 RBAC 模型没有直接的策略 AST 查询接口。8.2 Permission.list_actions() 端点改动
问题现象:
Permission.list_actions()端点 500(get_authority_meta):bkmonitor/iam/permission.py 该方法主体只有raise NotImplementedError,但 monitor_web/iam/resources.py 的GetAuthorityMetaResource(路由GET rest/v2/iam/get_authority_meta/)仍在调用,一旦被请求即刻 500;对应回归点在 tests/iam/refactor/test_permission_interface.py 的TestKnownRegressions.test_list_actions_returns_action_list(当前xfail(strict=True))。前端使用情况(已审计,结论:暂无任何实际消费方):
getAuthorityMeta()action,行为一致:GET rest/v2/iam/get_authority_meta/→transformDataKey(snake_case → camelCase 递归转换)→ 存入authorityMetastate。涉及 webpack/src/monitor-pc/store/modules/authority.ts、webpack/src/fta-solutions/store/modules/authority.ts、webpack/src/apm/store/modules/authority.ts、webpack/src/trace/store/modules/authority.ts,API 定义在 webpack/src/monitor-api/modules/iam.js。webpack/monitor/js/*.js)中均没有任何组件 / 路由守卫 / mixin 读取authorityMeta,store 的getAuthorityMeta也无人 dispatch —— 属于上游遗留的"死代码/僵尸脚手架",该端点返回值当前对前端无影响,即使 500 也不会在页面暴露。新旧契约对比:
list_actions()透传iam_client._client.query(BK_IAM_SYSTEM_ID)的data["actions"],即 v3 平台 action model 数组,id为 v3 平台 ID(如view_business_v2),其余字段name / name_en / type / version / related_resource_types / related_actions / description / description_en。ActionMeta.to_json()(bkmonitor/iam/action.py)输出的键与 v3 action model 逐一对应,结构完全兼容;唯一差异是id值变为业务 ID(如view_business)。结论与决策:
由于前端无人消费
authorityMeta,无需为兼容 v3 平台 ID 保留_v2命名——直接以业务 ID + v3 兼容结构返回即可(与前端其余鉴权端点check_allowed_by_action_ids等使用的业务 ID 命名一致,反而更自洽)。修复方案:重新实现list_actions()为 schema 驱动、provider-neutral,从框架 action 定义派生(如[ActionMeta.from_def(a).to_json() for a in _all_actions.values()]),恢复端点 200 并解除对应 xfail;前端 4 个 store 的死代码可另行清理。注意(反方向别名问题,与 list_actions 无关但同属本次 review 结论):前端源码中另有 ~95 处硬编码 v3 平台 ID(view_business_v2等)原样流入check_allowed_by_action_ids/check_allowed/fta alert allowed_biz/get_authority_detail等端点,v3 provider 下因 codec 恒等"意外可用",切 v4 后会在 v4 平台查不到 action 而拒绝;需在Permissionfacade 边界做 v3 别名 → 业务 ID 归一化,作为 v4 切换的兼容层(另行跟踪)。8.3 僵尸代码残留
本次改造把老 IAM 相关的"数据/元数据搬运"链路整体切到了
iam_engine的iam_engine_migrate命令 +bkmonitor/iam/iam_migrations/目录下,但为了不影响回滚,老的搬运链路(bkmonitor/iam/migrate.py+bkmonitor/iam/migrations/+support-files/iam/*.json+ 一个 legacy management command)代码物理上仍然保留、只是不再被任何入口驱动。遗物清单(本次改造已确认全部处于孤儿状态、无 live driver):
PolicyMigrator类migrations/0001_initial.py/0002_single_dashboard.pyimport;这两个脚本自己也已经无 driver。__init__.pybkmonitor.migrate.BaseMigration基类注册,全局 grep 未发现任何Migrator("iam", "bkmonitor.iam.migrations")或等价驱动代码。0001/0002 用PolicyMigrator,0003~0014 用IAMMigrator("xxx.json")。IAMMigrator("XXXX.json")引用;上层链路已死,这里也随之失效。IAMMigrator("legacy.json")/IAMMigrator("initial.json"),但这两个 JSON 已不在support-files/iam/目录中,即便手动跑也会直接报错。Migrator类BaseMigration基类还在给上面这些老 migration 脚本当父类;但Migrator调度类没有任何调用方。若清理上面 14 个脚本,Migrator也应一并删除;BaseMigration类是否保留取决于其他 app 是否复用(需先做全局审计)。