Backstage Auditor 核心服务解析:@backstage/backend-defaults 的审计 API 与实现机制
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
report-auditor.api.md是 Backstage 仓库中@backstage/backend-defaults包 auditor 入口的 API Extractor 报告文件,它冻结并声明了 Auditor 核心服务的全部公开 API:事件类型、默认实现DefaultAuditorService、服务工厂auditorServiceFactory以及基于独立 Winston 日志器的WinstonRootAuditorService。本篇以该 API 报告为主体骨架,结合 auditor 入口源码、单元测试与 核心服务文档 中的配置规范,讲解每个导出成员的语义、底层实现、backend.auditor配置项的解析逻辑,以及插件侧使用 AuditorService 的完整姿势,帮助你在自研后端插件中落地可检索、可分级、可合规的安全审计日志。
API 报告中的公开导出总览
API 报告 由 API Extractor 自动生成,标注"Do not edit",其作用是在接口发生不兼容变更时提供可 diff 的契约基线。报告中声明的@public导出可归为四类:
| 类别 | 导出成员 | 作用 |
|---|---|---|
| 事件类型 | AuditorEvent、AuditorEventOptions<TMeta>、AuditorEventStatus、AuditorEventActorDetails、AuditorEventRequest | 描述一条审计事件的结构、创建入参与状态机 |
| 日志函数 | AuditorLogFunction | 审计事件落地到日志系统的回调签名 |
| 默认实现 | DefaultAuditorService | 实现AuditorService接口的核心类 |
| 服务装配 | auditorServiceFactory、WinstonRootAuditorService、WinstonRootAuditorServiceOptions | 分别对应"复用现有 logger"与"独立 Winston logger"两种接入方式 |
类型导入关系值得注意:AuditorService、AuditorServiceEvent、AuditorServiceEventSeverityLevel、AuthService、HttpAuthService、PluginMetadataService均定义在@backstage/backend-plugin-api中,@backstage/backend-defaults只负责提供实现。也就是说,插件依赖注入面向的是接口,实现细节由 backend-defaults 提供。
事件类型系统:AuditorEvent 与其子类型
报告中最核心的类型是事件本体与它的状态联合:
export type AuditorEvent = { plugin: string; eventId: string; severityLevel: AuditorServiceEventSeverityLevel; actor: AuditorEventActorDetails; meta?: JsonObject; request?: AuditorEventRequest; } & AuditorEventStatus; export type AuditorEventStatus = | { status: 'initiated' } | { status: 'succeeded' } | { status: 'failed'; error: Error };AuditorEventStatus是一个可辨识联合(discriminated union):事件生命周期只有三种状态——initiated(操作开始)、succeeded(成功完成)、failed(失败并强制携带error: Error)。这种结构保证了失败日志一定包含错误对象,配合 winston 的errors({ stack: true })格式器可直接输出堆栈。AuditorEventActorDetails记录"谁在做":actorId(用户 entityRef 或服务主体)、ip、hostname、userAgent,全部为可选字段。AuditorEventRequest只保留url与method两个字段——即原始 Express 请求不会整体入日志,只留痕定位信息。AuditorEventOptions<TMeta extends JsonObject>是插件侧createEvent的入参形态:必填eventId,可选severityLevel、request、meta,同样与状态联合交叉。
在 DefaultAuditorService 的log方法 中可以看到这些字段如何被组装成最终的AuditorEvent:plugin取自PluginMetadataService.getId();severityLevel缺省为'low';actor从请求与凭据中解析;meta为空对象时会被规范化为undefined,避免在日志里留下无意义的空对象。
DefaultAuditorService:三段式审计事件与执行者解析
DefaultAuditorService实现了AuditorService接口,构造方式为静态工厂:
static create( logFn: AuditorLogFunction, deps: { auth: AuthService; httpAuth: HttpAuthService; plugin: PluginMetadataService; }, ): DefaultAuditorService;它的唯一依赖是一组AuditorLogFunction(事件落日志的回调)和三个核心服务。核心入口是createEvent:
async createEvent(options) { await this.log({ ...options, status: 'initiated' }); return { success: async params => { /* 合并 meta 后记录 status: 'succeeded' */ }, fail: async params => { /* 记录 error 与 status: 'failed' */ }, }; }见 DefaultAuditorService.ts 的 createEvent 实现。其行为有三点关键细节:
- 调用即记录:
createEvent被调用时立即输出第一条initiated日志,随后通过返回的success/fail闭包补记终态。一次完整的操作最多产生两条审计日志。 - meta 渐进合并:
success/fail的入参可以携带各自的meta,会与createEvent时的meta浅合并(后者覆盖前者)。单元测试 用 "use root meta" 用例验证了这一点:initiated 阶段记录{ initiated: 'test' },succeeded 阶段记录{ initiated, succeeded },failed 阶段记录{ initiated, failed }。 - 失败必含 Error:
fail({ error })会把error提升到事件顶层字段,与AuditorEventStatus中failed分支的必填约束一致。
执行者(actor)解析逻辑在私有方法getActorId中(源码):
- 无 HTTP 请求的后台任务:使用
auth.getOwnServiceCredentials(),执行者就是当前服务本身; - 有请求时:通过
httpAuth.credentials(request)从请求中解析凭据,解析失败会抛出ForwardedError('Could not resolve credentials')——即审计不降级,身份不明直接让操作失败; - 凭据是用户主体时取
principal.userEntityRef,是服务主体时取principal.subject,否则返回undefined。
DefaultAuditorService 测试 用mockServices验证了三种状态落日志的完整字段:{ eventId, status, plugin, severityLevel: 'low', actor: {} },其中actor为空对象正是因为 mock 凭据既非用户也非服务主体时各字段均为undefined。
auditorServiceFactory:复用现有 logger 的默认装配方式
auditorServiceFactory 是 backend-defaults 提供给插件体系的标准服务工厂,注册在coreServices.auditor上,依赖五个核心服务:
export const auditorServiceFactory = createServiceFactory({ service: coreServices.auditor, deps: { config: coreServices.rootConfig, logger: coreServices.logger, auth: coreServices.auth, httpAuth: coreServices.httpAuth, plugin: coreServices.pluginMetadata, }, factory({ config, logger, plugin, auth, httpAuth }) { const auditLogger = logger.child({ isAuditEvent: true }); const severityLogLevelMappings = getSeverityLogLevelMappings(config); return DefaultAuditorService.create(event => { /* ... */ }, { plugin, auth, httpAuth }); }, });工厂内部有两处设计要点:
- 审计日志与业务日志隔离标记:通过
logger.child({ isAuditEvent: true })派生子日志器,所有审计行都会带上isAuditEvent: true字段,便于在日志管道中精确过滤审计数据流; - severity → log level 动态路由:根据配置解析出的
severityLogLevelMappings,用auditLogger[mappings[event.severityLevel]](...)动态调用对应的 winston 级别方法(debug/info/warn/error)。当事件包含error时(即 failed 状态),会用除 error 外的其余事件字段创建一个 child logger 再记录,避免 Error 对象被当作普通 meta 序列化。日志的 message 固定为`${event.plugin}.${event.eventId}`,形成插件.事件的点号命名空间。
WinstonRootAuditorService:为审计建立独立日志通道
WinstonRootAuditorService提供另一种接入路径:不依赖插件体系已有的 logger,而是自建一个独立的 winston 日志器,适合需要将审计日志写入独立文件、独立传输通道(如专用 SIEM 通道)的场景。
报告与 WinstonRootAuditorService.ts 中定义的选项类型:
export type WinstonRootAuditorServiceOptions = { meta?: JsonObject; format?: Format; transports?: winston.transport[]; };static create(options?)内部通过WinstonLogger.create构建日志器,固定注入meta: { service: 'backstage' }与level: 'info',格式器为:
export const defaultFormatter = winston.format.combine( winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json(), );即输出带时间戳、展开错误堆栈的 JSON 行。另有一个公开的auditorFieldFormat,作用是在每条日志上追加isAuditEvent: true标记,并在create时无条件合入最终 format 链(winston.format.combine(auditorFieldFormat, options?.format ?? defaultFormatter))。若传入options.meta,会再派生一层 child logger 注入。
forPlugin(deps)方法与工厂方式不同,其 deps 中多一个config: Config(而非logger):
forPlugin(deps: { auth: AuthService; config: Config; httpAuth: HttpAuthService; plugin: PluginMetadataService; }): AuditorService { const severityLogLevelMappings = getSeverityLogLevelMappings(deps.config); return DefaultAuditorService.create(event => { /* 同工厂:按 severity 路由级别 */ }, deps); }从源码结构看,它本质上是一个"根上下文":文档示例 展示的标准用法是通过createRootContext()创建一次,然后在每个插件的factory里调root.forPlugin(...)得到该插件专属的AuditorService实例,既共享底层传输通道,又保持插件维度隔离。WinstonRootAuditorService 测试 验证了forPlugin返回的确实是DefaultAuditorService实例,且 initiated/succeeded 事件按预期流经内部log方法。
backend.auditor.severityLogLevelMappings 配置解析
两个实现共用 utils.ts 中的getSeverityLogLevelMappings(config)来解析配置:
- 配置根键为
backend.auditor,读取severityLogLevelMappings下的low/medium/high/critical四个键; - 使用 zod 枚举
['debug', 'info', 'warn', 'error']校验每个值,缺省默认映射为low: 'debug',medium/high/critical均为'info'; - 任一值非法时抛出
InputError,错误信息会明确指出具体键名、收到的非法值和全部合法取值。
对应的app-config.yaml写法(可只覆盖单个级别):
backend: auditor: severityLogLevelMappings: low: debug medium: info high: warn critical: error默认映射的含义是:low事件默认落在 debug 级别(生产环境通常被日志级别过滤掉),而 medium/high/critical 默认都记为 info——如需让高危事件更醒目,可将high/critical调至warn/error,这正是 核心服务文档 中"Severity Log Level Mappings"章节给出的用法。
插件中使用 AuditorService:接口、命名规范与 meta 约定
插件侧只依赖@backstage/backend-plugin-api的AuditorService接口,通过路由选项注入。官方文档 给出的 Express 路由集成示例:
export async function createRouter(options: RouterOptions): Promise<express.Router> { const { auditor } = options; const router = Router(); router.use(express.json()); router.post('/my-endpoint', async (req, res) => { const auditorEvent = await auditor.createEvent({ eventId: 'my-endpoint-call', request: req, meta: { // ... metadata about the request }, }); try { // ... process the request await auditorEvent.success(); res.status(200).json({ message: 'Succeeded!' }); } catch (error) { await auditorEvent.fail({ error }); res.status(500).json({ message: 'Failed!' }); throw error; } }); return router; }传入request: req后,实现层会自动提取ip、hostname、user-agent进入actor,originalUrl与method进入request字段,并从请求凭据解析actorId。
命名规范(源自核心服务文档,并在AuditorEventOptions的eventId文档注释中重申):
- kebab-case:
eventId使用如user-login、file-download的形式; eventId表逻辑分组:如entity-fetch归组所有实体读取操作,location-mutate归组所有 location 变更;- 具体动作放
meta:用meta.queryType、meta.actionType等字段表达组内细分动作,例如eventId: 'entity-fetch'+meta: { queryType: 'by-id' }; - 避免与 pluginId 冗余的前缀:插件上下文已经由
plugin字段单独承载。
常用 meta 键约定:
| Key | 说明 | 格式 | 示例 |
|---|---|---|---|
queryType | 查询类型 | kebab-case 字符串 | all、by-id、by-name、by-query、by-refs、ancestry、by-entity |
actionType | 变更动作类型 | kebab-case 字符串 | create、delete、refresh |
entityRef | 实体全引用 | [kind]:[namespace]/[name] | component:default/my-component |
locationRef | 被操作的 location | 任意位置引用字符串 | url:https://example.com/catalog-info.yaml |
uid | 对象唯一标识 | 任意有效唯一 ID 字符串 | 9a4e740b-e557-427f-b9f2-0d4f092b1c1e |
典型的写操作审计事件形如:{ "eventId": "entity-mutate", "meta": { "actionType": "delete", "uid": "some-entity-uid", "entityRef": "component:default/petstore" }, "severityLevel": "medium" }。
按 核心服务文档 的指引,plugins/catalog-backend的src/service/createRouter.ts是该服务在真实插件中的完整参考实现,Catalog 的 Audit Events 文档则示范了如何为一个插件的eventId清单撰写配套文档。选型上应记住:审计服务面向安全相关与合规类事件(会话管理、数据访问变更、配置变更等),一般业务日志仍应走标准LoggerService。
小结:从 API 契约到可落地的审计能力
report-auditor.api.md声明的这组导出,构成了 Backstage 后端审计能力的完整闭环:AuditorEvent*系列类型约束事件形状与三态状态机;DefaultAuditorService负责执行者解析、meta 合并与日志回调;auditorServiceFactory与WinstonRootAuditorService分别给出"复用现有 logger 并打标isAuditEvent"和"独立 Winston 通道(默认 JSON 格式 + 时间戳 + 堆栈展开)"两种装配路径;backend.auditor.severityLogLevelMappings配置则以 zod 校验支撑 severity 到日志级别的灵活路由。理解这层 API 与实现后,你可以在任何 Backstage 后端插件中以统一、可过滤、带执行者上下文的格式输出审计事件。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考