TT Lab
开始
学习 学习路径 课程

CBA — Backstage 认证助理

新加的后端插件只返回 401

在 TT Lab 中继续学习

目标

在 VM 中启动一个真实的 Backstage 后端进程,并亲手编写、接入一个后端插件。通过 HTTP 响应和日志,确认新路径为什么会返回 401、公开路径该如何开放,以及插件代码如何使用配置、其他插件和扩展点。

为什么重要

修改 Backstage 大多是添加插件,或者扩展现有插件。接入新的后端插件后,如果所有请求都返回 401,很容易把时间花在寻找代码缺陷上。实际上是默认认证策略在路由器之前就把请求拦住了。同样的道理,插件调用目录时也需要令牌;而要改变目录的行为,不是去修改它的软件包,而是添加模块。

本实验只涉及后端。前端插件(React、Material UI)需要构建应用包,而这台 VM 上不做这种构建,所以不直接确认。两者的区别会在报告中整理。

VM 首次启动大约需要 4 分钟,第 1 步的安装需要从互联网下载,约 30 秒。代码不用 TypeScript,而是写在一个 CommonJS JavaScript 文件(index.js)中。

步骤

  1. 在 /usr/local/cba-plugin 中以精确版本安装六个后端软件包。
  2. 在 7007 端口启动只包含 catalog 插件的后端。
  3. 接入 oncall 插件,并记录不带认证调用的结果(401)。
  4. 用 addAuthPolicy 只开放 /ping 这一条路径。
  5. 用 rootConfig 在每次请求时读取配置值,在不重启的情况下修改配置,并记录响应的变化。
  6. 通过 auth、discovery 服务以服务间调用的方式访问目录 API。
  7. 通过 catalog 模块(createBackendModule)添加实体 provider。
  8. 报告 401 和 404 分别由哪一层返回,以及后端插件与前端插件的区别。

参考

以精确版本下载用于挂载插件的后端材料

在 /usr/local/cba-plugin 中创建 npm 项目,并以不带范围符号的精确版本安装下面六个软件包。package.json 的 dependencies 中也必须写着不带 ^ 的该版本。根磁盘很小,所以 npm 缓存通过 npm_config_cache=/usr/local/cba-plugin/.npmcache 放在 scratch 磁盘上。六个软件包是 @backstage/backend-defaults@0.17.8、@backstage/backend-plugin-api@1.10.0、@backstage/plugin-catalog-backend@3.9.1、@backstage/plugin-catalog-node@2.2.4、better-sqlite3@12.4.1 和 express@4.22.3。

npm install --save-exact 패키지@버전 ...(占位符依次为软件包名和版本)会在 package.json 中不带范围地写入版本。better-sqlite3 是原生模块,最新的 13.x 没有适用于这个 Node 20 的预编译二进制文件,会尝试编译并失败——这就是要从 backend-defaults 所要求的 12.x 中选定固定版本的原因。安装完成后,用 npm ls --depth=0 确认版本。

启动只包含 catalog 的后端

创建 /usr/local/cba-plugin/app-config.yaml、/usr/local/cba-plugin/catalog/org.yaml 和 /usr/local/cba-plugin/index.js,在 7007 端口启动只包含 catalog 插件的后端。配置:backend.baseUrl: http://localhost:7007、backend.listen.port: 7007,backend.database 为 client: better-sqlite3、connection: ':memory:',catalog.rules 为 [{allow: [Component, Group, Location]}],catalog.locations 中写入 type: file、target: ./catalog/org.yaml。org.yaml 中放入 Group team-payments,以及该团队拥有的 Component payments-api 和 refund-worker。index.js 对 createBackend() 执行 add @backstage/plugin-catalog-backend 后 start。进程在 /usr/local/cba-plugin 中用 node index.js 启动,并把输出写入 /usr/local/cba-plugin/backend.log。/.backstage/health/v1/readiness 返回 200 就说明已就绪。

进程必须在关闭 shell 后仍然存活,因此要像 setsid nohup node index.js > backend.log 2>&1 < /dev/null & 这样把标准输入输出全部断开(不断开的话,命令不会结束而是被挂住)。请在启动日志中找到 Plugin initialization complete 这一行,并留意不带认证调用 /api/catalog/entities 时返回什么。

新插件的所有路径都是 401

在 index.js 中用 createBackendPlugin 创建 pluginId 为 oncall 的插件并 add。通过 coreServices.httpRouter 注册 express 路由器,并设置 GET /ping(→ {"ok":true})和 GET /roster(值班人员 JSON)两条路径。暂时不要添加认证策略。 重启后,把不带认证调用 http://127.0.0.1:7007/api/oncall/ping 的结果,按 curl -s -i 的输出原样保存到 /root/cba-plugin/before-policy.txt。

