remote.文档首页

AI 代理应读取 https://remote.ivjn.us/SKILL.md。

用于 Agent 操作的 HTTP API

原生密封操作现已支持纯 HTTP 调用端。使用 remote-cli --transport http 或 REMOTE_TRANSPORT=http。POST /api/sealed/connections 创建连接;POST /input 提交签名握手或密文,GET /events 长轮询结果,DELETE 关闭连接。客户端继续负责端到端加解密与设备授权验证。原有设备操作接口仍返回 409,设备端连接保持不变。 HTTP 连接须固定到创建它的 Exchange 实例,90 秒无活动后过期。串行提交输入,重试时保持序号和密文一致;处理结果后用 after 确认。Web Console、移动端和 LAN 保持现有传输。执行预设仍需 manage 权限。

https://remote.ivjn.us/sealed-http.md

设备动作接口的 HTTP 409 / Sealed sessions are required. 表示需要密封会话,单独的 Bearer 令牌不能执行设备动作。请使用 remote-cli --profile alice exec lab -- uname -a;仅允许 HTTP 时使用 remote-cli --profile alice --transport http exec lab -- uname -a,CLI 仍执行签名握手和端到端加密。

基底 URL

正式环境 Exchange:

https://exchange.ivjn.us

自行架设的部署使用您自己的 HTTPS 来源。下列所有路径均相对于此基底。传送 JSON 时请使用 Content-Type: application/json。

验证

