安全传输:端到端配置
此页面是用于在三种拓扑结构中安全地将客户端连接到守护进程的完整配置参考:
- 客户端直接连接守护进程 - 双向 TLS WSS,无中继。
- 守护进程到中继 - 守护进程与指定的中继保持出站桥接,因此即使位于 NAT/CGNAT 后也可访问。
- 客户端到中继再到守护进程 - 客户端_通过_该中继连接到守护进程,而真正的客户端<->守护进程 mTLS 仍在守护进程处终止。
如需查看 60 秒快速入门,请参阅远程设置 (WSS);本页将深入介绍每个选项的调节方法。
心智模型:两个信封,一个信任边界
存在两层 TLS,但其中只有一层是安全边界:
- 内部 mTLS(真正的边界)。仅支持 TLS 1.3,双向认证。客户端出示由守护进程签发的证书;守护进程出示其服务器叶证书。这是 RPC 平面。其上不存在仅服务器端 / 未认证路径 - 始终要求客户端证书。
- 外层 TLS(元数据边界)。 当中继位于路径中时,中继会终止外层 TLS + WebSocket 会话,并转发无法解读的密文。它从不持有能够读取内部 RPC 的密钥。在直连拓扑中不存在外层。
默认端口(均可配置):
| Plane | 默认 | 配置 |
|---|---|---|
| 守护进程 WSS(内部 mTLS RPC) | 9781 | [wss].port |
| 守护进程注册端点 | 9782 | [enroll].port |
| 中继(外层 TLS + WS) | 8443 | 中继 --bind / [bind] |
在全文中,<data_dir> 表示守护进程的数据目录(通常为 ~/.zeroclaw),而 <config-dir> 表示客户端的 zerocode 配置目录(--config-dir,通常为 ~/.zeroclaw)。配置文件不会展开 ~;请使用绝对路径。
拓扑 1:客户端直接连接到守护进程
zerocode ===== mutual-TLS WSS (TLS 1.3) =====> daemon [wss] :9781
1a. 守护进程端
启用 WSS 监听器。安全的默认设置是在首次启动时让守护进程自动生成自己的 CA 和服务器证书,这样您无需手动管理任何 TLS 材料:
[wss]
enabled = true
# bind = "0.0.0.0" # 默认值
# port = 9781 # 默认值
# 将 cert_path/key_path 留空,以便在首次启动时自动在
# <data_dir>/tls/ 下生成服务器证书。仅在需要使用自己的服务器证书时设置它们。
首次启动且 [wss].enabled = true 时,守护进程会在 <data_dir>/tls/(目录模式 0700)下写入:
| 文件 | 目的 | 模式 |
|---|---|---|
ca.crt | 每个守护进程的 CA 证书(公开) | 默认 umask |
ca.key | CA 私钥(用于签署客户端证书) | 0600 |
server.crt | WSS 服务器叶证书(SAN:localhost、127.0.0.1) | 默认 umask |
server.key | WSS 服务器私钥 | 0600 |
私钥会以 0600 权限写入;公有证书使用进程的 umask。tls/ 目录本身为 0700。
CA 绝不会静默轮换:如果 ca.crt 和 ca.key 存在,就会重复使用。自动生成的 CA 有效期为 10 年;服务器叶证书有效期约为 27 个月;签发的客户端证书有效期为 30 天。
开放端口(sudo ufw allow 9781/tcp)并启动守护进程。您应该会看到一条日志,显示 WSS 监听器已在 0.0.0.0:9781 上启动。
客户端现在需要证书。有两种获取方式。
1b. 客户端 - 选项 A:注册(推荐)
注册功能会通过一个受配对控制且经服务器身份验证的端点,在无需手动处理证书的情况下为无证书客户端提供其首个证书。启用它:
[enroll]
enabled = true
# bind = "0.0.0.0" # 默认
# port = 9782 # 默认
# 需要启用 [wss],并提供守护进程 CA 密钥(使用上方自动生成的密钥,或 BYO+key)。
# 如果缺少 CA 密钥,端点将默认拒绝连接,并且必须通过带外方式配置证书。
守护进程启动时,会将一次性 配对码 和 短认证字符串(SAS) 输出到其控制台/日志中。该代码只能使用一次,并在生成 10 分钟后过期——它是签发证书的唯一持有者凭据,而且会出现在控制台和日志中,因此复制的代码必须在操作员使用后不久停止工作。过期的代码会被拒绝并清除;需要新的代码时,请通过网关的配对 API 生成替代代码。然后,在工作站上:
# 交互式:无证书客户端在首次连接时自动注册。
zerocode --connect wss://<remote-host>:9781
# 或明确地/以非交互方式:
zerocode --enroll --connect wss://<remote-host>:9781
zerocode 会提示输入配对码,在本地生成 P-256 密钥和 CSR (私钥永远不会离开设备),并显示 SAS。请确认 SAS 与守护进程打印的 SAS 匹配(这可以检测出中间人 CA),然后它会在 <config-dir>/tls/ 下缓存:
| 文件 | 目的 | 模式 |
|---|---|---|
client.crt | 已签发的客户端证书 | 默认 umask |
client.key | 客户端私钥(在本地生成) | 0600 |
ca.crt | 为 RPC 平面固定的守护进程 CA 证书链 | 默认 umask |
profile.json | 已缓存的 device_id、not_after、中继配置文件 | 默认 umask |
后续每次运行都无需配置(zerocode --connect wss://<remote-host>:9781;如果配置中包含 uri,也可以直接运行 zerocode)。证书会在其生命周期的约 50%(约 15 天)时,通过活动的 mTLS 会话自动续期;已撤销的证书无法自行续期。
注册端点默认值:--enroll-host 默认为 --connect 的主机;--enroll-port 默认为 9782。
首个版本有意要求每次注册都使用配对码。在客户端拥有明确的无代码信任锚(例如固定的守护进程 CA 指纹)之前,保留的 allow_unpaired_enrollment 配置项会在守护进程启动时被拒绝。这样可以避免临时注册 TLS 路径变成盲目的首次使用时信任。
客户端 - 选项 B:由操作员签发的证书
如果您更愿意在守护进程上生成证书并将其复制出来:
# 在守护进程主机上。--out-dir 还会写入可直接使用的 ca.crt/client.crt/client.key。
zeroclaw security issue-client-cert --name my-laptop --out-dir /tmp/my-laptop-tls
添加 --force 以覆盖此名称的现有证书
将这三个文件复制到客户端的 <config-dir>/tls/,并分别命名为 ca.crt、client.crt、client.key(这样 zerocode --connect wss://host:9781 就会自动找到它们),或者显式指定它们的位置:
zerocode --connect wss://<remote-host>:9781 \
--tls-ca-cert /path/ca.crt \
--tls-client-cert /path/client.crt \
--tls-client-key /path/client.key
等效配置(因此直接使用 zerocode 即可):
[connection.wss]
uri = "wss://<remote-host>:9781"
[connection.wss.tls]
ca_cert_path = "/abs/path/ca.crt"
client_cert_path = "/abs/path/client.crt"
client_key_path = "/abs/path/client.key"
未注册证书的客户端在未完成注册的情况下访问 WSS 平面时,会收到明确的“请先注册”消息(同时,守护进程会记录被拒绝的未迁移客户端)——绝不会静默挂起。
--tls-skip-verify仅放宽针对自签名开发守护进程的 服务端 验证;客户端证书仍然是必需的。
拓扑 2:守护进程到中继
daemon ====== outbound: register + bridge ======> relay :8443
[relay] (blind forwarder)
守护进程连接到中继,证明一个稳定的 Ed25519 身份,并注册一个 node-id。客户端随后连接到该 node-id(拓扑 3)。中继始终只转发密文。
2a. 运行中继(zerorelay)
使用 relay.toml 进行配置(参见 apps/zerorelay/relay.example.toml);每个命令行标志都会覆盖文件中的对应值。[admission] 部分会在 SIGHUP 时热重载;如果重载会在未显式启用的情况下将公共中继变为开放且无需令牌的准入,则会被拒绝,并继续使用之前的策略。
# relay.toml
bind = "0.0.0.0:8443"
[tls]
# 首次运行时省略 cert/key 可将外层 TLS 证书自动配置到 dir(无需
# openssl)。将 sans 设置为 relay 的公共主机名/IP 地址。
dir = "/data/tls"
sans = ["relay.example.com"]
# 或者使用你自己的证书(例如公共 CA 证书):
# cert = "/etc/zerorelay/fullchain.pem"
# key = "/etc/zerorelay/privkey.pem"
[admission]
# "open" 允许任何已签名的守护进程接入(受 deny 列表约束);"allowlist" 仅允许
# 列出的守护进程公钥指纹接入。deny 始终优先。
mode = "open"
allow = []
deny = []
# 公共(非回环)relay 必须限制注册:在此设置共享密钥
# (每个守护进程通过 [relay] relay_token 提供该密钥),或使用 mode = "allowlist"。
# 否则,使用公共 bind 的 OPEN、无令牌 relay 将拒绝启动,因为
# 互联网上的任何守护进程都可能注册并抢占无人认领的 node-ids。(本地开发时使用的
# 回环 bind 不受此限制;有意运行的开放公共 relay 可通过 allow_public_open = true
# 覆盖此限制。)
relay_token = "change-me-to-a-long-random-secret"
[limits]
max_conns_per_node = 256
idle_timeout_secs = 300
lease_ttl_secs = 300
accept_burst_per_ip = 30
accept_rate_per_ip = 10.0
connect_burst_per_node = 60
connect_rate_per_node = 20.0
运行它:
zerorelay --config /etc/zerorelay/relay.toml
# 同样,仅使用标志(公开绑定需要令牌或允许列表,否则 the
# relay refuses to start):
zerorelay --bind 0.0.0.0:8443 --tls-san relay.example.com \
--relay-token change-me-to-a-long-random-secret
省略 --tls-cert/--tls-key 时,中继会在 TLS 目录下自行生成 CA 和服务器证书(解析顺序:$ZERORELAY_DATA_DIR/tls,否则为 $HOME/.zerorelay/tls,再否则为 ./zerorelay/tls);SAN 中始终包含 localhost 和 127.0.0.1。自行生成的 ca.crt 是守护进程/客户端用于信任中继外层 TLS 的证书。
准入。 open 模式加上可选的 relay_token 是最简单的访问控制方式。allowlist 模式根据守护进程注册公钥的指纹进行匹配(守护进程 <data_dir>/relay/registration.key 中 Ed25519 密钥的 SHA-256 十六进制值);将指纹添加到 allow 中(并使用 kill -HUP <pid> 重新加载)。节点 ID 会与其首个注册者的公钥绑定,因此不同的密钥无法劫持正在运行的节点 ID(会收到 node_taken)。
Docker. apps/zerorelay/Dockerfile 采用 distroless 运行,并使用 CMD ["--config", "/etc/zerorelay/relay.toml"] 和无 shell 的 zerorelay healthcheck --addr HEALTHCHECK;compose.yaml 将卷挂载到 /data,从而使自行配置的 TLS 得以持久化。暴露 8443 端口。
2b. 将守护进程指向中继服务器
[wss]
enabled = true # 必需:中继转发到本地 WSS 监听器
[relay]
enabled = true
url = "relay.example.com:8443"
# node_id:留空(推荐),以自动生成并持久化一个随机的 128 位
# 能力凭证,存储于 <data_dir>/relay/node_id。仅在需要固定特定 ID 时设置。
# token = "change-me" # 如果已设置,则必须与中继的 [admission].relay_token 匹配
# 对中继外部证书的信任——选择一个:
relay_ca_path = "/path/to/relay/ca.crt" # 信任中继的(自签名)CA
# tofu = true # 或在首次使用时固定中继叶证书
# relay_insecure = true # 或跳过外部验证(仅限开发环境)
# (对于使用公共 CA 的中继,将这三个选项全部留空,以使用内置公共根证书)
[relay] 要求启用 [wss](中继会桥接到 127.0.0.1:<wss.port>),如果 url 为空则默认拒绝。守护进程启动时会记录节点 ID,并附带提示 “将此作为 –relay-node 提供给客户端”;你也可以从 <data_dir>/relay/node_id 中读取该 ID。守护进程的稳定注册密钥会创建于 <data_dir>/relay/registration.key(0600)。
外层证书信任优先级(从高到低):relay_insecure > relay_ca_path > <data_dir>/relay/relay_pin 中存储的 pin(显式配置或 TOFU)> tofu > 公共根证书。因此,配置 CA 会替换过时的 pin,但不会将其删除。使用 tofu = true 时,观测到的 relay 叶证书指纹会被固定到 <data_dir>/relay/relay_pin,注册过程会将同一个 pin 传递给客户端,以便它们固定同一个叶证书。
2c.(可选)node-id 轮换和外部 mTLS
[relay]
node_id_rotation_days = 30 # auto-rotate the auto-minted id every N days (0 = never)
轮换会生成一个新的 ID,并让它与旧 ID 并行运行 10 分钟的宽限期,以免正在进行中的客户端连接被中断,随后停用旧 ID;客户端会在下一次证书续期时通过带内方式获取新 ID。现在可使用 zeroclaw security relay-rotate-node-id 强制执行一次(仅限 auto-mint 模式;固定的 node_id 永不轮换)。
对于也在外层对守护进程进行身份验证的中继,请在中继上设置 [admission].outer_client_auth = "required" + outer_client_ca,并在守护进程上设置 [relay].outer_client_cert / outer_client_key。这会在外层 TLS 上添加额外配置,且不会触及内部 mTLS。
拓扑 3:客户端到中继再到守护进程
zerocode ==outer TLS+WS==> relay ==forwards ciphertext==> daemon
\________________ inner mutual-TLS (TLS 1.3) terminates here _______________/
这结合了拓扑 1 和 2:客户端需要一个内部客户端证书(按照 1b 进行注册)以及中继协调信息(地址、node-id,以及对中继外部证书的信任)。
3a. 简便路径:注册携带中继配置文件
当守护进程配置了 [relay] 时,其注册响应会包含一个中继配置(relay_url、node_id 和中继的叶证书 relay_cert_pin)。因此,一次注册即可配置所有内容:
zerocode --enroll --connect wss://<daemon-host>:9781
zerocode 会将内部证书 和 中继配置缓存到 <config-dir>/tls/profile.json。之后,不带任何选项的普通 zerocode 会通过中继访问守护进程——它已经知道中继地址、节点 ID 和 PIN。
3b. 手动路径
显式向客户端提供中继坐标。内部证书仍来自注册或 --tls-*(拓扑 1):
zerocode \
--relay relay.example.com:8443 \
--relay-node <node-id-from-daemon-log> \
--relay-ca /path/to/relay/ca.crt
# inner mTLS material: from <config-dir>/tls (after enrolling), or pass --tls-* flags
为中继的外部证书准确选择一种信任模式,与守护进程保持一致:
| 标志 | 含义 |
|---|---|
--relay-ca <pem> | 信任中继的(自签名)CA |
--relay-pin <sha256> | 固定中继的外层叶证书(通常在注册时提供) |
--relay-tofu | 首次使用时信任;将 pin 持久化到 <config-dir>/relay/relay_pin |
--relay-insecure | 跳过外部验证(仅限开发/自签名) |
| (none) | 使用内置的公共根证书(public-CA 中继) |
--relay-host <name> | 覆盖预期的外部证书 SAN(默认为 --relay 主机) |
配置等效项(因此直接使用 zerocode 即可):
[connection.wss]
relay_url = "relay.example.com:8443"
relay_node = "<node-id>"
中继的外层信任(–relay-ca / –relay-pin / –relay-tofu / –relay-insecure)由标志或缓存的注册 PIN提供,而不是由[connection.wss]键提供。
优先直连,失败时回退到中继
如果同时为客户端提供直接地址和中继,它会优先使用直接路径,回退到中继,然后重新探测并迁移回直接路径:
zerocode --connect wss://<daemon-host>:9781 \
--relay relay.example.com:8443 --relay-node <node-id>
调优(位于 [connection.wss] 中):
| 键 | 默认 | 含义 |
|---|---|---|
direct_attempts | 2 | 在回退到中继之前尝试直接连接 |
direct_timeout_secs | 3 | 每次尝试直接连接的超时时间 |
reprobe_secs | 30 | 重新探测频率,以迁移回直接模式(0 禁用) |
在仅中继模式下(不使用 --connect/uri),内部 WSS URL 默认为 wss://127.0.0.1:9781,因为内部 mTLS 在守护进程的回环监听器处终止;中继地址仅是 TCP 拨号目标。
配置参考
守护进程 [wss]
| 键 | 默认 | 描述 |
|---|---|---|
enabled | false | 启用双向 TLS WSS 监听器 |
bind | 0.0.0.0 | 绑定地址 |
port | 9781 | 监听端口 |
cert_path | (空) | 自带服务器证书;留空则在 <data_dir>/tls/ 下自动生成 |
key_path | (空) | 自行提供服务器密钥;留空则自动生成 |
守护进程 [wss.client_auth](可选;无论如何,mTLS 始终启用)
| 键 | 默认 | 描述 |
|---|---|---|
enabled | false | 使用自带的 CA;为 false 时,守护进程使用其自动生成的 CA |
ca_cert_path | (空) | 用于验证客户端证书的 PEM CA(BYO 模式) |
pinned_certs | [] | 如果不为空,则仅接受与这些 SHA-256 指纹匹配的客户端证书 |
crl_path | (空) | 已吊销指纹文件;为空时使用由账本生成的 <data_dir>/tls/revoked |
守护进程 [enroll]
| 键 | 默认 | 描述 |
|---|---|---|
enabled | false | 启用注册端点(需要 [wss] + CA 密钥) |
bind | 0.0.0.0 | 绑定地址 |
port | 9782 | 监听端口 |
allow_unpaired_enrollment | (空) | 保留;在无代码客户端信任锚存在之前,将拒绝非空值 |
守护进程 [relay]
| 键 | 默认 | 描述 |
|---|---|---|
enabled | false | 启用中继桥接(需要 [wss]) |
url | (空) | 中继地址 host:port;启用时必填 |
node_id | (空) | 留空时自动生成并持久化一个 128 位 ID;设置后固定使用一个 |
token | (空,机密) | 注册时提供的共享密钥 |
relay_ca_path | (空) | 中继外部证书的 PEM CA;为空时使用公共根证书 |
relay_host | (空) | 预期的外部证书 SAN;留空时从 url 派生 |
relay_insecure | false | 跳过外部证书验证(仅限开发环境) |
tofu | false | 首次使用时,将 relay 叶节点固定到 <data_dir>/relay/relay_pin |
outer_client_cert | (空) | 用于中继准入的 Daemon 外层 mTLS 客户端证书 |
outer_client_key | (空) | outer_client_cert 的密钥 |
node_id_rotation_days | 0 | 每 N 天自动轮换自动生成的 node-id(0 = 从不) |
Relay relay.toml
| Section.key | 默认 | 描述 |
|---|---|---|
bind | 0.0.0.0:8443 | 监听地址(守护进程 + 客户端) |
[tls].cert / .key | (自助预配) | 外部 TLS 身份;两者均省略以自行配置 |
[tls].dir | 数据目录 /tls | 自配置证书的写入位置 |
[tls].sans | [] | 额外 SAN(始终包含 localhost、127.0.0.1) |
[admission].mode | open | open 或 allowlist |
[admission].allow / .deny | [] | 守护进程公钥指纹(拒绝优先) |
[admission].relay_token | (none) | 可选的共享密钥门控 |
[admission].outer_client_auth | off | off / optional / required(外层 mTLS) |
[admission].outer_client_ca | (none) | 外部客户端证书的 PEM CA |
[admission].route_by_client_cert | false | 根据外层证书 CN 的 node-id 进行路由 |
[limits].max_conns_per_node | 256 | 每个 node-id 的并发客户端连接数 |
[limits].idle_timeout_secs | 300 | 在 N 秒后丢弃空闲客户端连接 |
[limits].lease_ttl_secs | 300 | 注册时公布的租约 TTL(v1 中仅供参考:WebSocket 存活状态才是真正的清理规则) |
[limits].accept_burst_per_ip / accept_rate_per_ip | 30 / 10.0 | 每个 IP 的握手令牌桶 |
[limits].connect_burst_per_node / connect_rate_per_node | 60 / 20.0 | 每节点连接令牌桶 |
[limits].max_pending_handshakes | 256 | 已通过 accept 但尚未分类的套接字 |
[limits].handshake_timeout_secs | 10 | TLS、WS 升级和签名注册共用一个截止时间 |
[limits].max_registered_nodes | 1024 | 同时注册的守护进程(N+1 获得 registry_full) |
zerorelay CLI(会覆盖 relay.toml)
--config --bind --tls-cert --tls-key --tls-dir --tls-san(可重复指定)--registration-mode --allow(可重复指定)--deny(可重复指定)--relay-token --max-conns-per-node --idle-timeout-secs --lease-ttl-secs --status-file。子命令:healthcheck [--addr 127.0.0.1:8443]、status --file <path>。
zerocode [connection.wss] 和 CLI
[connection.wss] 键 | 默认 | CLI 覆盖 |
|---|---|---|
uri | (none) | --connect |
relay_url | (none) | --relay(需要 --relay-node) |
relay_node | (none) | --relay-node(需要 --relay) |
direct_attempts | 2 | - |
direct_timeout_secs | 3 | - |
reprobe_secs | 30 | - |
[connection.wss.tls] 键 | 默认 | CLI 覆盖 |
|---|---|---|
ca_cert_path | <config-dir>/tls/ca.crt | --tls-ca-cert |
client_cert_path | <config-dir>/tls/client.crt | --tls-client-cert(需要密钥) |
client_key_path | <config-dir>/tls/client.key | --tls-client-key(需要证书) |
skip_verify | false | --tls-skip-verify |
Relay 的外部信任和注册仅限于 CLI/缓存:--relay-ca --relay-host --relay-insecure --relay-pin --relay-tofu --relay-client-cert --relay-client-key --enroll --enroll-host --enroll-port。
文件布局
守护进程 <data_dir>/:
tls/ca.crt tls/ca.key per-daemon CA (key 0600)
tls/server.crt tls/server.key WSS server leaf (key 0600)
tls/ledger.db issued-cert ledger (SQLite)
tls/revoked revoked fingerprints (handshake-checked)
relay/registration.key Ed25519 relay identity (0600)
relay/node_id auto-minted node-id
relay/relay_pin pinned relay outer-leaf fingerprint (TOFU)
客户端 <config-dir>/:
tls/client.crt tls/client.key client identity (key 0600)
tls/ca.crt pinned daemon CA
tls/profile.json device_id, not_after, cached relay profile
relay/relay_pin relay outer-leaf pin (--relay-tofu)
Relay <tls-dir>/: ca.crt、server.crt、server.key(自行配置)。
升级现有的 issued-cert 账本
tls/ledger.db 中记录了架构版本。新守护进程首次打开由早期版本写入的账本时,会在原位置重建该账本,同时保留每个已签发和已吊销的证书,以及其设备 ID、有效期和审计操作者。重建在单个事务中运行:如果无法完成,守护进程将拒绝启动,并保留原始账本不变,而不会留下迁移了一半的状态;错误信息会指明该文件。无需操作员执行任何操作,已注册设备也无需重新注册。
未交付的证书
守护进程会先将签发记录写入台账,然后才把证书交给注册客户端、续期客户端,或写入操作员的 issue-client-cert 文件。这个顺序是有意为之:如果顺序相反,某人手中可能会有一张由 CA 签名的有效证书,却没有对应的台账记录;而台账从未记录过的证书无法列出或撤销。
代价是,中间发生的故障——例如客户端在响应过程中断开连接或重命名失败——会留下一条针对无人收到的证书的 active 记录。此类记录会单独作为_未交付_记录进行跟踪,并且在其存在时间超过一小时后撤销,时间点取以下各项中最早者:
- 任何新证书的签发或续期,
- 任何注册连接,
- 任何守护进程重启或其他账本打开操作。
这不是后台计时器。完全不执行证书操作的守护进程会将扫描推迟到下一次执行此类操作时;在此之前,证书不会出现在 tls/revoked 中,仍可通过 WSS 握手,但无法续期。任何注册或续期流量——包括身份验证失败的连接——都足以对其进行同步.
经对账的证书会被撤销,而不会删除:该行会保留在账本中,其指纹会像操作员撤销的证书一样写入 tls/revoked,审计主体记录为 reconcile:undelivered,以便在审计轨迹中将两者区分开来。security list-client-certs 只显示 ACTIVE 证书,因此经过对账的证书会从该列表中消失——在事件响应期间,请读取 tls/revoked 或审计日志来查看撤销历史。如果设备报告称它 did 接收到的证书已停止工作,请查找该审计主体——这意味着证书交付记录已丢失,解决方法是重新注册或重新签发。
验证和故障排除
- **守护进程已启动:**查找
0.0.0.0:9781上的 WSS 监听日志,以及使用中继时的node_id日志行。 - **无证书连接被拒绝:**这是 mTLS 平面上的预期行为——请先完成注册(拓扑 1b)。该消息需要采取相应操作,并非卡住。
- Relay 可访问:
zerorelay healthcheck --addr <host>:8443退出码为 0。 - **中继指标:**使用
--status-file <path>运行,然后执行zerorelay status --file <path>(仅计数,绝不包含有效载荷)。 - **撤销丢失的设备:**在守护进程账本中执行撤销会生成
<data_dir>/tls/revoked;证书会在下一次握手时被拒绝,且无法自行续期。 - **注册时 SAS 不匹配:**客户端拒绝持久化证书并中止。不匹配意味着你收到的 CA 不是守护进程的 CA——在重试前调查可能存在的中间人攻击。
scripts/dev/mtls-relay-testbed.sh 中提供了一个自包含的端到端测试工具,它会启动守护进程和中继、签发证书、通过网络完成注册,并测试全部三种拓扑;请将其作为一个完整示例来阅读。