插件路由器挂在 /api/<pluginId> 之下。代码没有缺陷却返回 401,原因请到文档的默认认证策略中去找。为了对比,再调用 /api/oncall/없는경로(韩文,意为“不存在的路径”)和 /api/없는플러그인/x(韩文,意为“不存在的插件”),就能看出 401 是由哪一层返回的。

只开放一条健康检查路径,无需认证

在 oncall 插件的 init 中,用 http.addAuthPolicy 以 allow: 'unauthenticated' 只开放 /ping 这一条路径。重启后,不带认证时 /api/oncall/ping 应返回 200 {"ok":true},而 /api/oncall/roster 仍应返回 401。

策略的 path 与写在路由器中的路径形式相同(不带插件前缀)。开放整个插件的设置(backend.auth.dangerouslyDisableDefaultAuthPolicy)会让所有插件都无需认证,请不要使用。

从配置而不是代码中读取值班频道

在 app-config.yaml 中加入 oncall.channel: '#payments-oncall',给 oncall 插件注入 coreServices.rootConfig,添加一个在每次收到请求时返回 config.getString('oncall.channel') 的 GET /channel(→ {"channel":"..."},允许无需认证访问),然后重启。确认响应后,不要重启,把配置文件中的值改为 '#platform-oncall',并确认响应随之变化。在 /root/cba-plugin/channel.txt 中留下 before=<처음 응답의 channel> 和 after=<바뀐 응답의 channel> 两行(占位符依次为第一次响应和变化后响应中的 channel)。

后端会监视配置文件,变化后会重新读取(日志中会再次打印 Found 0 new secrets in config)。如果值仍然没变,请检查是否在 init 中只读取一次并存进了变量。请轮询几秒,直到响应发生变化。

插件调用目录时也需要令牌

在 oncall 插件中添加 GET /services(允许无需认证访问)。这个处理函数通过 coreServices.auth 的 getPluginRequestToken(onBehalfOf 为 auth.getOwnServiceCredentials(),targetPluginId 为 catalog)获取令牌,通过 coreServices.discovery 的 getBaseUrl('catalog') 获得地址,用 Authorization: Bearer 请求头调用 /entities?filter=kind=component,然后返回 {"catalogStatus": <카탈로그 응답 코드>, "names": [Component 이름 정렬]}(占位符依次为 catalog 的响应码和排序后的 Component 名称)。重启后,响应的 names 中应包含 payments-api 和 refund-worker。第 5 步中修改的频道配置保持不变。

插件之间不能通过代码相互调用,只能通过 HTTP 通信。因此即使在同一个进程内,也需要能通过目录默认认证策略的令牌。刚启动时,目录可能还没处理完文件,列表可能为空,请过几秒再次调用。还请在后端日志中查看 /api/catalog/entities 请求的 User-Agent 被记录成了什么。

不修改目录,而是通过模块注入实体

用 createBackendModule 创建 pluginId 为 catalog、moduleId 为 pager-provider 的模块并 add。注入 @backstage/plugin-catalog-node 的 catalogProcessingExtensionPoint,用 addEntityProvider 注册名为 pager-provider 的 provider,并在 connect 中通过 applyMutation({type: 'full', ...}) 放入 Component pager-bridge(owner 为 team-payments,包含 backstage.io/managed-by-location 和 backstage.io/managed-by-origin-location 注解)。不要把它写进 org.yaml。 重启后,/api/oncall/services 的 names 中应同时出现 pager-bridge 以及 payments-api 和 refund-worker。

模块只能通过目标插件公开的扩展点来扩展该插件。请在模块文档中确认,为什么要从 -node 库软件包而不是 catalog 软件包本身获取扩展点。即使是 provider 放入的实体,缺少 location 注解也会在处理阶段被过滤掉。

报告 401 和 404 分别由哪一层返回

在 /root/cba-plugin/report.md 的前五行,把在当前运行的后端上亲自确认的值按 키=값(占位符为键和值)的格式写出——mount_path=(oncall 插件路由器挂载的路径)、protected_route_status=(不带认证访问 /roster)、unknown_route_status=(不带认证访问 oncall 插件下不存在的路径)、unknown_plugin_status=(未注册的 pluginId 下的路径)、catalog_direct_status=(不带认证访问 /api/catalog/entities)。在其下说明后端插件与前端插件各自在哪里运行、如何连接。

数字请用 curl 现场测得的值,不要凭记忆填写。不存在的路径返回的不是 404,是因为认证检查在路由器之前。说明中请写出:前端插件在浏览器中运行,并调用 backend.baseUrl 的 /api/<pluginId>。