セキュアトランスポート: エンドツーエンド構成
このページは、次の3つのトポロジでクライアントをデーモンに安全に接続するための完全な設定リファレンスです:
- クライアントからデーモンへ直接接続 - 相互TLS対応WSS、リレーなし。
- デーモンからリレーへ - デーモンは、指定されたリレーへのアウトバウンドブリッジを維持することで、NAT/CGNAT の背後からも到達可能になります。
- クライアントからリレー経由でデーモンへ - クライアントはそのリレー を介して デーモンに接続しますが、実際のクライアント<->デーモン間の mTLS は引き続きデーモンで終端します。
60秒で始めるクイックスタートについては、リモートセットアップ(WSS)を参照してください。このページでは、各設定項目について詳しく説明します。
メンタルモデル: 2つのエンベロープ、1つの信頼境界
TLS レイヤーは 2 つあり、そのうちセキュリティ境界となるのは 1 つだけです:
- 内側の mTLS(実質的な境界)。 TLS 1.3 のみを使用し、相互認証を行います。クライアントはデーモンが発行した証明書を提示し、デーモンはサーバーのリーフ証明書を提示します。これは RPC プレーンです。このプレーンにはサーバーのみの認証、または未認証の経路は存在しません。クライアント証明書は常に必要です。証明書が認可するのは_デバイス_であり、RPC の資格情報ではありません。リモートの
initializeはすべて、_プリンシパル_を識別する bearerauth_tokenも併せて送信する必要があります。送信しない場合、デーモンはAUTH_REQUIRED(-32010)で拒否します。Bearer token を参照してください。 - 外側の TLS(メタデータの境界)。 リレーが経路上にある場合、リレーは外側の TLS + WebSocket セッションを終端し、不透明な暗号文を転送します。内部 RPC を読み取れる鍵を保持することは決してありません。直接トポロジーでは外側のレイヤーはありません。
デフォルトポート(すべて設定可能):
| 平面 | デフォルト | 設定 |
|---|---|---|
| デーモン WSS(内部 mTLS RPC) | 9781 | [wss].port |
| デーモン登録エンドポイント | 9782 | [enroll].port |
| Relay(外側 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 での初回起動時に、デーモンはディレクトリモード 0700 の <data_dir>/tls/ 配下に次のファイルを書き込みます:
| ファイル | 目的 | モード |
|---|---|---|
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 で起動したことを示すログ行が表示されるはずです。
クライアントに証明書が必要になりました。取得する方法は 2 つあります。
1b. クライアント側 - オプション A: 登録(推奨)
登録により、証明書を持たないクライアントは、ペアリングでアクセスが制限されたサーバー認証済みエンドポイントを介して、証明書を手動で扱うことなく最初の証明書を取得できます。有効にするには:
[enroll]
enabled = true
# bind = "0.0.0.0" # デフォルト
# port = 9782 # デフォルト
# [wss] の enabled とデーモン CA キー(上記で自動生成されるもの、または BYO+key)が必要です。
# CA キーがない場合、エンドポイントはフェイルクローズし、証明書は
# 帯域外で用意する必要があります。
デーモンは起動時に、ペアリングコードとshort-auth-string (SAS)をコンソールまたはログに一度だけ出力します。このコードは1回しか使用できず、発行されてから10分後に有効期限が切れます。証明書発行に使用できる唯一のベアラー認証情報であり、コンソールやログに出力されるため、コピーされたコードがオペレーターによる使用後すぐに機能しなくなる必要があります。期限切れのコードは拒否されて消去されます。新しいコードが必要になったら、ゲートウェイのペアリング API から交換用コードを発行してください。その後、ワークステーションで次の操作を行います。
# インタラクティブ: 証明書なしのクライアントは初回接続時に自動登録されます。
zerocode --connect wss://<remote-host>:9781
# または明示的に / 非対話的に:
zerocode --enroll --connect wss://<remote-host>:9781
zerocode はペアリングコードの入力を求め、P-256 鍵と CSR をローカルで生成し(秘密鍵がデバイスから外に出ることはありません)、SAS を表示します。SAS がデーモンによって出力されたものと一致することを確認してください(これにより中間者 CA を検出できます)。また、<config-dir>/tls/ 配下に次をキャッシュします:
| ファイル | 目的 | モード |
|---|---|---|
client.crt | 発行済みクライアント証明書 | デフォルトの umask |
client.key | クライアント秘密鍵(ローカルで生成) | 0600 |
ca.crt | RPC プレーン用にピン留めされたデーモンの CA チェーン | デフォルトの umask |
profile.json | キャッシュされた device_id、not_after、リレープロファイル | デフォルトの umask |
後続の実行では、再登録を行わずにキャッシュされた証明書を再利用します。証明書は有効期間の約50%(約15日)に達すると、稼働中のmTLSセッション上で自動的に更新されます。失効した証明書は自己更新できません。
登録で発行されるのは証明書のみで、ベアラートークンは発行されません。デーモンがセッションを受け入れる前に、ゲートウェイとペアリングして zc_... トークンを取得し、ZEROCLAW_AUTH_TOKEN、auth_token_file、auth_token のいずれかを使って zerocode に渡してください(Bearer トークン を参照)。トークンを設定すると、zerocode --connect wss://<remote-host>:9781(設定に uri がある場合は単に zerocode)だけで接続でき、追加のフラグは不要です。
登録エンドポイントのデフォルト: --enroll-host は --connect のホストをデフォルトとし、--enroll-port のデフォルトは 9782 です。
初回リリースでは、意図的にすべての登録でペアリングコードを必須とします。予約済みの allow_unpaired_enrollment 設定は、クライアントがピン留めされたデーモン CA フィンガープリントなど、コード不要の明示的な信頼アンカーを持つようになるまで、デーモンの起動時に拒否されます。これにより、暫定的な登録 TLS パスが盲目的な trust-on-first-use に変わるのを防ぎます。
1c. クライアント側 - オプション 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/ に 3 つのファイルを 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"
zerocode は依然としてベアラートークンが必要です。トークンは ZEROCLAW_AUTH_TOKEN から、または [connection.wss] 配下の auth_token_file / auth_token から取得されます。
証明書を持たないクライアントが登録せずに WSS プレーンへ接続した場合、対処可能な「先に enroll してください」というメッセージが表示され(デーモンは拒否された未移行クライアントをログに記録します)、サイレントハングが発生することはありません。
--tls-skip-verifyは、自己署名の開発用デーモンに対する サーバー 検証を緩和するだけであり、クライアント証明書は引き続き必須です。
トポロジー 2: デーモンからリレーへ
daemon ====== outbound: register + bridge ======> relay :8443
[relay] (blind forwarder)
デーモンはリレーに接続し、固定された Ed25519 ID を証明して node-id を登録します。クライアントは後でその node-id に接続します(Topology 3)。リレーが転送するのは暗号文だけです。
2a. リレー (zerorelay) を実行する
relay.toml(apps/zerorelay/relay.example.tomlを参照)で設定します。すべてのCLIフラグは、対応するファイルの値を上書きします。[admission]セクションはSIGHUPでホットリロードされます。明示的なオプトインなしにパブリックリレーをオープンでトークン不要の受け入れに変更するリロードは拒否され、以前のポリシーが引き続き有効になります。
# relay.toml
bind = "0.0.0.0:8443"
[tls]
# cert/key を省略すると、初回起動時に dir へ外側の TLS 証明書を自動生成します(openssl は不要)。
# sans にはリレーの公開ホスト名または IP アドレスを設定してください。
dir = "/data/tls"
sans = ["relay.example.com"]
# 独自の証明書を使う場合(例:パブリック CA 発行の証明書):
# cert = "/etc/zerorelay/fullchain.pem"
# key = "/etc/zerorelay/privkey.pem"
[admission]
# "open" は署名済みのすべての daemon を受け入れます(拒否リストの対象を除く)。"allowlist" は
# 登録された daemon の公開鍵フィンガープリントのみを受け入れます。拒否リストは常に優先されます。
mode = "open"
allow = []
deny = []
# 公開(ループバック以外)のリレーは登録を制限する必要があります。共有シークレットをここに設定するか
# (各 daemon は [relay] token で提示します)、mode = "allowlist" を使用してください。
# `zeroclaw relay claim` で登録された daemon を提供するリレーは、mode = "allowlist" を使用し、
# relay_token を未設定のままにする必要があります(2d を参照)。
# そうしないと、公開バインドのオープンでトークンなしのリレーは起動を拒否します。インターネット上の
# 任意の daemon が登録し、未請求のノードIDを占有できてしまうためです。(ローカル開発用のループバック
# バインドは例外です。意図的にオープンな公開リレーとする場合は 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
# 同様に、フラグのみ(パブリックバインドにはトークンまたは許可リストが必要で、そうでなければその
# 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 16 進数)を基準にします。フィンガープリントを allow に追加し、kill -HUP <pid> で再読み込みします。node-id は最初の登録者の公開鍵にバインドされるため、別の鍵で稼働中の node-id を乗っ取ることはできません(node_taken になります)。
リレーはすべてのモードで、許可リストより前に relay_token を確認します。したがって、セルフサービスのクレーム(2d)を提供するリレーは、relay_token を未設定にした状態で allowlist を使用する必要があります。クレームによって共有トークンが配布されることはないため、クレーム済みのデーモンは forbidden: bad relay token で拒否されます。公開リレーは許可リストだけで既に制限されているため、そこではトークンは何の効果も加えません。
Docker. apps/zerorelay/Dockerfile は distroless で実行され、CMD ["--config", "/etc/zerorelay/relay.toml"] とシェルを使用しない 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 と一致する必要があります
# リレーの外側の証明書の信頼設定 - 1つを選択:
relay_ca_path = "/path/to/relay/ca.crt" # リレーの(自己署名)CAを信頼する
# tofu = true # または、初回使用時にリレーのリーフ証明書をピン留めする
# relay_insecure = true # または、外側の検証をスキップする(開発時のみ)
# (パブリックCAを使用するリレーの場合は、組み込みのパブリックルートを使用するため、3つすべて未設定のままにする)
[relay] には [wss] の有効化が必要です(リレーは 127.0.0.1:<wss.port> にブリッジします)。url が空の場合はフェイルクローズします。デーモンは起動時に、ヒント “クライアントにはこれを –relay-node として渡してください” とともに node-id をログに出力します。<data_dir>/relay/node_id から読み取ることもできます。デーモンの固定された登録キーは <data_dir>/relay/registration.key (0600) に作成されます。
外側証明書の信頼優先順位(高い順):relay_insecure > relay_ca_path > <data_dir>/relay/relay_pin に保存されたピン(明示的または TOFU) > tofu > パブリックルート。したがって、CA を設定すると、古いピンが削除されずに置き換えられます。tofu = true の場合、観測されたリレーのリーフ証明書のフィンガープリントが <data_dir>/relay/relay_pin にピン留めされ、登録時に同じピンがクライアントに渡されるため、クライアントは同一のリーフ証明書をピン留めします。
2c.(オプション)node-id のローテーションと外部 mTLS
[relay]
node_id_rotation_days = 30 # auto-rotate the auto-minted id every N days (0 = never)
ローテーションでは新しい ID を発行し、進行中のクライアントが切断されないよう 10 分間の猶予期間中は古い ID と併用してから、古い ID を廃止します。新しい ID は、クライアントの次回の証明書更新時にインバンドでクライアントに届きます。zeroclaw security relay-rotate-node-id を使用して今すぐ 1 つ強制発行できます(auto-mint モードのみ。固定された node_id はローテーションされません)。
外側のレイヤーでもデーモンを認証するリレーの場合は、リレー側で[admission].outer_client_auth = "required" + outer_client_caを設定し、デーモン側で[relay].outer_client_cert / outer_client_keyを設定します。これは外側の TLS に追加されるもので、内側の mTLS には一切影響しません。
2d. zeroclaw relay claim によるセルフサービス登録
リレーをZeroRelayコントロールプレーンと併用する場合、オペレーターは、リレーのallow listを手動で編集する代わりに、ワンタイムのclaim tokenを使ってデーモンを登録できます。コントロールプレーンはtokenを発行し、デーモンホストで実行するコマンドを表示します:
zeroclaw relay claim <TOKEN> --control https://control.example.com
デーモンは既存の登録キーでクレームに署名するため、コントロールプレーンが許可リストに登録するフィンガープリントは、デーモンが登録に使用するキーと完全に一致します。検証に成功すると、コントロールプレーンはそのフィンガープリントをリレーの許可リストに追加し、リレーを再読み込みします。その後デーモンは [relay] の enabled、url、node_id、relay_host を書き込みます。登録するには、デーモンを起動または再起動してください。
請求する前に:
- リレーは指紋だけで接続を許可しなければなりません。
mode = "allowlist"を使用し、relay_tokenは未設定のままにしてください(2a を参照)。クレームでは共有トークンは配信されません。 [wss]を有効にしてください。 WSSリスナーが有効になるまで、リレーは登録を拒否します。WSSリスナーを有効にせずに行われたクレームも保存されますが、[wss]を有効にした後の次回起動時に反映されます。- リレーの外側の証明書に対する信頼を、まず設定してください(手順2bのとおり:
relay_ca_path、tofu、またはパブリックルート)。この要求では上記の4つのフィールドのみを書き込み、[relay]の残りの部分には手を加えません。 - claim が管理するフィールドを環境変数による上書きで設定しないでください。
relay.enabled、relay.url、relay.node_id、またはrelay.relay_hostが上書きされている場合、上書きによって書き込まれた値が破棄されるため、claim は何も送信せずに拒否します。
--control は https、またはループバックアドレス上の http である必要があります。リダイレクトとプロキシは無効化されており、トークンが別のオリジンに送信されることはありません。すべての拒否では no config was written と表示されます。トークンは一度しか使えません。コントロールプレーンがトークンを受け入れると、その後デーモンがレスポンスを拒否した場合(例: 使用できないリレーアドレス)でも、トークンは使用済みとなります。その場合は原因を修正し、新しいトークンを発行してください。
トポロジー 3: クライアントからリレーを経由してデーモンへ
zerocode ==outer TLS+WS==> relay ==forwards ciphertext==> daemon
\________________ inner mutual-TLS (TLS 1.3) terminates here _______________/
これはトポロジー 1 と 2 を組み合わせたものです。クライアントには、内側のクライアント証明書(1b と同様の enroll)およびリレーの接続情報(アドレス、node-id、リレーの外側の証明書に対する信頼)が必要です。
3a. 簡単な方法: 登録にリレープロファイルを含める
デーモンに [relay] が設定されている場合、その登録応答には リレープロファイル(relay_url、node_id、およびリレーのリーフ relay_cert_pin)が含まれます。つまり、1 回の登録ですべてがプロビジョニングされます。
zerocode --enroll --connect wss://<daemon-host>:9781
zerocode は内部証明書 と リレープロファイルを <config-dir>/tls/profile.json にキャッシュします。その後、通常の zerocode はフラグなしでリレー経由でデーモンに接続します。リレーのアドレス、node-id、ピンはすでに把握しています。
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
リレーの外部証明書について、デーモンと同様に、信頼モードを必ず1つだけ選択します。
| フラグ | 意味 |
|---|---|
--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)は、[connection.wss]のキーではなく、フラグまたはキャッシュされた登録ピンによって指定されます。
3c. 直接接続を優先し、失敗時はリレーにフォールバック
クライアントに直接アドレスとリレーの 両方 を指定すると、直接経路を優先し、リレーにフォールバックした後、再プローブして直接経路に戻ります:
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 のダイヤル先にすぎません。
3d. リレーのフロントドア経由のブラウザ登録(オプトイン、登録のみ)
リレーはブラウザ登録ページ(「frontdoor」)を提供できます。リレーのURLを開き、ノードIDとペアリングコードを入力し、ショートオース文字列をデーモンのコンソールと照合すると、ブラウザにクライアント証明書が発行されます。
enrolは登録のみです。 ブラウザセッションの階層はありません。登録が完了すると、ページは証明書と秘密鍵を提供し、zerocode の利用を案内します。デーモンのRPCプレーンはmTLSを必要としますが、ブラウザはネイティブのWebSocket APIを通じてmTLSを提示できません。そのため、ブラウザセッションを実現するには、ページのJavaScriptにTLS実装を組み込むか(意図的に出荷していません。下記を参照)、デーモン側でリレー終端型のセッションモードを用意する必要があります。これは欠けている機能ではなく、未解決の設計課題です。
デフォルトではオフになっています。これは、使用するブラウザに対してブラインド転送の保証を2つの異なる点で狭めてしまうためです。
- リレーは信頼されたコードの配信元です。 ここに登録するブラウザは、リレーから配信される登録用JavaScriptを実行します。侵害されたリレーは、ページが扱う情報を漏えいさせるコードにすり替えることができます。
- リレーは登録における当事者です。 デーモンの登録エンドポイントはTLSで通信し、リレーはそのバイト列を復号せずに転送します。そのため、ブラウザは自力でこのやり取りを実行できません。リレーがブラウザの代わりにこれを実行します。まずデーモンのCAを取得し、次にCAでピン留めした
POST /enrollを、ペアリングコードを添えて送信します。リレーはペアリングコードと発行された証明書を参照でき、観測したペアリングコードを使って自身のクライアントを登録することも可能です。
リレーが受け取らないのは秘密鍵です。ブラウザが WebCrypto で鍵ペアを生成し、送信するのは CSR のみです。
short-auth-string は今も検出器として機能します。異なる daemon CA に差し替える中継があれば、生成される SAS は daemon コンソールの値と一致せず、一致しない場合は停止してください。信頼することを選んだ中継からは守れません。frontdoor を経由したブラウザでの登録は中継で終端され、エンドツーエンドではありません。
このフロントドアの以前のイテレーションでは、ブラウザがデーモンと直接通信できるように、リレーが配信するJavaScriptに手書きのTLS 1.3およびX.509クライアントを組み込んでいました。それは移植ではなく削除しました。監査されていないTLSスタックを配信されるJavaScriptに置くことは、正直に開示されたリレー終端方式よりも信頼性の面で劣りますし、いずれにせよ信頼されたコードの配信元という問題は解決されないためです。
絞り込みには範囲の上限があります。すべてのモードにおいて、RPCプレーンはリレーを越えてもエンドツーエンドのmTLSのままです。また、zerocode/ネイティブ登録は、リレーが提供するコードを決して実行せず、リレーで終端されることも決してありません。リレーを完全に信頼しない状態に保つ必要がある場合は、それら(1b/1c/3a節)を使用してください。
# relay.toml
[frontdoor]
enabled = true # default false; read the trust note above first
無効化されている場合、リレーへの通常の HTTP リクエストには 404 が返され、ブラウザからはリレー経由で登録できません。デーモンの登録、zerocode クライアント、トンネル化された RPC プレーンには影響しません。登録ページ自体にも同じ信頼に関する注記が表示され、フロントドアが有効な間は zerorelay が起動時に警告をログに出力します。
事前入力された登録リンク
スマートフォンのユーザーがノードIDを長々と入力しなくて済むよう、リンクにノードIDとペアリングコードを含めることができます。ページは読み込まれると両方の入力欄に自動で値を入力します:
https://relay.example.com/#node=<node-id>&code=<pairing-code>
https://relay.example.com/?node=<node-id>&code=<pairing-code>
# 形式の使用を推奨します。URL のフラグメントはどのサーバーにも送信されないため、ペアリングコードがリバースプロキシや CDN のアクセスログに残りません。? 形式も使用できます。リレー自体はルーティングの前にクエリ文字列を破棄し、ログへの記録も応答への反映も行いません。また、ページは cache-control: no-store と referrer-policy: no-referrer を付けて配信されます。
このページはフィールドに値を入力するだけです。エージェントCAの取得や送信は行いません。ユーザーは引き続き エージェントCAを取得 を押し、short-auth-string を確認する必要があります。node-id または pairing-code の形式に一致しない値は無視されます。両方のパラメータは、ページがそれらを読み取った時点でアドレスバーと現在の履歴エントリから削除されるため、コピーされたURLやブックマークされたURLにコードは残りません。
ブラウザは、最初に開かれたリンクを、そのリンクの形式(? 形式でも # 形式でも Chrome は記録します)のまま自身の履歴データベースに保存することがあります。これはどのページにも書き換えられません。そのコピーが無害である理由はコード自体にあります。コードは最初に登録が成功した時点で消費され、発行から10分後に失効します。つまり保存されたリンクは使用済みか失効済みのどちらかです。未使用のコードを含むリンクは、コードそのものと同様に扱ってください。また、コードを発行したら、なるべく速やかに登録することを推奨します。
設定リファレンス
デーモン [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 | BYO 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 | (空) | 予約済みです。no-code クライアントのトラストアンカーが存在するまで、空でない値は拒否されます。 |
デーモン [relay]
| キー | デフォルト | 説明 |
|---|---|---|
enabled | false | リレーブリッジを有効にする([wss] が必要) |
url | (空) | リレーアドレス host:port; 有効時に必須 |
node_id | (空) | 空の場合、128 ビット ID を自動生成して永続化します。1 つを固定するには設定します。 |
token | (空、シークレット) | 登録時に提示された共有シークレット |
relay_ca_path | (空) | リレーの外側の証明書用の PEM CA。空の場合は公開ルートを使用します |
relay_host | (空) | 外側の証明書の SAN を想定。空の場合は url から導出されます |
relay_insecure | false | 外側の証明書の検証をスキップ(開発環境のみ) |
tofu | false | 初回使用時にリレーリーフを <data_dir>/relay/relay_pin にピン留めします |
outer_client_cert | (空) | リレー受け入れ用の Daemon の外部 mTLS クライアント証明書 |
outer_client_key | (空) | outer_client_cert のキー |
node_id_rotation_days | 0 | 自動発行された node-id を N 日ごとに自動ローテーション(0 = 実行しない) |
Relay relay.toml
| Section.key | デフォルト | 説明 |
|---|---|---|
bind | 0.0.0.0:8443 | リッスンアドレス(デーモン + クライアント) |
[tls].cert / .key | (セルフプロビジョニング) | 外側 TLS ID; 両方を省略すると自己プロビジョニング |
[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 | ノードごとの接続トークンバケット |
[frontdoor].enabled | false | ブラウザ登録ページを配信する(3dを参照:リレーを信頼されたコードのオリジンであり、かつブラウザ登録におけるプリンシパルにもする) |
[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 --frontdoor(オプトイン。信頼に関する影響については3dを参照)。サブコマンド: 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 | - |
auth_token | (none) | ZEROCLAW_AUTH_TOKEN 環境変数(両方のトークンキーより優先) |
auth_token_file | (none) | - (auth_tokenより優先; 所有者限定である必要があります) |
auth_provider | native(ペアリングトークン) | - (例: oidc.<alias>) |
[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(自己プロビジョニング済み)。
既存の発行済み証明書台帳のアップグレード
tls/ledger.db にはスキーマバージョンが記録されています。以前のリビジョンで書き込まれた台帳は、新しいデーモンが初めて開く際にその場で再構築され、発行済みおよび失効済みのすべての証明書と、それぞれのデバイス ID、有効期間、監査主体が保持されます。再構築は単一のトランザクションで実行されます。完了できない場合、デーモンは起動を拒否し、移行途中の状態にするのではなく元の台帳を変更せずに残します。また、エラーにはそのファイル名が示されます。オペレーターによる操作は不要で、登録済みのデバイスを再登録する必要もありません。
未配信の証明書
デーモンは、登録クライアント、更新クライアント、またはオペレーターの issue-client-cert ファイルに証明書を引き渡す 前に、発行を台帳に記録します。その順序は意図的です。別の順序にすると、台帳に行がないまま、CA 署名済みの有効な証明書が誰かの手元に残る可能性があり、台帳が存在を把握していない証明書は一覧表示も失効もできません。
その代償として、途中で障害が発生すると(応答の途中で切断するクライアントや、rename の失敗など)、誰も受け取っていない証明書の active 行が残ります。このような行は undelivered として別途追跡され、1時間を超えた時点で失効します。次のうち最初に発生した時点で:
- 証明書の新規発行または更新、
- 任意の登録接続、
- デーモンの再起動やその他の台帳のオープン。
これはバックグラウンドタイマーではありません。証明書処理をまったく行っていないデーモンでは、次にその処理を行うまでスイープが延期されます。それまでは証明書は tls/revoked の外に残り、更新はできないものの、WSS ハンドシェイクには引き続き通過します。認証に失敗する接続を含め、登録または更新のトラフィックがあれば、それを整合させるのに十分です。
照合済みの証明書は取り消され、削除されることはありません。台帳の行は残り、フィンガープリントはオペレーターによる取り消しの場合とまったく同様に tls/revoked に登録されます。監査アクターには reconcile:undelivered が設定されるため、監査証跡で両者を区別できます。security list-client-certs は ACTIVE の証明書のみを表示するため、照合済みの証明書はその一覧から表示されなくなります - インシデント対応中は、取り消し履歴を確認するために tls/revoked または監査ログを参照してください。デバイスが、_実際に_受け取った証明書が機能しなくなったと報告した場合は、そのアクターを探してください - これは配布記録が失われたことを意味するため、再登録または再発行が必要です。
検証とトラブルシューティング
- デーモン起動済み:
0.0.0.0:9781の WSS リスナーログを確認し、リレーを使用している場合はnode_idのログ行も確認します。 - 証明書なしの接続が拒否されました: mTLS プレーンでは想定される動作です - 先に登録してください(トポロジー 1b)。このメッセージは対処可能であり、ハングではありません。
- Relay に到達可能:
zerorelay healthcheck --addr <host>:8443は終了コード 0 で終了します。 - Relay メトリクス:
--status-file <path>を指定して実行し、その後zerorelay status --file <path>を実行します(カウントのみで、ペイロードは一切含まれません)。 - 紛失したデバイスを失効させる: デーモンの台帳で失効処理を行うと
<data_dir>/tls/revokedが生成されます。証明書は次回のハンドシェイクで拒否され、自動更新できなくなります。 - 登録時のSAS不一致: クライアントは証明書の永続化を拒否して中止します。不一致は、受け取ったCAがデーモンのものではないことを意味します。再試行する前に、中間者攻撃の可能性を調査してください。
scripts/dev/mtls-relay-testbed.sh に、デーモン + リレーを起動し、証明書を発行し、ネットワーク経由で登録を行い、3 つすべてのトポロジを検証する自己完結型のエンドツーエンド・ハーネスがあります。実例として参照してください。