每个 /api/* 路由(公开的验证/注册辅助端点除外)都需要:

Authorization: Bearer <token>

<token> 可以是下列任一项:

  1. 来自 POST /api/auth/login 的密码登入工作阶段权杖
  2. 操作员主要 API 金钥(建立操作员帐号时仅显示一次的长效权杖)

两者皆由主控台与 CLI 使用的同一个 authenticate_operator 路径接受。

密码登入

curl -sS -X POST 'https://exchange.ivjn.us/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"alice@example.com","password":"your-password-here"}'

email 是帐号识别资料,必须为有效的电子邮件地址。服务会将其正规化(去除前后空白、转为小写),并要求完成地址验证。现有部署在帐号改用电子邮件前,仍可传送 { "username": "alice", "password": "..." };username 中包含 @ 的值会视为电子邮件。无效地址会在检查密码前遭到拒绝。

回应范例:

{
  "username": "alice",
  "email": "alice@example.com",
  "tenant_id": "00000000-0000-0000-0000-000000000000",
  "token": "<prefix>.<secret>",
  "expires_in": 2592000
}

将 token 用作 Bearer 凭证。使用相同标头呼叫 POST /api/auth/logout,即可撤销该工作阶段。登出不会撤销操作员主要 API 金钥。

操作员 API 金钥(主要权杖)

TOKEN='<prefix>.<secret>'   # issued at operator creation; store offline
curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"

列出装置

curl -sS 'https://exchange.ivjn.us/api/devices' \
  -H "Authorization: Bearer ${TOKEN}"

每个项目包含 id、name、online、enabled、info、时间戳记与 active_sessions。装置清单属于 Exchange 中继资料。列出装置不会建立操作该装置所需的密封工作阶段。

装置动作 HTTP 拒绝与工作阶段管理

所有装置操作都使用密封 WebSocket 工作阶段。下列 HTTP 路由无法建立用户端/装置握手,因此会对已验证、已授权且通过请求验证的请求传回 HTTP 409。拒绝发生在向装置派送之前:不会向代理传送任何命令、感测器请求、网路探测、更新、ubus 呼叫、保留工作请求或钉选命令。装置在线也无法让这些路由执行操作。

验证、权限与输入验证仍然适用。无效凭证传回 401;禁止的操作传回 403;无效命令、URL 或请求主体仍保留其验证错误,包括 400。密封工作阶段的 409 不会取代较早发生的验证或授权失败。钉选命令的权限检查遵循钉选命令原则;通过检查仍不允许透过 HTTP 执行。

方法与路径 通过验证、授权与输入验证后的行为
POST /api/devices/{id}/exec 409:在密封 WebSocket 工作阶段内执行
GET /api/devices/{id}/exec/holds 409:在密封 WebSocket 工作阶段内检视保留工作
POST /api/devices/{id}/exec/holds/{job_id}/kill 409:在密封 WebSocket 工作阶段内停止保留工作
GET /api/devices/{id}/exec/holds/{job_id}/cat 409:在密封 WebSocket 工作阶段内读取保留工作输出
POST /api/devices/{id}/sensors 409:在密封 WebSocket 工作阶段内收集感测器资料
POST /api/devices/{id}/net 409:在密封 WebSocket 工作阶段内执行网路探测
POST /api/devices/{id}/update 409:在密封 WebSocket 工作阶段内检查或套用更新
POST /api/devices/{id}/ubus 409:在密封 WebSocket 工作阶段内呼叫 ubus
POST /api/devices/{id}/commands/{cmd}/run 409:取得钉选命令,并以装置授权的 manage 授权在密封 WebSocket 工作阶段内执行

例如,下列有效动作请求是拒绝行为的检查,并非命令执行介面:

curl -i -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/exec" \
  -H "Authorization: Bearer ${TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{"command":"uname -a","timeout_secs":30}'

预期收到 HTTP 409,英文错误讯息为 Sealed sessions are required.(依请求语言本地化)。请建立原生密封 WebSocket 工作阶段来执行操作。此 HTTP 路由不会传回成功的命令输出回应。

Exchange 工作阶段管理

工作阶段清单与取消仍可透过 HTTP 使用,因为它们管理的是 Exchange 路由中继资料。它们不会执行装置操作,也不会开启额外的装置工作阶段。

方法与路径 成功回应主体
GET /api/devices/{id}/sessions {sessions:[DeviceSessionInfo], limit, count}
POST /api/devices/{id}/sessions/{session_id}/kill {session_id, released}

DeviceSessionInfo 栏位为 session_id(装置端数字 ID)、operator_session_id、kind、hold、age_secs 与 deadline_unix_ms。limit 为 max_sessions_per_device(预设 32);count 为目前清单长度。列出时会直接读取本机 Exchange 中枢状态;在本机离线的装置会传回 503。Kill 使用装置端 ID,传送适当的取消讯息(包括保留的 exec),即使装置未回应也会在本机释放路由。工作阶段不存在或装置在本机离线时,传回 HTTP 200 及 released:false。唯读操作员存取这两个端点都会收到 403。

curl -sS "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions" \
  -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "https://exchange.ivjn.us/api/devices/${DEVICE_ID}/sessions/${SESSION_ID}/kill" \
  -H "Authorization: Bearer ${TOKEN}"

Exchange 在因工作阶段数已满而拒绝请求前,会回收操作员已失效与超过截止时间的路由:exec 为逾时时间 + 15 秒,档案传输为 3600 秒 + 15 秒,其他种类为 24 小时。仍在截止时间内的保留 exec,在操作员断线时会分离,而非遭到终止。

原生密封 CLI 操作

CLI 会在一般装置操作中建立密封工作阶段。请先完成工作区根核准与装置授权;请参阅密封工作阶段。无法选择停用密封改用明文。一般命令不需要额外的密封旗标:

remote-cli exec "$DEVICE_ID" -- uname -a
remote-cli shell "$DEVICE_ID"
remote-cli exec-holds "$DEVICE_ID"
remote-cli exec-hold-cat "$DEVICE_ID" "$JOB_ID"
remote-cli exec-hold-kill "$DEVICE_ID" "$JOB_ID"
remote-cli sensors "$DEVICE_ID" --json
remote-cli net "$DEVICE_ID" ping 1.1.1.1
remote-cli net "$DEVICE_ID" http https://example.com/
remote-cli ubus "$DEVICE_ID" system board '{}'
remote-cli commands list "$DEVICE_ID"
remote-cli commands run "$DEVICE_ID" "$COMMAND_ID"

保留工作操作会在密封工作阶段内执行;Exchange 工作阶段管理则可另透过 remote-cli sessions 与 remote-cli session-kill 使用。取得钉选命令中继资料不代表获准执行:装置必须接受该密封操作员的 manage 授权。

密封工作阶段金钥目录与授权转送

Exchange 储存操作员公开金钥及拥有者签署的 rsg1. 授权权杖,让用户端能互相探索。**这本身不是授权。**装置仍会依本机原则验证拥有者签章。Exchange 仅为权杖承载资料(owner_key、operator_key、device_id、level、expires_at)建立索引,绝不将上传视为许可。Exchange 无法伪造根签章,但可以扣留凭证、向自动核准的根用户端不实呈现名册、观察中继资料或拒绝服务。

工作区根(rsr1.)与授权邀请(rsi1.)也是同一类转送。Exchange 会检查权杖格式及 Ed25519 自签章(邀请也必须符合已储存的根金钥),而不会决定核准哪些操作员金钥。GET /api/seal/roster 是供超级管理员检视成员、角色与 operator_device_grants 的端点。在根用户端于本机核准前,用户端不得将新出现的金钥视为可信任。请参阅 docs/sealed-sessions.md。

任何已验证的工作区成员都可列出金钥、注册自己的金钥及上传授权(授权可自行验证)。金钥的 DELETE 仅限注册该金钥的帐号(管理员可删除任何金钥)。授权的 DELETE 仅限上传者(管理员可删除任何授权)。GET /api/seal/grants 会略过已过期的资料列。

金钥注册证明是针对下列内容的 Ed25519 签章:

remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>

issued_at_ms 必须在伺服器时钟的 ±10 分钟内。public_key 与 proof 使用标准 base64(32 位元组金钥、64 位元组签章)。

方法与路径 请求主体 成功回应
POST /api/seal/keys {"public_key","label"?,"proof","issued_at_ms"} 已注册的金钥(同一帐号采新增或更新)
GET /api/seal/keys — [{operator_id, username, email, public_key, label, created_at, last_seen_at}]
DELETE /api/seal/keys/{key} — 204({key} 是经 URL 编码的公开金钥)
POST /api/seal/grants {"tokens":["rsg1.…"]} {stored:[…], errors:[…]}
GET /api/seal/grants?mine=1 — operator_key 为呼叫者已注册金钥之一的授权。每个 owner_key 不是已发布根金钥的授权也会包含 chain(凭证权杖,可能为空)
GET /api/seal/grants?owner_key=… — 由该拥有者金钥签署的授权
DELETE /api/seal/grants/{id} — 204
PUT /api/seal/root {"token":"rsr1.…","rotation":"rsx1.…"?} 已储存的工作区根。仅限超级管理员。会检查格式及金钥自身的签章;这不代表核准。rotation(选用)必须由目前已发布的根签署,并指明新的根;接著 Exchange 会向线上密封装置推送 seal_root {token, rotation},装置仅在轮替资料由已信任的根签署时才重新钉选。没有有效轮替资料时,已钉选旧根的装置会保留旧根
GET /api/seal/root — {workspace_id, root_key, payload, signature, token, created_at},若尚未发布则为 404
POST /api/seal/invitations {"token":"rsi1.…"} 已储存的邀请。仅限超级管理员,且根必须已存在
GET /api/seal/invitations — 未过期的邀请,最新的优先(没有时为 [])
GET /api/seal/roster — {members:[{operator_id, username, email, role, restricted, enabled, keys:[{public_key, label}], device_grants:[{device_id, can_manage}]}]}。仅限超级管理员。包含已停用的成员

目录不可用时仍须满足密封会话和设备授权要求。尚未发布工作区根时,GET /api/seal/root 返回 404。

# Register this operator key (proof signed locally)
curl -sS -X POST "$BASE/api/seal/keys" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"public_key":"<ed25519-b64>","label":"cli","proof":"<sig-b64>","issued_at_ms":1700000000000}'

curl -sS "$BASE/api/seal/keys" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X POST "$BASE/api/seal/grants" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"tokens":["rsg1.…"]}'

curl -sS "$BASE/api/seal/grants?mine=1" -H "Authorization: Bearer ${TOKEN}"

中继凭证(rsc1.)

Exchange 储存中继凭证,让非根金钥也能签署装置会接受的授权。它会验证签发者的 Ed25519 签章,且无法伪造凭证。上传权杖不代表授权:呼叫者必须是签发者的操作员。由根签署的凭证(issuer_key 等于已发布的工作区根)仅可由工作区超级管理员发布。中继凭证仅可由已注册密封金钥为 issuer_key 的帐号发布,且该签发者本身必须是已储存、未撤销、未过期且具备 can_issue 的凭证主体。子凭证的层级、装置范围与到期时间必须受限于父凭证(与装置所做的检查相同)。重新发布相同的 (issuer_key, subject_key) 会取代旧资料列并清除撤销状态。

GET /api/seal/certs/chain?subject_key= 会沿著签发者链结向上追溯至已发布的根,传回 {chain:[tokens]},最多 4 张凭证,由根签发的排在最前,或传回 404。任何已验证成员都可呼叫。GET /api/seal/grants?mine=1 会在非根签署的授权中加入该 chain,让用户端随授权一并取得。由根签署的授权会省略 chain。

GET /api/seal/certs 会列出所有资料列,包括已撤销者(供管理员与超级管理员使用)。DELETE /api/seal/certs/{serial}(由签发者帐号或超级管理员呼叫)会设定 revoked。这只会停止分发。装置仍会接受已取得的副本,直到其过期;硬性撤销需使用装置原则的 revoked_keys。{serial} 是经 URL 编码的标准 base64 序号。

方法与路径 请求主体 成功回应
POST /api/seal/certs {"token":"rsc1.…"} 已储存的凭证(依签发者与主体新增或更新)
GET /api/seal/certs/chain?subject_key= — {chain:["rsc1.…"]},由根签发的优先,或 404
GET /api/seal/certs — [{serial, issuer_key, subject_key, token, expires_at_ms, created_at, revoked}]。管理员或超级管理员
DELETE /api/seal/certs/{serial} — 204。签发者或超级管理员。仅停止分发
curl -sS -X POST "$BASE/api/seal/certs" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"token":"rsc1.…"}'

curl -sS "$BASE/api/seal/certs/chain?subject_key=<ed25519-b64>" \
  -H "Authorization: Bearer ${TOKEN}"

工作区根金钥备份(选用)

PUT / GET / DELETE /api/seal/backup 为每个帐号储存一个不透明的 rsb1. 资料块。PUT 与 GET 仅限超级管理员:资料块是工作区的根私密金钥,在用户端以 Argon2id(64 MiB、t=3、p=1)与 ChaCha20-Poly1305 加密。通关片语为选用,且没有强制强度要求。**若通关片语为空白或强度不足,任何持有资料块的人(包括 Exchange)都能解密根金钥并控制所有装置。**上传是选用的;用户端也可汇出根金钥(rsk1.)并离线保存。Exchange 会检查 blob 是否以 rsb1. 开头及其长度,绝不解密。GET 在每个处理程序内限制为每个帐号每小时 5 次请求(HTTP 429)。读取、写入与删除均会稽核,但仅记录位元组数。回应会传送 Cache-Control: no-store。

方法与路径 请求主体 成功回应
PUT /api/seal/backup {"blob":"rsb1.…"} {blob, updated_at}
GET /api/seal/backup — {blob, updated_at},未储存时为 404。有速率限制
DELETE /api/seal/backup — 204,即使资料列不存在亦同
curl -sS -X PUT "$BASE/api/seal/backup" \
  -H "Authorization: Bearer ${TOKEN}" -H 'Content-Type: application/json' \
  -d '{"blob":"rsb1.…"}'

curl -sS "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"

curl -sS -X DELETE "$BASE/api/seal/backup" -H "Authorization: Bearer ${TOKEN}"

浏览器扩充功能的根凭证链验证

浏览器操作使用原生密封 WebSocket 工作阶段,而非 HTTP 单次执行。扩充功能会自行产生 Ed25519 操作员金钥。它使用操作员 Bearer 权杖,从 /api/auth/me 取得 tenant_id / operator_id,再以 {public_key, label, proof, issued_at_ms} 呼叫 POST /api/seal/keys。PoP 签署的内容为:

remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>

金钥/签章使用标准 base64(32/64 位元组);时间必须在伺服器时钟的 ±10 分钟内。注册仅证明持有金钥,不代表授权。扩充功能会同步 GET /api/seal/root、GET /api/seal/grants?mine=1,以及授权内附或从 GET /api/seal/certs/chain?subject_key=<url-encoded-key> 取得的凭证权杖。授权必须能直接或经由有效凭证链验证至可信任的工作区根,并具备正确的装置范围、层级与有效期限。装置权杖注册与此操作员权杖目录流程仍彼此独立。

扩充功能会透过其根验证收到的原生 hello,并将自己签署的 hello/授权/凭证链放入 SealAccept.peer。CLI/主控台会验证该证明,并要求 peer.operator_key == accept.device_key,才会接受已签署回应。扩充功能担任操作员时,Rust 代理使用其一般根原则。证明无效或缺少时会拒绝连线;没有独立的浏览器信任储存区/介面或旧版备援机制。原生装置与工作区根的首次使用即信任(TOFU)仍然存在;扩充功能会为每个伺服器/工作区储存首次取得的根,并拒绝变更。

**安全性:**请透过带外方式验证首次取得的根;PoP 或伺服器角色不代表根已核准。在根用户端自动核准前,请审查未知金钥。保护端点设定档/金钥与托管指令码。从目录删除不会撤销已快取的签署凭证。Exchange 可看到中继资料、扣留凭证或拒绝服务;密封机制无法隔离具敌意且拥有完整 CDP 权限的操作员。

选择 WebSocket 或 HTTPS

需求 使用方式
命令、保留工作检视或取消、感测器、网路探测、更新、ubus、钉选命令 原生密封 WebSocket 工作阶段
互动式 PTY、串流输出、档案传输、Android 萤幕/树状结构/点按 原生密封 WebSocket 工作阶段
验证、装置清单、金钥/授权/凭证目录、根备份 HTTPS API
Exchange 路由工作阶段清单与取消 HTTPS 工作阶段管理端点
即时装置在线状态与长效操作员连线 WebSocket;连线上的装置操作仍需要密封

浏览器票证流程(凭证绝不放入 WebSocket URL):

  1. 以 Authorization: Bearer … 呼叫 POST /api/ws-ticket。
  2. 连线至 GET /ws/browser,并传送第一个 JSON 影格 {"ticket":"…"}。
  3. 在传送任何装置操作之前,先建立原生密封用户端/装置握手。操作员的授权与凭证链必须能通过装置信任的根验证。

原生用户端使用 GET /ws/operator,并采用操作员验证及相同的密封工作阶段要求。仅有 WebSocket 连线或成功的帐号登入,不代表获准执行装置操作。请参阅 Web 主控台与 Web Push、通讯协定及密封工作阶段。

错误状态速查

状态 常见原因
400 动作输入无效,例如空白命令或无效的更新来源 URL;验证发生在密封工作阶段拒绝之前
401 缺少或无效的 Bearer 权杖
403 帐号或装置权限未授权此请求
409 有效且已授权的 HTTP 装置动作需要密封 WebSocket 工作阶段;不会向装置派送
404 依端点契约,目录项目不存在或尚未发布工作区根
429 端点特定的速率限制,包括工作区根备份读取
503 工作阶段清单在本机 Exchange 中枢状态中找不到装置

装置动作 HTTP 路由不会等待代理输出,因此正常已授权的结果是 409,而非执行成功、离线派送错误或 HTTP 执行逾时。中继资料、验证与目录 API 的错误仍保留各端点特定的含义。

手动验证计划

使用具备已知权限的测试帐号与装置。HTTP 拒绝检查不需要在装置上执行作业:

  1. 透过 HTTPS 验证并列出装置;确认帐号与装置权限。
  2. 向拒绝表格中的每个装置动作路由提交有效请求。预期收到 HTTP 409,并确认没有任何动作抵达代理,包括保留工作读取与钉选命令执行。
  3. 分别检查无效凭证、禁止存取的帐号与无效请求输入。预期收到验证、权限与输入验证错误,而非假设每个请求都传回 409。
  4. 读取 Exchange 工作阶段清单。针对明确选定、可舍弃的工作阶段测试取消,并验证 released;工作阶段不存在时会传回 released:false。对在本机离线的装置列出工作阶段会传回 503。
  5. 完成根核准与授权,再执行无害的原生 CLI 操作,例如 remote-cli exec "$DEVICE_ID" -- uname -a。确认它透过密封握手成功执行。装置授权缺少或无效时,必须拒绝执行。
  6. 分别验证金钥注册、授权/凭证撷取、选用根备份与浏览器根凭证链验证;这些 HTTPS API 仍依上述契约提供服务。

审计推送目标

仅 PostgreSQL Exchange 支持,按已认证工作区隔离,只有管理员和超管可用;其他账号返回 403。Worker 版暂不支持,Web / 移动 Console 没有配置界面。

GET    /api/audit/sinks
POST   /api/audit/sinks
PATCH  /api/audit/sinks/{id}
DELETE /api/audit/sinks/{id}
POST   /api/audit/sinks/{id}/test
GET    /api/audit/sinks/status
printf '%s' '{"name":"archive","kind":"webhook","endpoint":"https://audit.example.net/events","secret":"REPLACE-WITH-RANDOM-SECRET","mode":"full"}' | remote-cli audit sink add
remote-cli audit sink list
printf '%s' '{"interval_ms":500,"batch_size":16}' | remote-cli audit sink update 1
remote-cli audit sink test 1
remote-cli audit sink status
remote-cli audit sink remove 1

默认 throttled 直接发送已落库审计:文本超过 256 KiB 留首 250 KiB / 尾 6 KiB,文件仅 hash,流式在会话结束后发送。full 在截断前采集,原始 JSON 和二进制按块(默认 1 MiB)推送,含操作 / 资产 ID、序号、偏移、结束标志、SHA-256 和总大小。sealed 两者都只有元数据及密文引用 / hash,不能恢复端点明文。

secret 和 headers 加密保存,读取只显示 ***;PATCH *** 保留,header 的 null 删除。网络异步、持久化 FIFO、指数退避重试。节流满队列丢最旧并计数;全量磁盘队列满则背压。status 显示成功 / 失败、积压、dropped、gaps。删除会丢弃积压且记录审计。所有配置变更同事务审计。

webhook 是 JSON events 数组,二进制 base64;X-Remote-Timestamp 与 X-Remote-Signature: sha256=<hex> 校验 HMAC-SHA256(timestamp + "." + 原始 body)。OTel 使用 OTLP/HTTP JSON /v1/logs,块放 body.bytesValue,属性 remote.device.id / remote.operator / remote.action;service.name 可配置。gRPC 通过 Collector 转发,不直接支持。

以下为默认配置、webhook 载荷、验签和 Collector 示例。验签使用原始 HTTP body;持久化并按 delivery_id 去重后才返回 2xx。Loki 3.x 开启 allow_structured_metadata,在 Grafana 添加 Loki 数据源并查询 {service_name="remote-exchange"}。配置 gRPC 时将 pipeline exporters 改为 [otlp/grpc]。生产接入应使用 TLS 和认证。

{"name":"collector","kind":"otel","mode":"throttled","endpoint":"https://collector.example.net:4318/v1/logs","headers":{"Authorization":"Bearer REPLACE"},"service_name":"remote-exchange","resource":{"deployment.environment.name":"production"},"batch_size":32,"interval_ms":1000,"chunk_bytes":1048576,"queue_limit":10000,"overflow_bytes":10737418240,"actions":[],"devices":[],"operators":[],"enabled":true,"ca_pem":null}
{"version":1,"events":[{"delivery_id":123,"type":"full","mode":"full","entry":{"id":42,"action":"exec","operator_name":"alice","device_id":"dev"},"data":{"kind":"binary","asset_id":"asset-uuid","path":"/data_base64","sequence":0,"offset":0,"last":true,"data_base64":"YWJj","total_size":3,"sha256":"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad","context":{"direction":"output","frame_sequence":0,"stream_offset":0}}}]}
import hashlib, hmac, time

def verify(headers, raw_body, secret):
    stamp = headers['X-Remote-Timestamp']
    if abs(time.time() - int(stamp)) > 300:
        raise ValueError('expired webhook')
    expected = 'sha256=' + hmac.new(
        secret.encode(), stamp.encode() + b'.' + raw_body, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(expected, headers['X-Remote-Signature']):
        raise ValueError('invalid signature')
receivers:
  otlp:
    protocols:
      http:
        endpoint: 127.0.0.1:4318
processors:
  batch: {}
exporters:
  otlphttp/loki:
    endpoint: http://loki:3100/otlp
  otlp/grpc:
    endpoint: telemetry.example.net:4317
    headers:
      Authorization: "${env:OTEL_AUTHORIZATION}"
    tls:
      insecure: false
service:
  pipelines:
    logs:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlphttp/loki]
审计字段 OTLP 字段
service_name / resource Resource service.name / attributes
entry.started_at timeUnixNano
entry.id / delivery_id remote.audit.id / remote.delivery.id
entry.device_id / operator_name / action remote.device.id / remote.operator / remote.action
entry.tenant_id remote.workspace.id
entry.started_at / ended_at / detail remote.started_at / remote.ended_at / remote.result
traceId / spanId traceId / spanId
data.data_base64 body.bytesValue / remote.chunk.*
stored events body.stringValue