Skip to content

Registry 副本服务(registry-server)

apps/registry-server 是 DN42 registry 数据在机群里的唯一同步与落库点。此前 Python 控制面(榜单名称富化 / 管理 UI)与 auth-server(ASN 归属验证物料 / OAuth claims 富化)各自维护一份近乎逐行重复的副本实现(同步循环、副本表、查询组装),任何口径改动都要双写;拆出独立服务后消费方降级为 HTTP 客户端,重复维护消失。

边界与依赖

  • 独立 database(compose 内置 PG 的第三个 database dn42_registry):六张表 registry_asn / registry_mntner / registry_person / registry_key_cert / registry_route / registry_meta,整表随每次成功同步全量替换(registry 是外部权威,本地不做增量合并)。schema 用 create_all(整库都是可随时全量重建的只读镜像,不引迁移链)。
  • 同步链路registryserver/services/store.py,原样收编自两个消费面的实现):sha 变更门控 → tarball 拉取(须 git.dn42.dev 只读 token,匿名被拒;content-type 校验防把登录页 HTML 当 tar 解)→ 解析落库。空结果守卫:aut-num / mntner / person 任一为零按异常轮拒绝,绝不用可疑 tarball 清表。失败保留旧数据、只 log——消费方对 registry 数据的态度是「宁旧勿空」。
  • 解析口径仍在共享包 packages/dn42_registry(rpsl.py 纯函数 + forgejo.py 拉取);本服务是它的唯一落库消费方。
  • API 全量 Bearer 鉴权service_token,消费方共用一枚;未配置 = 全 API fail-closed 403,/healthz 除外)。不对宿主发布端口、无公网域名——只被容器网内的姊妹服务调用,运维操作(手动 sync / status)走两个消费方的既有代理端点。

API 面(/registry/v1,均为 service token 鉴权)

方法路径说明
GET/status副本状态(enabled / commit / 各对象计数 / synced_at)
POST/sync?force=手动同步(与周期循环经锁互斥)。未配上游 token 409、上游失败 502(旧数据保留)、sha 未变 synced=false
POST/names批量 ASN → as-name(body {asns:[…]},上限 1000 超限 422;未收录省略)
GET/asns/{asn}aut-num 摘要;未收录 404
GET/asns/{asn}/mntnersASN → 维护者联查(悬空引用跳过);未收录 404
GET/asns/{asn}/verification-options验证方式枚举(email/pgp/ssh,确定性选项 id,含旧式 PGPKEY-xxx → key-cert 指纹解析);未收录 404
GET/asns/{asn}/routes该 ASN 起源前缀(v4/v6 分组排序;未收录 = 空列表,不 404)
GET/asns/{asn}/identity?mntner=OAuth claims 富化物料(数据不全逐字段回落 None,不 404)
GET/mntners/{name}mntner 摘要;未收录 404
GET/key-certs/{fingerprint}按指纹回查 PGP 公钥 armor(指纹归一);未收录 404

auth: 方法的收录口径

MNTNER-SCHEMA 对 auth 只声明 > [method] [value]...,不枚举方法名,所以规范的事实源是 registry 仓库根的签名工具 sign-my-commitguess_mntner_method / filter_ssh_auths)。验证方式枚举与之逐档对齐:

auth: 方法归档说明
pgp-fingerprint <指纹>pgp指纹去空格大写归一
PGPKEY-<8 位>pgp按名字引用 key-cert 对象,回查 fingerpr: 得指纹后并入同一档;对象悬空则不出选项
ssh-* / ecdsa-*ssh公钥体原样保留(base64 大小写敏感)
sk-ssh-* / sk-ecdsa-*sshFIDO 硬件钥,核验见 auth-server

规范外的写法不出选项——端出一个必然验不过的选项对用户是负价值。全库唯一的例外是 ed25519-pw(1 条,对象自带「Irregular or legacy auth method」备注,registry 自己的签名工具也按未知方法拒绝)。

同一份凭据经多条 auth: 行重复登记(如既写 pgp-fingerprint 又写指向同一把钥的 PGPKEY-xxx)时,按 (kind, value) 去重后只剩一条,标注首个来源 mntner。

消费方接入与失败语义

客户端是共享包的 dn42_registry.RegistryClient(方法名与旧本地 store 逐一对齐,消费方以鸭子类型近零改动切换)。错误语义统一:未收录 → None(404 映射),网络失败 / 未配置 → RegistryClientError(status_code=503),其余非 2xx 透传 4xx、归一 502。读路径超时 5s(names_for 在榜单热路径上),sync 单独 300s(下载 ~15MB + 整表重写)。客户端不吞错,容错由消费方按各自哲学处理:

消费方路径registry-server 不可达时
control-server 榜单名称富化(names_for)软失败:返回 Redis 缓存命中的部分 + WARN,其余回落占位符 AS<asn>;绝不写负缓存(防把好 ASN 冻成"无名"一小时)
control-server /ui/registry/* 显式查询503(status_code 透传)
auth-server 验证方式枚举 / 挑战签发·核验fail-closed 503(detail registry service unavailable,前端稳定契约)
auth-server PGP 公钥回退链的 registry 一环跳过该候选源,继续 keyserver / 用户提交
auth-server OAuth claims 富化(ID Token / userinfo)软失败:令牌照发,富化字段回落空骨架 + WARN

control-server 侧的 regname: 单 ASN 粒度 Redis 缓存(正/负缓存,TTL 1h)保留在控制面 façade(app/services/registry.py)——缓存命中时零 HTTP,热路径不受拆分影响。

配置(env 前缀 DN42_REGISTRY_*,TOML /etc/dn42-registry/registry-server.toml)

TOML 键环境变量说明
server.service_tokenDN42_REGISTRY_SERVICE_TOKEN本服务 API 的 Bearer token(未配 = 全 API 403)
database.urlDN42_REGISTRY_DATABASE_URL独立库 DSN(生产 dn42_registry;默认 SQLite 便于 dev/CI)
upstream.tokenDN42_REGISTRY_UPSTREAM_TOKENgit.dn42.dev 只读 access token(未配 = 不同步,查询可用但表为空)
upstream.base_url / upstream.repoDN42_REGISTRY_UPSTREAM_BASE_URL / ..._REPO同步源(默认 https://git.dn42.dev / dn42/registry)

实配 deploy/docker/registry-server.toml;compose 服务 registry-server(镜像 deploy/docker/Dockerfile.registry-server)。消费方 depends_onservice_started 而非 service_healthy——registry 对控制面是软依赖,不能让它的故障连坐拖住控制面重启。

遗留与清算

  • dn42_control 库里曾有一组 registry_* 副本表。它们随本服务拆分已由迁移 b1c2d3e4f5a6 drop——控制面不再落 registry 库,全部查询经 HTTP 客户端打到本服务。
  • dn42_auth 库里的六张 registry 表在切换部署稳定后手动 DROP(见部署 runbook)。

测试

  • registryserver/tests:同步引擎 / 全部查询 / API 鉴权 / client↔server 契约(RegistryClienthttpx.ASGITransport 直连本服务 app,覆盖完整序列化路径)。SQLite/PG 双跑(DN42_REGISTRY_TEST_DATABASE_URL)。
  • auth-server 测试用同法 ASGI 内嵌一个真 registry-server(authserver/tests/test_challenge.pymake_registry_client)——挑战签发/核验/OAuth claims 走与生产完全相同的跨服务路径,而非对手工复刻的选项结构演戏。
  • control-server 测试用内存假 client(app/tests/test_registry.pyFakeRegistryClient)——那边要测的是缓存语义与代理映射,契约覆盖不重复。