Skip to content

feat: 【监控平台】引入 iam_engine 鉴权框架并接入 v3/v4 provider - #12031

Draft
qiushui175 wants to merge 53 commits into
TencentBlueKing:masterfrom
qiushui175:feature/iam-v4-support
Draft

feat: 【监控平台】引入 iam_engine 鉴权框架并接入 v3/v4 provider#12031
qiushui175 wants to merge 53 commits into
TencentBlueKing:masterfrom
qiushui175:feature/iam-v4-support

Conversation

@qiushui175

@qiushui175 qiushui175 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

feat: 【监控平台】引入 iam_engine 鉴权框架并接入 v3/v4 provider

TL;DR

本次改造对监控平台的 IAM 鉴权层做一次完整的分层重构:把散落在 Permission / DRF 权限类 / Grafana / kernel_api RPC 等 10+ 个入口的 IAM 直连调用,统一收口到一个 provider-neutral 的鉴权框架 iam_engine,并同时接入了 v3(既有模型)与 v4(新模型)两种后端 provider。

  • 上层调用零感知Permission.is_allowed / filter_data_by_permission / insert_permission_field 等外部 API 全部保留旧签名,业务代码不需要改动就能跟随框架从 v3 切到 v4。
  • provider 可切换、可组合:一行 IAM_FRAMEWORK.PROVIDERS 配置切换 v3/v4。
  • 统一资源与操作定义bkmonitor/iam/definitions/ 一份 SSOT(Single Source of Truth),SchemaRegistry 冻结加载后,v3/v4 provider、迁移工具、平台反向回调、insert_permission_field 全部从这里派生;老版本要在 ResourceEnum / ActionEnum / support-files/iam/*.json / SDK Provider register 四处同步维护。
  • 统一鉴权处理流程 + 极简扩展契约:所有鉴权路径(单条 / 批量 / 反向列举 / 策略查询 / apply_url / 创建者授权 / 豁免)收敛到 IAMFramework 门面 + PermissionProvider 契约;接入新 IAM 平台 = 一个 PermissionProvider 子类 + 四个方言层抽象方法。

详细的架构分层与关键决策见下文。

一图看懂

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
Loading

一、背景与目标

1.1 现状痛点

老版本鉴权(本次改造前的 master)存在以下问题:

  1. 直连 SDK,无法解耦Permission 类直接持有 iam.IAM 客户端,实例化、鉴权、策略查询、迁移等逻辑全都堆在同一个类里;bkmonitor/iam/drf.py / packages/monitor_web/grafana/permissions.py 等消费方各自 Permission().is_allowed(...),SDK 细节泄漏到业务层。
  2. v3 硬编码,无法接入 v4:v3 平台的策略 AST(Op.OR/Op.AND/{rt}.id/_bk_iam_path_)、资源 ID 拼接(ancestors + "/" 格式)散落在 bkmonitor/iam/permission.pybkmonitor/iam/resource.pypackages/monitor_web/iam/views.py 各处;v4 平台走不同的模型,硬要接入必然是重写。
  3. 资源与操作定义散落多处:新增一个 action 需要同步改 4 个地方:bkmonitor/iam/action.pyActionEnumbkmonitor/iam/resource.pyResourceEnumsupport-files/iam/*.json 手写模型、monitor_web/iam/views.py 的 SDK Provider register。任一处漏改都会导致"能鉴权但拉不到资源"、"平台注册了但代码里查不到" 之类的错位。
  4. 鉴权入口分散、豁免语义不一致
    • Permission.is_allowed 里有一段 token / skip_check / ActionIdMap 的豁免逻辑;
    • bkmonitor/iam/drf.py 里的 IAMPermission / BusinessActionPermission 各自重复了一份豁免;
    • Grafana has_any_dashboard_permission 直接绕开 Permission,另调一次 SDK。
      同一个"用户是否有权限"的问题,被四五种不同的路径回答,语义随处漂移。
  5. 策略 AST 泄漏到业务层monitor_web/iam/resources.py 等业务代码里直接处理 v3 平台返回的 dict AST(识别 Op.OR / Op.IN / {rt}.id / _bk_iam_path_),策略结构一旦变化业务侧全部要跟着改。

1.2 改造目标

  • 上层调用零感知:业务代码在切换 v3/v4 时不需要改一行;Permission / drf.py / filter_data_by_permission / insert_permission_field 等对外符号保留旧签名与语义。
  • provider 可切换、可组合:v3 / v4 各是一个独立的 PermissionProvider 实现,IAM_FRAMEWORK.PROVIDERS 一行切换。
  • 统一资源与操作定义bkmonitor/iam/definitions/ 作为 SSOT,SchemaRegistry 冻结加载后,v3/v4 provider、迁移工具、平台反向回调、insert_permission_field 全部从这里派生;新增一个 action 只需改一处。
  • 统一鉴权处理流程 + 极简扩展契约:所有鉴权路径(单条 / 批量 / 反向列举 / 策略查询 / apply_url / 创建者授权 / 豁免)收敛到 IAMFramework 门面 + PermissionProvider 契约;接入新 IAM 平台 = 一个 PermissionProvider 子类 + 四个方言层抽象方法;PolicyExpression AST 让"反向列举可见资源"在不同后端上共用同一套心智模型;BypassRule + check_iam_preflight 收敛豁免语义。

二、总体架构

2.1 分层设计

iam_engine 采用六层结构,每一层只依赖下方(或同层的核心 core):

目录 职责 关键类
schema iam_engine/schema/ 定义 Action / ResourceType / Role 的元数据类型,管理注册表与差异比较 ActionDef / ResourceTypeDef / RoleDef / SchemaRegistry
policy iam_engine/policy/ 策略表达式 AST 与本地求值器 PolicyExpression / Op / DictEvaluator
provider iam_engine/provider/ 权限平台接入契约、组合策略、编解码、资源补全、路由 PermissionProvider / CompositionPolicy / NameCodec / ResourceResolver / ProviderRouter
migration iam_engine/migration/ 迁移文件加载、diff、planner、recorder MigrationLoader / MigrationPlanner / MigrationRecorder
crosscutting iam_engine/crosscutting/ 横切关注点:豁免规则链 BypassRule
django iam_engine/django/ Django 集成(AppConfig、settings 装配、management 命令) IamEngineConfig / load_framework / get_framework

以及框架之外、业务层面的适配:

目录 职责
iam_engine/callback/ 回调服务,供 iam_v4 callback 视图分发到业务侧 handler
iam_engine/core/ 值对象(Subject / ResourceInstance / AuthRequest 等)、异常、IAMFramework 门面、utils
bkmonitor/iam/iam_v3/ v3 provider 实现(V3PermissionProvider / V3Client / V3Migrator
bkmonitor/iam/iam_v4/ v4 provider 实现(V4PermissionProvider / V4Client / V4Migrator / callback)
bkmonitor/iam/adapters/ 业务侧适配:MonitorV3Codec / MonitorV4Codec / MonitorResourceResolver
bkmonitor/iam/definitions/ 业务元数据定义:Actions / ResourceTypes / Roles
bkmonitor/iam/permission.py Permission Facade(保留旧对外签名)
bkmonitor/iam/drf.py DRF 权限类 / insert_permission_field / filter_data_by_permission

2.2 关键抽象

以下八个概念构成整个框架的骨架,理解它们再看代码基本无障碍:

  • IAMFrameworkcore/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 等方法。
  • PermissionProviderprovider/base.py):所有 IAM 平台接入的唯一扩展契约,模板方法模式。基类完成"业务命名 ↔ 平台方言"编解码、批量分片、并发;子类只实现 _is_allowed_dialect / _batch_by_resource_dialect_page / _batch_by_action_dialect_page / _get_apply_url_dialect 四个方言层抽象方法即可接入新平台。
  • CompositionPolicyprovider/composition/base.py):多 provider 组合策略:
    • single:只用一个 provider(默认);
    • any_of:任一 provider 允许即允许(用于灰度切换 / 迁移期兼容);
    • all_of:全部 provider 允许才允许(用于双写校验期);
    • primary:以 primary 为主,fallback 为辅(primary 不可用时降级)。
  • NameCodecprovider/codec.py):业务命名 ↔ 平台方言的编解码器。同一个 "view_business" 业务动作,v3 可能编码为 view_business,v4 编码为 view_business_v4;同一个 "space" 资源类型,v3 是 space,v4 可能是 bk_biz。所有编解码约定由 MonitorV3Codec / MonitorV4Codec 集中管理。
  • ResourceResolverprovider/resolver.py):资源实例补全器。业务只给 type + id,resolver 查数据库补上 nameancestor_chain。v3/v4 共用 MonitorResourceResolver
  • SchemaRegistryschema/registry.py):冻结的元数据注册表,通过 IAM_FRAMEWORK.ACTIONS/RESOURCE_TYPES/ROLES 三个 dotted path 从业务定义加载;跨 provider 共享,使用业务规范化命名。
  • PolicyExpression ASTpolicy/expression.py):provider-neutral 的策略表达式抽象。v3 从平台拿到的 dict AST 会被翻译成 PolicyExpression,v4 未来会从 get_authorized_resources 结果反构。字面量 ID 一律是业务命名。
  • BypassRulecrosscutting/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
Loading

关键说明:

  • 两级豁免check_iam_preflight(业务侧:token 分享、settings.SKIP_IAM_PERMISSION_CHECK)与 BypassRule 链(框架侧)并存;前者复刻旧版 Permission.is_allowed 的豁免语义,后者是框架层的横切扩展点。两者互不冲突,前者先执行。
  • provider 内部两层结构is_allowed(业务命名) → _is_allowed_dialect(方言命名)。基类完成 codec 编解码、批量分片、并发,子类只处理"打一次平台 API"。
  • codec 双向对齐:出站 encode(业务 → 方言),入站 decode(方言 → 业务);上层永远只见业务命名。

2.4 目录布局

改造后 bkmonitor/bkmonitor/iam/ 与相关模块的目录结构:

bkmonitor/bkmonitor/iam/
├── __init__.py                          # 兼容 export:ResourceEnum / Business / ...
├── permission.py                        # Permission Facade(对外唯一入口)
├── drf.py                               # DRF 权限类 + insert_permission_field / filter_data_by_permission
├── action.py                            # 兼容层:ActionEnum / ActionMeta / MINI_ACTION_IDS
├── resource.py                          # 兼容层:ResourceEnum / Business / ApmApplication / ...
├── migrate.py                           # V1→V2 遗留策略搬迁器(保留,未挂钩)
├── definitions/                         # 业务元数据(provider 无关)
│   ├── actions.py                       # Actions.VIEW_BUSINESS / MANAGE_APM_APPLICATION / ...
│   ├── resource_types.py                # ResourceTypes.SPACE / APM_APPLICATION / GRAFANA_DASHBOARD / RUM_APPLICATION
│   └── roles.py                         # Roles.BUSINESS_OPERATOR / ...
├── adapters/                            # 业务侧适配层(provider 共用)
│   ├── resolver.py                      # MonitorResourceResolver(v3/v4 共用)
│   ├── v3/codec.py                      # MonitorV3Codec
│   └── v4/codec.py                      # MonitorV4Codec
├── iam_v3/                              # v3 provider 实现
│   ├── client.py                        # V3Client(继承 iam.IAM + V1 兼容 + 动作别名)
│   ├── config.py                        # V3Options
│   ├── provider.py                      # V3PermissionProvider
│   └── migrator.py                      # V3Migrator
├── iam_v4/                              # v4 provider 实现
│   ├── client.py                        # V4Client(原生 HTTP + 多租户)
│   ├── config.py                        # V4Options
│   ├── provider.py                      # V4PermissionProvider
│   ├── migrator.py                      # V4Migrator
│   └── callback/                        # v4 平台反向回调
│       ├── views.py                     # DRF View
│       └── auth.py                      # IamCallbackAuthentication
└── iam_engine/                          # 通用鉴权框架(业务无关)
    ├── core/                            # 值对象 + 门面 + 异常
    │   ├── types.py                     # Subject / ResourceInstance / AuthRequest / ...
    │   ├── framework.py                 # IAMFramework
    │   ├── config.py                    # FrameworkConfig / MigrationConfig
    │   ├── exceptions.py
    │   └── capabilities.py
    ├── schema/                          # 元数据层
    │   ├── definitions.py               # ActionDef / ResourceTypeDef / RoleDef
    │   ├── registry.py                  # SchemaRegistry
    │   ├── loaders.py                   # 模块 dotted path 加载器
    │   └── diff.py                      # Change / MigrationPlan / MigrationReport
    ├── policy/
    │   ├── expression.py                # PolicyExpression / Op
    │   └── evaluator.py                 # DictEvaluator(本地求值)
    ├── provider/
    │   ├── base.py                      # PermissionProvider ABC
    │   ├── codec.py                     # NameCodec / IdentityCodec
    │   ├── resolver.py                  # ResourceResolver ABC
    │   ├── router.py                    # ProviderRouter(bypass + composition 组合)
    │   ├── dialect_types.py             # DialectAuthRequest / DialectResource / ...
    │   └── composition/                 # single / any_of / all_of / primary
    ├── migration/
    │   ├── loader.py                    # 迁移文件加载
    │   ├── planner.py                   # 待应用迁移排序
    │   └── diff.py                      # 与远端 diff
    ├── crosscutting/
    │   └── bypass.py                    # BypassRule
    ├── callback/
    │   ├── registry.py                  # @register_list_instance 等装饰器
    │   └── service.py                   # CallbackService 分发
    └── django/
        ├── apps.py                      # IamEngineConfig(AppConfig.ready 触发 load_framework)
        ├── conf.py                      # load_framework:从 settings 装配
        ├── facade.py                    # get_framework() 单例访问
        ├── migration_recorder.py        # DjangoMigrationRecorder(记录已应用的迁移文件)
        ├── signals.py                   # post_migrate 挂钩(semi_auto 模式使用)
        └── management/commands/
            ├── iam_engine_migrate.py        # 迁移执行命令
            ├── iam_engine_makemigrations.py # 生成迁移文件
            └── iam_generate_config.py       # 生成平台注册所需的 config

三、核心模块详解

本节采用"设计意图 + 关键接口"的轻量描述,实现细节直接引用源码链接。

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) -> ActionDef
    • get_resource_type(id) -> ResourceTypeDef
    • all_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 枚举)、fieldvaluecontent(子节点)。提供构造快捷方法: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.INOp.ANY 表达),这样 filter_visible_resources 的本地求值代码就可以 v3/v4 共用。

3.3 iam_engine.provider — 权限平台接入契约

设计意图接入新平台 = 新增一个 PermissionProvider 子类 + 实现四个方言层方法,框架其余部分零改动。基类通过模板方法把"编解码、分片、并发、异常统一"这些通用工作固化,子类只负责"打一次平台 API"。

关键接口(PermissionProvider)

  • 接口层(业务命名,基类实现,final):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**optionsoptions 完全由 provider 自己解析(推荐用 dataclass 声明契约类,如 V3Options / V4Options)。

关键接口(CompositionPolicy)

  • SinglePolicy:只有一个 provider 时的直通(默认)。
  • AnyOfPolicy:任一 provider 返回 True 即 True,用于灰度切换期。
  • AllOfPolicy:全部 provider 返回 True 才 True,用于双写校验期。
  • PrimaryPolicy:primary 为主,fallback 为辅(primary 不可用时降级)。

关键接口(ProviderRouter)

  • 组合 BypassRule 链 + CompositionPolicy,作为 IAMFramework 内部的鉴权入口。
  • 单一职责:先跑 bypass,再跑 composition,最终把结果聚合。

关键接口(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 场景豁免因为强绑定 Django request.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 / rolechange_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。

关键接口

  • IamEngineConfigAppConfigready() 里调 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 保证)。

关键类

  • V3Clientiam_v3/client.py):继承 iam.IAM(IAM SDK),覆盖 _do_policy_query / _do_policy_query_by_actions,注入两项 v3 特有逻辑:
    1. V1→V2 兼容:对老 action_id(如 view_business → 双查旧 view_business_v1)做 OR 合并;由 enable_v1_compat 构造参数控制。
    2. 动作语义别名ACTION_COMPATIBLE_ALIASES = {"new_dashboard": ["manage_dashboard", "manage_datasource"]},仅在 policy_query 层生效(is_allowed 直连平台走原动作)。
  • V3PermissionProvideriam_v3/provider.py):实现方言层 + filter_visible_resources(用 query_policy 拿 AST + DictEvaluator 本地求值,保留 {rt}.id IN/EQ 快速路径) + has_any_permission(AST 非空即真近似判定)+ plan_migration / apply_migration(委托 V3Migrator)。
  • V3Migratoriam_v3/migrator.py):把 SchemaRegistry 中的 action / resource_type / role 定义翻译成 v3 平台的 add-action / add-resource-type 等 API 调用;reconcile 已有资源避免重复 create。
  • MonitorV3Codecadapters/v3/codec.py):v3 场景下业务命名与方言命名基本一致(spacespaceview_businessview_business),少量特殊映射走 codec 兜底。

3.9 iam_v4 实现

设计意图:v4 平台是全新的 RBAC 模型,从 API 契约到资源模型都与 v3 不同。本次实现不复用任何 v3 代码,从零构造一个原生 HTTP 客户端 + provider + migrator + callback,通过 PermissionProvider 契约统一表现,让上层调用无感知。

关键类

  • V4Clientiam_v4/client.py):原生 requests-based HTTP 客户端;不依赖 iam SDK。
    • 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-Id HTTP 头。
    • 模块级 _CACHED_TOKEN 存 v4 auth token(供 callback 校验用)。
  • V4PermissionProvideriam_v4/provider.py):按 subject.tenant_id 从内部 dict[tenant → V4Client] 拿客户端;实现方言层 + filter_visible_resources(顶层资源反向列举走 get_authorized_resources,实例级走正向批量鉴权)。
  • V4Migratoriam_v4/migrator.py):与 V3Migrator 接口对齐(plan_migration / apply_migration 签名一致),只是底层调 v4 平台的 upsert/delete API。
  • v4 callbackiam_v4/callback/):
    • views.py:DRF View 接收 v4 平台的资源枚举 / 详情查询请求。
    • auth.py: IamCallbackAuthentication:用 v4 平台下发的 auth_token 校验(每小时刷新)。
  • MonitorV4Codecadapters/v4/codec.py):v4 命名与业务命名的差异映射。

3.10 adapters — v3/v4 共用适配层

设计意图CodecResourceResolver 是"业务侧"的,不该放到 iam_engine(框架应该 domain-neutral);也不该重复放到 iam_v3 / iam_v4(避免两地维护)。所以单独抽 bkmonitor/iam/adapters/ 目录,作为两个 provider 共用的业务适配层。

关键类

  • MonitorResourceResolveradapters/resolver.py):单一入口,按 resource.type 分派到 _resolve_space / _resolve_apm / _resolve_grafana / _resolve_rum;补 nameancestor_chain
  • MonitorV3Codec / MonitorV4Codec:v3/v4 各自的命名差异映射。

3.11 definitions — 业务侧元数据

设计意图:把"监控平台有哪些业务动作、资源类型、角色"作为单一定义源(single source of truth),schema 层从这里加载,framework 层从这里派生,v3 / v4 provider 迁移工具也是从这里读取生成注册包。

关键文件

新增一个动作 = 在 actions.py 加一个 ActionDef 成员;然后跑 iam_engine_makemigrations 生成迁移文件;再跑 iam_engine_migrate 应用到平台。全流程与 django-migrations 类似。


四、上层入口收口

本章解释"为什么业务代码在 v3 → v4 切换过程中不用改一行"。所有入口都经过一次统一收口到 IAMFramework,切换 provider 只需要动 IAM_FRAMEWORK.PROVIDERS 配置。

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) SDK 直连 + 内联 token 豁免 + skip_check check_iam_preflightself._fw.is_allowed(AuthRequest(...))
is_allowed_by_biz(bk_biz_id, action) 空间级判定快捷方法 内部改为 fw.is_allowed + 自动构造 space resource
batch_is_allowed(actions, resources) 每 (action, resource) 一次 SDK 调用 按资源类型分组,每 (action, 类型组) 一次 batch_by_resource 批量调用
filter_space_list_by_action(action) 遍历空间逐个 is_allowed fw.filter_visible_resources(subject, action_id, candidates)
grant_creator_action(resource_type, resource_id, ...) 创建者授权 v3/v4 各自实现
get_apply_url(action_ids, resources) 生成申请页 URL fw.get_apply_url(ApplyURLRequest(...))
get_iam_client(bk_tenant_id) 返回 SDK IAM 客户端 保留,返回 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) 两个纯函数,Permissiondrf.py 共用;语义与老版本逐行对齐(修复了老版本的生成器恒真 bug 和 ActionIdMap KeyError 风险)。

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_preflightfw.batch_by_resource(BatchByResourceRequest(...)) 按每个 action 拉一次批量鉴权,回填到每一项的 permission dict。
  • 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_permissioninstance_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.pyiam/resource.pyiam/action.py

设计意图:老版本外部有大量 from bkmonitor.iam import ResourceEnumfrom bkmonitor.iam.resource import Business, ApmApplication, ...from bkmonitor.iam.action import ActionEnum, ActionMeta 等 import。这些符号即使新框架完全不用,也必须保留为兼容包装,避免 CI 全站 ImportError。

保留清单

兼容符号 新版本实现
ResourceEnum.BUSINESS / APM_APPLICATION / GRAFANA_DASHBOARD / RUM_APPLICATION 各成员就是 Business / ApmApplication / ... 类本身
Business / ApmApplication / GrafanaDashboard / RumApplication 轻量包装类,元数据从 ResourceTypeDef 派生;create_instance(id) 返回 FwResource
ActionEnum.VIEW_BUSINESS / ... Actions.VIEW_BUSINESS 通过 property 兼容
ActionMeta 兼容类,.type / .related_resource_types 通过 property 提供
MINI_ACTION_IDS SaaS 空间小型化场景的 action id 集合,保留
get_action_by_id(id) 从 SchemaRegistry 查 ActionDef

已删除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_allowed
Permission.is_allowed_by_biz(bk_biz_id, action) 空间级判定快捷方法 内部改为 fw.is_allowed + 自动构造 space resource
Permission.batch_is_allowed(actions, resources) 批量鉴权 内部按资源类型分组批量调用 fw.batch_by_resource
Permission.filter_space_list_by_action(action) 空间列表过滤 内部改为 fw.filter_visible_resources
Permission.grant_creator_action(resource_type, resource_id, ...) 创建者授权 v3/v4 各自实现
Permission.get_apply_url(action_ids, resources) 生成申请页 URL 内部改为 fw.get_apply_url
Permission.get_iam_client(bk_tenant_id) 保留但受限 仅两处 v3 集成点使用;最好不新增调用方
Permission.list_actions() 未实现 NotImplementedError,端点被调用会 500,见 §8.2
bkmonitor.iam.drf.IAMPermission 等 5 个权限类 DRF 权限装饰器 签名保留,内部委托 _fw_check_any
insert_permission_field(...) / filter_data_by_permission(...) 响应注入 / 数据过滤 签名保留(含未使用的 instance_create_func / batch_create
ResourceEnum / Business / ActionEnum / ActionMeta / MINI_ACTION_IDS 老版本对外 import 兼容包装类保留
前端 check_allowed / check_allowed_by_action_ids / get_authority_detail / get_authority_apply_info 等 REST 端点 权限中心 UI 使用 内部实现改造,输出结构不变
admin.permission.query_user_permissions RPC 端点 operations 消费 内部走 fw.query_policies_by_actions + 权限树解析

5.2 数据兼容

数据面 处理
ApiAuthToken 表 沿用,未改结构;token 分享豁免逻辑保留在 check_iam_preflight
ExternalPermission 表 沿用,未改结构;外部用户 branch 的 authorizer 实例级约束在 has_permission 里用 ExternalPermission.resources 作候选还原(上轮已修复)
IAM 平台 v3 侧 system_id / action_id / resource_type_id 完全不变;support-files/iam/*.json 手写模型保留但不再作为初始化来源,未来靠 iam_engine_migrate
IAM 平台 v4 侧 首次接入,schema 由 V4Migrator 从 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 配置。

两种执行模式

模式 触发点 破坏性变更控制 老版本对应
manual 仅手动 iam_engine_migrate 命令 CLI --allow-destructive 参数(默认取自 MIGRATION.allow_destructive ✗ 无对应(老版本必然自动跑)
semi_auto post_migrate 信号,跟随 manage.py migrate 部署脚本触发 MIGRATION.allow_destructive 配置项 ✓ 与老版本 _migrate_iam 钩子对齐

破坏性开关矩阵

mode allow_destructive 结果
manual False(配置)+ 不加 CLI flag 手动跑 CLI,默认不 DELETE
manual 配置或 CLI flag 任一为 True 手动跑 CLI,允许 DELETE
semi_auto False manage.py migrate 触发,DELETE skip 告警
semi_auto True manage.py migrate 触发,允许 DELETE

5.4 部署 checklist

⚠️ 通用前置(无论手动 / 半自动都必须做)

部署前必须在开发环境跑一次 iam_engine_makemigrations,并把生成的迁移文件一并提交到代码仓库

  • 命令:python manage.py iam_engine_makemigrations --name <变更描述>(例如 --name add_incident_actions);可用 --dry-run 预览、--directory 覆盖默认目录。
  • 迁移是框架级、provider-neutral 的iam_engine_makemigrations 基于 definitions/ SSOT(actions.py / resource_types.py / roles.py)生成统一的 schema diff(Change 列表),v3 / v4 provider 在下发时各自翻译;不需要也不支持 --provider 参数。
  • 产物:bkmonitor/iam/iam_migrations/000X_<name>.py(单一平铺目录,由 IAM_FRAMEWORK.MIGRATION.directory 配置,默认即此路径;不按 provider 分子目录)。
  • 只要 definitions/actions.py / resource_types.py / roles.py 有变更(新增 / 修改 / 删除 action、resource、role 等),就必须重新生成并提交;否则线上 iam_engine_migrate 会因为 schema 与迁移历史不一致而漏迁移。
  • CI 建议加卡口:在 review 阶段执行 python manage.py iam_engine_makemigrations --name ci_check --dry-rungit diff --exit-code bkmonitor/iam/iam_migrations/,若发现有未提交的产物(说明有人改了 definitions 但没提交迁移文件),直接 fail。

方式 A:手动部署(MIGRATION.mode = "manual"

适用场景:私有化 / 生产环境,希望人工控制迁移时机与破坏性操作。

首次上线(保持 v3-only,与老版本平级)

  1. 部署前:确认代码里 bkmonitor/iam/iam_migrations/ 下的迁移文件已随本次改动一起提交(迁移文件是框架级共享的,v3 / v4 provider 下发时共用同一份)。
  2. 部署代码,IAM_FRAMEWORK.PROVIDERS 保持 v3-only(默认配置),MIGRATION.mode = "manual"
  3. 不会自动跑迁移;如需对齐 v3 平台元数据,SRE 手动执行:
    • python manage.py iam_engine_migrate --provider v3 --dry-run 查看计划(不加 --provider 则默认对配置中所有 provider 依次执行);
    • 确认无误后 python manage.py iam_engine_migrate --provider v3 真实执行;
    • 涉及 DELETE / 破坏性变更时,需要显式追加 --allow-destructive 才会真正下发(若已在 MIGRATION.allow_destructive = True 配置中开启,则默认允许,可省略 flag)。
  4. 上线后回归:Permission.is_allowed / DRF 权限类 / Grafana / kernel_api RPC / v3 反向回调 五条链路各手验一次。

切换到 v4

  1. 部署前:由于迁移文件是 provider-neutral 的,本次改造用于 v3 的迁移文件 v4 可直接复用;只有当 definitions/ 又有新增/修改时才需要重新 iam_engine_makemigrations --name <desc> 并提交。
  2. 部署代码,把 IAM_FRAMEWORK.PROVIDERS 替换为 v4 provider 配置块。
  3. SRE 手动执行 iam_engine_migrate --provider v4 --dry-run 查看变更计划。
  4. 确认无误后 iam_engine_migrate --provider v4(如需 DELETE 追加 --allow-destructive)。

方式 B:半自动部署(MIGRATION.mode = "semi_auto"

适用场景:CI / 容器化部署,希望 manage.py migrate 触发时顺带把 IAM 元数据也带上。

首次上线(保持 v3-only)

  1. 部署前:确认迁移文件已在代码仓库中提交(同方式 A 通用前置)。
  2. 部署配置:IAM_FRAMEWORK.PROVIDERS 保持 v3-only;MIGRATION.mode = "semi_auto"MIGRATION.allow_destructive 按环境策略配置(生产建议 False,测试环境可 True)。
  3. 部署过程中 python manage.py migrate 会通过 post_migrate 信号自动触发 iam_engine_migrate
    • allow_destructive=False:DELETE 操作只打告警日志、不执行(等价于 dry-run 掉 DELETE 部分,其余变更正常下发);
    • allow_destructive=True:DELETE 操作也会真实执行。
  4. 上线后同方式 A 做五条链路的回归。

切换到 v4

  1. 部署前:迁移文件是 provider-neutral 的,本次改造用于 v3 的迁移文件 v4 可直接复用;只有当 definitions/ 又有新增/修改时才需要重新 iam_engine_makemigrations --name <desc> 并提交。
  2. 部署配置更新:IAM_FRAMEWORK.PROVIDERS 加入 v4;MIGRATION.mode 仍保持 semi_auto
  3. 部署时 manage.py migrate 会自动对配置中所有 provider 依次执行迁移;DELETE 行为由 allow_destructive 控制。

六、配置示例与术语表

6.1 settings.IAM_FRAMEWORK 配置项

字段说明(对应 iam_engine/core/config.pyFrameworkConfig dataclass):

字段 类型 默认值 说明
ACTIONS dotted path 必填 业务动作定义模块(须导出 Actions 类,含所有 ActionDef 成员)
RESOURCE_TYPES dotted path 必填 业务资源类型定义模块
ROLES dotted path 可选 业务角色定义模块(v4 RBAC 用)
PROVIDERS list[dict] 必填 至少一个 provider 配置块(见 6.2)
COMPOSITION.policy single/any_of/all_of/primary single 多 provider 组合策略
COMPOSITION.options dict {} 策略参数(max_workers / strict_errors / fallback_on_error 等)
MIGRATION.mode manual / semi_auto manual 迁移触发模式
MIGRATION.directory str "" 迁移文件目录
MIGRATION.allow_destructive bool False 破坏性变更开关
BYPASS_RULES list[dotted path] [] 豁免规则类的 dotted path 列表

每个 provider 配置块结构:

字段 类型 说明
class dotted path provider 类(bkmonitor.iam.iam_v3.provider.V3PermissionProvider / bkmonitor.iam.iam_v4.provider.V4PermissionProvider
options dict 直接透传给 provider __init__ 的 kwargs;结构由 provider 自己决定(V3Options / V4Options

6.2 完整配置示例

v3-only(当前默认配置)

IAM_FRAMEWORK = {
    "ACTIONS": "bkmonitor.iam.definitions.actions.Actions",
    "RESOURCE_TYPES": "bkmonitor.iam.definitions.resource_types.ResourceTypes",
    "ROLES": "bkmonitor.iam.definitions.roles.Roles",
    "PROVIDERS": [
        {
            "class": "bkmonitor.iam.iam_v3.provider.V3PermissionProvider",
            "options": {
                "codec_class": "bkmonitor.iam.adapters.v3.codec.MonitorV3Codec",
                "resolver_class": "bkmonitor.iam.adapters.resolver.MonitorResourceResolver",
                "base_url": BK_IAM_V3_API_BASE_URL,
                "bk_tenant_id": "system",
                "provider_config_path": BKAPP_IAM_RESOURCE_PATH,
                "credentials": {
                    "app_code": BK_IAM_APP_CODE,
                    "app_secret": BK_IAM_APP_SECRET,
                },
                "system": {
                    "id": BK_IAM_V3_SYSTEM_ID,
                    "name": "监控平台",
                    "description": "...",
                    "clients": BK_IAM_V3_SYSTEM_CLIENTS_LIST,
                },
            },
        },
    ],
    "COMPOSITION": {"policy": "single"},
    "MIGRATION": {
        "mode": "manual",
        "directory": "bkmonitor/iam/iam_migrations",
        "allow_destructive": False,
    },
}

v4-only(切换到 v4 后的配置)

IAM_FRAMEWORK = {
    "ACTIONS": "bkmonitor.iam.definitions.actions.Actions",
    "RESOURCE_TYPES": "bkmonitor.iam.definitions.resource_types.ResourceTypes",
    "ROLES": "bkmonitor.iam.definitions.roles.Roles",
    "PROVIDERS": [
        {
            "class": "bkmonitor.iam.iam_v4.provider.V4PermissionProvider",
            "options": {
                "codec_class": "bkmonitor.iam.adapters.v4.codec.MonitorV4Codec",
                "callback_module": "bkmonitor.iam.adapters.v4.callbacks",
                "resolver_class": "bkmonitor.iam.adapters.resolver.MonitorResourceResolver",
                "base_url": BK_IAM_V4_API_BASE_URL,
                "bk_tenant_id": "system",
                "credentials": {
                    "app_code": BK_IAM_APP_CODE,
                    "app_secret": BK_IAM_APP_SECRET,
                },
                "system": {
                    "id": BK_IAM_V4_SYSTEM_ID,
                    "name": "蓝鲸监控平台V4",
                    "description": "...",
                    "callback_url": BK_IAM_V4_CALLBACK_URL,
                    "managers": ["admin"],
                    "clients": [BK_IAM_APP_CODE],
                },
            },
        },
    ],
    "COMPOSITION": {"policy": "single"},
    "MIGRATION": {
        "mode": "manual",
        "directory": "bkmonitor/iam/iam_migrations",
        "allow_destructive": False,
    },
}

组合模式(v3 → v4 灰度期)

IAM_FRAMEWORK = {
    # 元数据段同上,省略
    ...
    "PROVIDERS": [
        # 主 provider 放前面
        {"class": "bkmonitor.iam.iam_v4.provider.V4PermissionProvider", "options": {...}},
        {"class": "bkmonitor.iam.iam_v3.provider.V3PermissionProvider", "options": {...}},
    ],
    # any_of:任一 provider 允许即允许;灰度期"双写"场景
    "COMPOSITION": {
        "policy": "any_of",
        "options": {
            "max_workers": 2,               # 两个 provider 并发调用
            "fallback_on_error": True,      # 单个 provider 异常时降级到另一个
        },
    },
    # 或者用 primary:primary 为主 fallback 为辅
    # "COMPOSITION": {"policy": "primary", "options": {"primary_name": "v4"}},
}

6.3 术语表

术语 含义
Provider 权限平台接入实现,一个 provider = 对接一个 IAM 后端(v3 / v4 / 未来的其他 IAM 系统)。
Codec 编解码器:业务命名 ↔ 平台方言。同一个业务 action view_business 在 v3 是 view_business,v4 可能是 view_business_v4;codec 负责在鉴权调用出/入站时双向对齐。
Resolver 资源实例补全器:业务只传 type + id,resolver 查数据库补 name / ancestor_chain
Composition 多 provider 组合策略:single / any_of / all_of / primary
Dialect 平台方言:某个 IAM 平台特有的 ID 命名 / 拼接格式(如 v3 的 _bk_iam_path_)。
Bypass 豁免:跳过鉴权,直接放行;由 BypassRule 链表达(框架层)+ check_iam_preflight 表达(业务层)。
Schema 元数据:ActionDef / ResourceTypeDef / RoleDef 的集合,跨 provider 共享。
PolicyExpression provider-neutral 的策略 AST;用于 query_policy 结果传递、filter_visible_resources 的本地求值。
Migration 把本地 definitions 同步到 IAM 平台的过程;由 plan_migration 生成 Change 列表,apply_migration 查远端 + reconcile + 执行。
destructive 破坏性变更:DELETE / 方言 id 变更重建;由 --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):

测试文件 测试的功能 用例数
tests/iam/test_callback.py v4 平台回调的注册中心与 CallbackService 分发逻辑 17
tests/iam/test_codec.py v4 codec 业务命名与平台方言的编解码往返 22
tests/iam/test_config.py FrameworkConfig / V4Options 配置解析与默认值 22
tests/iam/test_schema.py SchemaRegistry 元数据注册表、快照与批量鉴权分片 23
tests/iam/test_v3_codec.py v3 codec 编解码往返 21
tests/iam/refactor/test_action_interface.py bkmonitor.iam.action 对外接口兼容性(ActionEnum / ActionMeta)与新旧 codec 对照 20
kernel_api/rpc/tests/test_admin_permission.py 用户权限树解析:iam_path / 策略表达式 / display_name / 批量查询 55
tests/iam/test_migration.py 迁移体系:快照 diff、迁移文件加载、planner 排序、recorder 28
tests/iam/test_v3_config.py V3Options / V3SystemInfo 配置解析 19
tests/iam/test_v3_provider.py v3 provider 方言层鉴权(读/写缓存策略、批量、apply_url)与 plan/apply 迁移 19
tests/iam/refactor/test_drf_parity.py DRF 权限类、insert_permission_field / filter_data_by_permission 的新旧行为对照 42
tests/iam/refactor/test_resource_interface.py ResourceEnum / create_* 资源构造、MonitorResourceResolver 补全行为、SDK 载荷一致性 20
packages/monitor_web/tests/grafana/test_folder_permission.py Grafana 文件夹展开、dashboard 资源解析、list_instance(本地 DB) 17

涉及真实 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.pyGetAuthorityMetaResource(路由 GET rest/v2/iam/get_authority_meta/)仍在调用,一旦被请求即刻 500;对应回归点在 tests/iam/refactor/test_permission_interface.pyTestKnownRegressions.test_list_actions_returns_action_list(当前 xfail(strict=True))。

前端使用情况(已审计,结论:暂无任何实际消费方)

新旧契约对比

  • 旧版(v3 SDK 耦合):list_actions() 透传 iam_client._client.query(BK_IAM_SYSTEM_ID)data["actions"],即 v3 平台 action model 数组,idv3 平台 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 而拒绝;需在 Permission facade 边界做 v3 别名 → 业务 ID 归一化,作为 v4 切换的兼容层(另行跟踪)。

8.3 僵尸代码残留

本次改造把老 IAM 相关的"数据/元数据搬运"链路整体切到了 iam_engineiam_engine_migrate 命令 + bkmonitor/iam/iam_migrations/ 目录下,但为了不影响回滚,老的搬运链路(bkmonitor/iam/migrate.py + bkmonitor/iam/migrations/ + support-files/iam/*.json + 一个 legacy management command)代码物理上仍然保留、只是不再被任何入口驱动。

遗物清单(本次改造已确认全部处于孤儿状态、无 live driver):

文件 / 目录 大小 状态 说明
bkmonitor/iam/migrate.pyPolicyMigrator ~8 KB 纯僵尸 只被 migrations/0001_initial.py / 0002_single_dashboard.py import;这两个脚本自己也已经无 driver。
bkmonitor/iam/migrations/000{1..14}_*.py 14 个脚本 + __init__.py ~15 KB 孤儿 通过 bkmonitor.migrate.BaseMigration 基类注册,全局 grep 未发现任何 Migrator("iam", "bkmonitor.iam.migrations") 或等价驱动代码。0001/0002 用 PolicyMigrator,0003~0014 用 IAMMigrator("xxx.json")
bkmonitor/support-files/iam/*.json 17 个 JSON ~285 KB 传递僵尸 只被上一行 14 个脚本 IAMMigrator("XXXX.json") 引用;上层链路已死,这里也随之失效。
bkmonitor/bkmonitor/management/commands/iam_upgrade_action_v2.py 16 KB 不可执行的历史命令 V1→V2 政策数据一次性搬运脚本,业务上早已完成过;代码里还引用 IAMMigrator("legacy.json") / IAMMigrator("initial.json"),但这两个 JSON 已不在 support-files/iam/ 目录中,即便手动跑也会直接报错。
bkmonitor/bkmonitor/migrate.pyMigrator 5.6 KB(整个模块) 半僵尸 BaseMigration 基类还在给上面这些老 migration 脚本当父类;但 Migrator 调度类没有任何调用方。若清理上面 14 个脚本,Migrator 也应一并删除;BaseMigration 类是否保留取决于其他 app 是否复用(需先做全局审计)。

@github-actions

Copy link
Copy Markdown

请在 PR 中添加项目标签,例如:project/monitorproject/apmproject/logproject/publicproject/aiops

@github-actions

Copy link
Copy Markdown

请在 PR 中添加类型标签,例如:fixfeatdocsstylerefactortestchoremerge, perf

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant