外观
账号、门户与自动对等
身份与自动对等子系统。主体是 L1 配置事实源(分层模型)——账号、会话、审计与策略都需要事务、关联查询与审计追溯,丢了不可重建;另有一张 L3 时序表(autopeer_route_rollup)与一张已退为降级路径的表(portal_login_transactions)。
控制库中存在两个完全分离的信任域,本册各覆盖其一:
| 信任域 | 主体 | 表 | 覆盖的接口 |
|---|---|---|---|
| 管理员 | 运维人员 | admin_users、admin_sessions、admin_audit_log | /control/v1/admin/* 全部写操作 |
| 门户用户 | 外部 AS 的维护者 | portal_login_transactions、portal_sessions、peering_requests | /control/v1/autopeering/*,权限按已验证 ASN 划分 |
autopeering_node_policies 位于两域交界:由管理员写入(开放哪些节点、开放何种能力),供门户用户读取。
一处例外:登录往返的瞬态凭据(portal_login_transactions)主路径已迁至 L4(Valkey),本表退为降级路径,详见下文。
admin_users
一个管理员账号。app/db/models/auth.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
username | String(64) | NOT NULL,unique | 用户名 |
password_hash | String(255) | NOT NULL | argon2id 编码串(含参数与盐,形如 $argon2id$v=19$m=…) |
role | String(32) | NOT NULL,默认 admin | 预留字段,当前恒为 admin,无权限分级 |
created_at / updated_at | DateTime(tz) | NOT NULL,server_default now,updated_at 带 onupdate | 时间戳 |
admin_sessions
账号登录签发的一条会话。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
user_id | Integer | FK→admin_users.id(CASCADE),NOT NULL,index | 所属账号 |
token_hash | String(64) | NOT NULL,unique | 完整 Bearer 的 sha256 十六进制摘要(唯一查找键) |
created_at | DateTime(tz) | NOT NULL,server_default now | 签发时刻 |
expires_at | DateTime(tz) | NOT NULL | 过期时刻 |
revoked_at | DateTime(tz) | 可空 | 非空即已吊销(改密时批量吊销该用户全部会话) |
安全模型与 agent token 同纪律:明文令牌永不落库,只存 sha256 摘要;密码只存 argon2id 单向散列。账号会话令牌与静态 DN42_CONTROL_ADMIN_TOKEN 在 require_admin 同一入口无差别接受。登录失败计数在 L4(throttle:login:*),不落库。
admin_audit_log
append-only 的管理写操作记录。app/db/models/audit.py。无外键。每条对应一次到达 /control/v1/admin 的变更类请求(POST / PUT / PATCH / DELETE),无论鉴权是否通过。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 主键 |
actor | String(64) | 可空,index | 鉴权通过的主体:静态 admin token 记 admin,账号会话记用户名;鉴权失败时为空 |
method | String(8) | NOT NULL | HTTP 方法 |
path | String(512) | NOT NULL,index | 请求路径 |
status_code | Integer | NOT NULL | 响应状态码 |
detail | JSON | NOT NULL,默认 {} | 详情(如 query 串) |
created_at | DateTime(tz) | server_default now,index | 写入时刻 |
不设保留期:审计是唯一一张有意无限增长的表。当前 883 行 / 272 kB,按现有写入频率(管理写操作本身稀疏)无需处置。
portal_login_transactions
降级路径专用——主路径在 L4(Valkey db 1 的 portal:login:<state>,TTL 30 分钟)。
一次进行中的 OIDC 授权往返,短 TTL、单次使用。app/db/models/portal.py。
登录事务属于「30 分钟内消费一次即作废、无审计价值、不参与关联查询」的瞬态凭据,置于 KV 优于 PostgreSQL:TTL 自动回收、GETDEL 原子消费,同时免去一张表的死元组与「条件 UPDATE 抢占消费」的实现。KV 未配置或写入失败时自动落回本表,切换期间已在途的登录仍可正常完成(services/portal_sessions.py 保留两条路径)。
会话本身(portal_sessions)留在 L1:它有吊销语义、要被审计、还挂着上游 token 的刷新状态。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
state | String(64) | PK | 授权请求的 state,也是回调查找键 |
nonce | String(64) | NOT NULL | ID Token 绑定用 nonce |
code_verifier | String(128) | NOT NULL | PKCE 私密串,只在服务端 |
return_path | String(512) | NOT NULL,默认 / | 登录后回跳的站内相对路径(防开放重定向) |
expires_at | DateTime(tz) | NOT NULL | 30 分钟窗口 |
used_at | DateTime(tz) | 可空 | 非空即已消耗(条件 UPDATE 保证单次使用) |
created_at | DateTime(tz) | server_default now | 时间戳 |
portal_sessions
门户自有登录会话,与上游令牌解耦(TTL 见 [portal] session_ttl_seconds)。会话令牌只存 sha256。
注意:上游 access token 与 refresh token 以明文存储,因为服务端需要再次使用它们。这是控制库中敏感度最高的两张表之一,备份与访问控制应按此定级(另一张为 node_wireguard_keys)。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
token_hash | String(64) | PK | 会话令牌 sha256;兑换交接码时轮换 |
sub | String(64) | NOT NULL,index | OIDC 稳定标识(维护者 × AS) |
asn | BigInteger | NOT NULL,index | 已验证的 ASN,门户端点按它划权 |
mntner | String(128) | 可空 | 活跃维护者 |
claims | JSON | NOT NULL,默认 {} | 最近一次 ID Token / userinfo claims 缓存 |
claims_refreshed_at | DateTime(tz) | 可空 | claims 刷新时刻 |
oidc_access_token / oidc_refresh_token | Text | 可空 | 上游令牌(明文,见上) |
oidc_access_expires_at | DateTime(tz) | 可空 | 上游 access token 过期时刻 |
handoff_hash | String(64) | 可空,index | 一次性交接码 sha256;兑换后置空 |
handoff_expires_at | DateTime(tz) | 可空 | 交接码 2 分钟窗口 |
expires_at / revoked_at / created_at | DateTime(tz) | — | 会话生命周期 |
autopeering_node_policies
节点的自动对等供给策略。app/db/models/autopeering.py。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
node_id | String(64) | PK,FK→nodes.node_id(CASCADE) | 节点 |
enabled | Boolean | NOT NULL,默认 false | 是否开放自动对等 |
endpoint_host | String(255) | 可空 | 对端拨入用的公网主机名或 IP(不含端口)。未配置表示只支持本网主动拨出的形态,该节点不出现在可拨入清单里 |
max_sessions | Integer | 可空 | 可选的额外会话上限。可用连接数的单一事实源是外部端口池剩余量;本列为 NULL 表示只受端口池约束 |
notes | String(512) | 可空 | 运维备注 |
region | String(32) | 可空 | 大区枚举:east-asia / southeast-asia / europe / north-america / pacific-oceania / other。前端分类标签的口径,服务端不参与过滤 |
country_code | String(2) | 可空 | ISO 3166-1 alpha-2,前端渲染国旗 |
description | String(256) | 可空 | 一句话卖点 |
bandwidth_mbps | Integer | 可空 | 标称带宽(仅展示) |
monthly_quota_gb | Integer | 可空 | 月流量配额(仅展示用量占比,超额不触发任何处置) |
notice | String(256) | 可空 | 告警条文案;有值前端渲染提示 |
created_at / updated_at | DateTime(tz) | server_default now | 时间戳 |
peering_requests
一条自动对等请求。
提交即落成:同事务建 Peering 聚合并 materialize,status 直接落 provisioned、decided_by='auto'。免审批的依据是身份已由门户会话背书(ASN 归属验证),且配额与容量全部机器可判。请求或当场落成,或整体回滚且不留残行。
pending 与 rejected 是审批闸门时期的遗留状态值,当前代码不产出;cancelled 是拆除终态(用户自助拆除,或运维删聚合时联动推进)。手工建立的外部对等在对应 ASN 拉取门户列表时惰性收养为请求行(decided_by='adopted')。
连接参数列是提交或收养时刻的审计快照——落成后的展示真相在 Peering 聚合的接口与 BGP spec 里,门户读路径现查派生,管理面改动聚合后展示自动跟进,本表列不回写。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
id | Integer | PK,autoincrement | 请求 ID |
asn | BigInteger | NOT NULL,index | 请求方 ASN(取自门户会话,已验证) |
sub | String(64) | NOT NULL | 请求方稳定标识快照 |
mntner | String(128) | 可空 | 请求方维护者快照 |
node_id | String(64) | FK→nodes.node_id(CASCADE),NOT NULL,index | 目标节点 |
status | String(16) | NOT NULL,默认 pending,index | 状态机存储态 |
wg_public_key | String(64) | 可空 | 对端 WG 公钥快照。表单只收公钥,绝不收私钥 |
remote_endpoint | String(255) | 可空 | 对端拨入口 host:port 快照;空表示对端不提供固定入口 |
remote_link_local | String(64) | 可空 | 对端隧道 link-local 快照 |
notes | String(512) | 可空 | 用户留言 |
session_addressing | String(16) | NOT NULL,默认 link-local | link-local / ipv6-ula,仅 mp_bgp=true 时有意义 |
mp_bgp | Boolean | NOT NULL,默认 true | false 表示 v4 + v6 双纯会话 |
peer_ipv4 / peer_ipv6 | String(64) | 可空 | 对端隧道内地址(经 registry route / route6 校验) |
peering_id | Integer | FK→peerings.id(SET NULL),可空 | 落成的 Peering |
interface_name / listen_port / mtu | String(64) / Integer / Integer | 可空 | 落成结果快照 |
probed_pmtu | Integer | 可空 | 二分扫出的路径 MTU(接口 MTU 口径)。探测不改 spec,mismatch 只提示 |
mtu_probe_status | String(16) | 可空 | ok / mismatch / unreachable;三列全空表示未探测 |
mtu_probed_at | DateTime(tz) | 可空 | MTU 探测时刻 |
latency_rtt_avg_ms / latency_rtt_max_ms / latency_loss_pct | Float | 可空 | RTT 与丢包采样结果 |
latency_probed_at | DateTime(tz) | 可空 | 延迟探测时刻 |
decided_by / decided_at / reject_reason | String / DateTime(tz) / String | 可空 | 决策轨迹 |
created_at / updated_at | DateTime(tz) | server_default now | 时间戳 |
部分唯一索引 UNIQUE(asn, node_id) WHERE status IN ('pending','provisioned')——同一个 AS 对同一节点最多一条在途或生效的对等,从 DB 层兜住并发提交竞态。
autopeer_route_rollup
自动对等会话的路由计数时序(每 节点 × 接口 × 5 min 桶,L3),是门户状态页时序图的数据层。app/db/models/autopeering.py。
数据源:runtime snapshot 入库时对该节点的 autopeer 接口按 node_route_entries 计数(导入侧),upsert 当前桶。导出侧列在 agent 上报 per-AF 计数(1.0.233+)就位后由快照直读填充,更早的 agent 期间恒空。
注意:本表为 gauge 语义——取该桶最后一次观测值,既不累加也不求平均。这是全库唯一不设 sum / count 的时序表:路由条数属于瞬时量,累加同一桶内的多次观测没有意义。通用桶形态见时序建模通则。
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
node_id | String(64) | PK(复合),FK→nodes.node_id(CASCADE) | 所属节点 |
interface | String(64) | PK(复合) | 接口名 |
bucket_start | BigInteger | PK(复合) | 桶起点(epoch 秒,对齐 5 min) |
imported_v4 / imported_v6 | Integer | NOT NULL,默认 0 | 导入路由计数 |
exported_v4 / exported_v6 | Integer | 可空 | 导出路由计数;旧 agent 期间为 NULL |
updated_at | DateTime(tz) | server_default now,带 onupdate | 更新时刻 |
保留 60 天,与流量存档同口径。