外观
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}/mntners | ASN → 维护者联查(悬空引用跳过);未收录 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-commit(guess_mntner_method / filter_ssh_auths)。验证方式枚举与之逐档对齐:
auth: 方法 | 归档 | 说明 |
|---|---|---|
pgp-fingerprint <指纹> | pgp | 指纹去空格大写归一 |
PGPKEY-<8 位> | pgp | 按名字引用 key-cert 对象,回查 fingerpr: 得指纹后并入同一档;对象悬空则不出选项 |
ssh-* / ecdsa-* | ssh | 公钥体原样保留(base64 大小写敏感) |
sk-ssh-* / sk-ecdsa-* | ssh | FIDO 硬件钥,核验见 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_token | DN42_REGISTRY_SERVICE_TOKEN | 本服务 API 的 Bearer token(未配 = 全 API 403) |
database.url | DN42_REGISTRY_DATABASE_URL | 独立库 DSN(生产 dn42_registry;默认 SQLite 便于 dev/CI) |
upstream.token | DN42_REGISTRY_UPSTREAM_TOKEN | git.dn42.dev 只读 access token(未配 = 不同步,查询可用但表为空) |
upstream.base_url / upstream.repo | DN42_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_on 用 service_started 而非 service_healthy——registry 对控制面是软依赖,不能让它的故障连坐拖住控制面重启。
遗留与清算
dn42_control库里曾有一组registry_*副本表。它们随本服务拆分已由迁移b1c2d3e4f5a6drop——控制面不再落 registry 库,全部查询经 HTTP 客户端打到本服务。dn42_auth库里的六张 registry 表在切换部署稳定后手动 DROP(见部署 runbook)。
测试
registryserver/tests:同步引擎 / 全部查询 / API 鉴权 / client↔server 契约(RegistryClient经httpx.ASGITransport直连本服务 app,覆盖完整序列化路径)。SQLite/PG 双跑(DN42_REGISTRY_TEST_DATABASE_URL)。- auth-server 测试用同法 ASGI 内嵌一个真 registry-server(
authserver/tests/test_challenge.py的make_registry_client)——挑战签发/核验/OAuth claims 走与生产完全相同的跨服务路径,而非对手工复刻的选项结构演戏。 - control-server 测试用内存假 client(
app/tests/test_registry.py的FakeRegistryClient)——那边要测的是缓存语义与代理映射,契约覆盖不重复。