Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

edgesentry-audit

エッジインフラへの信頼と検証

なぜこのプロジェクトが必要か

近年、インフラ運営における人手不足が深刻な課題となっています。建設業などの労働集約型産業では、遠隔点検のために IoT デバイスの導入が進んでいます。

一方で、デバイスのなりすまし・乗っ取り・点検データの改ざんが発生すると、システム全体への信頼は根底から損なわれます。そのため、デバイスの真正性とデータの完全性を継続的に検証することが不可欠です。

ビジョンと原則

EdgeSentry-Audit は 初期段階の学習プロジェクト です。 IoT セキュリティ技術への理解を深めるために実践的に構築しています。ライセンスは商用互換( MIT/Apache 2.0 )ですが、実装はまだ始まったばかりであり、プロダクション環境への導入は想定していません。 DuckDB のような成功した「インプロセス」システムのガバナンスモデルに倣い、コアとなる知的財産をオープンかつベンダーニュートラルに保つことで、時間をかけて公共財として成長させることを目指しています。

私たちのゴールは、公共インフラ・海事( MPA )・スマートビルディング( BCA )分野のベンダーにとっての 共通トラストレイヤー となり、シンガポールの CLS レベル 3/4iM8 、そして日本の 統一政府標準 を含む最高水準の規制要件を満たす支援をすることです。

信頼のインフラは単一の民間企業が独占すべきではないと考えています。

  • すべてに開放: ベンダー非依存のリファレンス実装として、企業がコンプライアンス達成に要するコストを引き下げる。
  • 業界横断の学習: 企業の垣根を超えてエンジニアが協力し、グローバル IoT セキュリティ標準の複雑さを習得する。
  • 持続的な成長: コアはコミュニティ主導のリファレンス実装に留め、商用サービス(高度な分析・自動コンプライアンスレポート)はこの安定した基盤の上に構築する。

段階的なコンプライアンス計画についてはロードマップを参照してください。

初期スコープ

公共インフラ向け IoT 導入においては、シンガポールのサイバーセキュリティラベリングスキーム(CLS)レベル 3 およびレベル 4 がハードウェアレベルのセキュリティ要件を導入します。EdgeSentry-Audit はハードウェア拡張を通じてこれらの要件をサポートするよう設計されています。ハードウェアセキュリティ自体はハードウェア側で実装され、本ライブラリはソフトウェアインテグレーション層を提供します。初期スコープでは改ざん防止と改ざん検知可能な監査レコードをカバーし、ハードウェアレベルの拡張ポイントを最初から組み込んでいます。

どのように実現するか

「シンプル・ポータブル・高速」という哲学をモデルに、 EdgeSentry-Audit は高性能な組み込みを念頭に置き、 Rust で 3 つの信頼の柱を実装しています。

  1. 同一性 — Ed25519 デジタル署名により、デバイスとデータ双方の真正性を保証します。 C/C++ FFI を中核に持つ設計により、レガシーな産業システムやロボティクスプラットフォームが全面的な書き直しなしにセキュアな同一性を導入できます。

  2. 完全性 — BLAKE3 ハッシュチェーンによりデータの不変性を保証します。ローカルまたはクラウドで検証可能な暗号証明レコードを提供し、オフライン環境においてもフォレンジックの準備を確保します。

  3. 回復力 — オフラインバッファリング(OfflineBufferInMemoryBufferStore および buffer-sqlite フィーチャーによる SQLite)はフェーズ 1 で提供済みであり、CLS-09 を満足しています。帯域幅が限られた環境向けのインテリジェントなデータ要約(フェーズ 2 (計画中))では、限られた回線上での優先キューイングが追加される予定です。ロードマップを参照してください。

edgesentry-audit はクレート名です。 Rust ライブラリは edgesentry_audit(アンダースコア)としてインポートされます。監査レコードの全型定義・ハッシュ・署名検証・チェーン検証・インジェスト時検証・重複排除・シーケンス検証・永続化ワークフロー・ CLI がすべて含まれています。

ライセンス

このプロジェクトは以下のいずれかのライセンスの下で提供されています。

いずれかをお選びください。

ロードマップ

EdgeSentry-RS は段階的なアプローチを採用しています。まずシンガポールのコンプライアンスベースライン( CLS レベル 2 → レベル 3 、 SS 711:2025 )を確立し、次に GCLI 相互承認を通じて日本( JC-STAR ・サイバートラストマーク)へ展開し、最終的に EU ・英国・重要インフラ市場へのグローバルコンバージェンスを目指します。これは DuckDB モデルを踏襲したアプローチです。すなわち、ロックインではなくエコシステムへの採用を通じてデファクトスタンダードとなる、埋め込み可能な OSS コアを構築します。

なぜシンガポールを最初に選ぶのか

シンガポールの CLS は欧州の ETSI EN 303 645 標準を直接的な基礎としています。日本の JC-STAR も同様に ETSI EN 303 645 を技術的な根拠として参照しています。つまり、 3 つの規制体制は共通の基盤を持っています。

標準地域根拠
ETSI EN 303 645欧州 (CRA)オリジナル
CLS レベル 2/3/4シンガポールETSI EN 303 645
JC-STAR日本ETSI EN 303 645

シンガポール CLS への対応を最初に実装することで、技術的な作業の大部分が日本の JC-STAR および欧州の CRA 要件を直接満たすことになります。シンガポールは単なる地域ターゲットではなく、グローバルなコンプライアンス対応への最短経路です。

GCLI と日本シンガポール間の直接 MoC

日本は 2025 年に Global Cyber Labelling Initiative (GCLI) に署名し、シンガポール・英国・フィンランド・ドイツ・韓国など 11 カ国に加わりました。 GCLI は各国の IoT セキュリティラベル間で相互承認を確立する多国間の枠組みであり、シンガポール CLS 認証取得製品は再認証なしで日本の JC-STAR 準拠として認められます。これが「シンガポール優先」戦略を日本市場参入パスとして機能させる構造的な仕組みです。

2026 年 3 月、日本とシンガポールはこれをさらに強化する形で、 METI/IPA (日本)と CSA (シンガポール)の間で JC-STAR と CLS の直接相互承認に関する協力覚書( MoC ) に署名しました。この MoC は 2026 年 6 月 1 日に発効 します。この枠組みのもとでは、有効な JC-STAR ラベルはそのまま CLS として認められます。 JC-STAR の認証データを CLS 要件に再マッピングする必要はありません。日本はシンガポール CLS との二国間相互承認を達成した 5 番目の国となりました(フィンランド・ドイツ・韓国・英国に次ぐ)。

未解決の問題: JC-STAR の各レベル( STAR-1 〜 STAR-4 )と CLS スターレベル( 1 〜 4 )の公式な等価対応表は、 CSA/METI によってまだ公表されていません。どの JC-STAR レベルが特定の CLS 目標レベルを満たすかを判断するために、 CSA の CLS ページおよび METI/IPA の JC-STAR ページを継続的に確認してください。

シンガポール CLS とフィンランド・ドイツ・韓国の間には、さらに二国間 MRA (相互承認協定)も存在します。ドイツや韓国の IoT 認証を取得済みの日本のお客様にとっては、これらの MRA が CLS 取得の近道となります。

SS 711:2025 設計原則

シンガポールの国内 IoT 標準 SS 711:2025 ( TR 64:2018 に替わるもので、 CLS レベル 3 評価の基盤となる)は、 4 つのセキュリティ設計原則を定義しています。 EdgeSentry-RS はこれらを中心に設計されています。

原則要件実装
セキュア・バイ・デフォルト一意のデバイス同一性、署名付き OTAidentity.rs( Ed25519 )、update.rs(署名付きアップデート検証)
防御の厳格性STRIDE の脅威モデリング、改ざん検知integrity.rs( BLAKE3 ハッシュチェーン)、 STRIDE 脅威モデルアーティファクト
アカウンタビリティ監査証跡、操作ログingest/( AuditLedger 、 OperationLog 、 IntegrityPolicyGate )
回復力デフォルト拒否のネットワーキング、レート制限ingest/network_policy.rs( IP/CIDR アローリスト)

実装マッピング

CLS / ETSI EN 303 645 / JC-STAR 要件とソースコードの詳細な条項別マッピングは、コンプライアンス・トレーサビリティマトリックスを参照してください。


フェーズ 1 :シンガポールゲートウェイ(現在〜 6 ヶ月)

目標: CLS レベル 2 → レベル 3 、 SS 711:2025 、 iM8

IMDA の評価担当者が求める SDL エビデンスアーティファクト(脅威モデル・ SBOM ・バイナリ分析)を備え、シンガポール CLS レベル 2 のサイバーハイジーン要件を満たしつつレベル 3 へ進展するソフトウェアリファレンス実装を提供します。

マイルストーン 1.1 :同一性&完全性コア ✅ 実装済み

  • edgesentry_rs::identity — Ed25519 デバイス署名の実装
  • edgesentry_rs::integrity — BLAKE3 ハッシュチェーンによる改ざん検知プロトコル
  • edgesentry_rs::ingest::NetworkPolicy — デフォルト拒否の IP/CIDR アローリスト( CLS-06 )

マイルストーン 1.2 : C/C++ブリッジ ✅ 実装済み

  • edgesentry-bridge — Ed25519 署名・署名検証・ハッシュチェーン検証を C/C++プロジェクトに公開する C 互換 FFI レイヤー
  • 目的: 最小限の変更で既存の日本製ハードウェア(ゲートウェイ・センサー)にシンガポール水準のセキュリティを組み込む
  • 使い方・リンク方法・メモリ安全規約についてはC/C++ FFI ブリッジを参照してください

マイルストーン 1.3 :コンプライアンスマッピング v1.0 ✅ 実装済み

マイルストーン 1.4 : SBOM +ベンダー開示チェックリスト ✅ 実装済み

IMDA の IoT サイバーセキュリティガイドは、 CLS レベル 3 評価エビデンスとしてベンダー開示チェックリストを必須としています。 5 つの必須カテゴリは、暗号化サポート・識別と認証・データ保護・ネットワーク保護・ライフサイクルサポート( SBOM )です。

  • CycloneDX JSON SBOM をすべてのクレートに対して生成し、各 GitHub Release と共に公開
  • 5 つの全カテゴリのベンダー開示チェックリスト回答を文書化
  • チェックリスト回答を既存実装にトレーサビリティマトリックスでマッピング
  • SBOM とベンダー開示および #92を参照してください

マイルストーン 1.5 :トランスポート層・非同期インジェスト・オフラインバッファ ✅ 実装済み

  • async-ingest フィーチャー:AsyncIngestService<R,L,O>Arcによる安全なマルチタスク共有) — #115 クローズ
  • transport-http フィーチャー:axum ベースのPOST /api/v1/ingestエンドポイント;暗号検証前にNetworkPolicyでソース IP をゲート;eds serve CLI — #116 クローズ
  • transport-tls フィーチャー:rustls TLS 1.2/1.3 によるserve_tls()eds serve-tls --tls-cert / --tls-key CLI;CLS-05 HTTP チャネル機密性を満足 — #176 クローズ
  • transport-mqtt-tls フィーチャー:CA 証明書パスを持つ MqttTlsConfigrumqttc::TlsConfiguration::Rustls 経由の rustls ClientConfigeds serve-mqtt --tls-ca-cert CLI;CLS-05 MQTT チャネル機密性を満足 — #180 クローズ
  • transport-mqtt フィーチャー:serve_mqtt()が設定可能なトピックをサブスクライブし、AsyncIngestServiceにルーティング;<topic>/responseに承認/拒否を公開;eds serve-mqtt CLI — #146 クローズ
  • buffer モジュール:プラグ可能なBufferStoreトレイトを持つOfflineBuffer<S>InMemoryBufferStoreデフォルト;buffer-sqliteフィーチャー配下のSqliteBufferStore;CLS-09 回復力を満足 — #74 クローズ

マイルストーン 1.6 : STRIDE 脅威モデル+バイナリ分析エビデンス ✅ 実装済み

CLS レベル 3 の評価担当者はコードだけでなく、記録された設計アーティファクトを要求します。 SS 711:2025 はすべての攻撃面( API ・通信・ストレージ)の STRIDE ベース脅威モデリングを必要とします。

  • STRIDE の脅威モデル(対象:なりすまし(デバイス同一性)・改ざん(監査レコード)・否認(操作ログ)・情報漏洩(ペイロードストレージ)・サービス拒否(ネットワークポリシー)・特権昇格(インジェストゲート))— docs/ja/src/threat_model.md を参照
  • 出荷クレートに既知の CVE がないことを確認するバイナリ分析レポート(cargo-auditcargo-deny
  • 脅威モデルの緩和策をトレーサビリティマトリックスエントリにリンク— docs/src/traceability.md(「防御の厳格性」を ✅ に更新済み)
  • 日本語訳: docs/ja/src/threat_model.md
  • クローズ済み: #93 (PR #143

フェーズ 2 : GCLI を通じた日本対応( 6 〜 12 ヶ月)

目標: CLS レベル 4 、 JC-STAR STAR-1/2 、サイバートラストマーク / ISO 27001

マイルストーン 2.0 :相互承認フレームワーク( GCLI + 日本シンガポール MoC ) 🔲 計画中

重複認証なしに日本市場へ参入するための、補完的な 2 つのメカニズムがあります。

  1. GCLI — 「シンガポール優先」戦略全体を支える多国間フレームワーク( 10 カ国以上)。
  2. 日本シンガポール直接 MoC ( 2026 年 3 月署名、 2026 年 6 月 1 日発効 )— JC-STAR と CLS の二国間相互承認。有効な JC-STAR ラベルはそのまま CLS として認められるため、認証データの再マッピングは不要。

本マイルストーンの成果物:

  • GCLI ルートと直接 MoC ルートの両方をカバーする日本のお客様向けコンプライアンス経路ガイド
  • JC-STAR ラベル検証・証明モジュール(edgesentry_rs::compliance::jcstar) — #121を参照してください
  • CLS ↔ JC-STAR レベル等価対応表( CSA/METI による公表待ち; CSA および METI/IPA のページを継続的に確認してください)
  • フィンランド・ドイツ・韓国の IoT 認証取得済みのお客様向け MRA ファストトラックガイダンス
  • #94を参照してください

マイルストーン 2.1 : JC-STAR STAR-1/2 アライメント 🔲 計画中

  • 日本の IoT 製品セキュリティ適合性評価基準に基づくセルフチェックリストと実装ガイダンス
  • #82を参照してください

マイルストーン 2.2 :エッジインテリジェンス 🔲 計画中

  • edgesentry-summary — 帯域幅が制約された回線上の高性能な日本製センサー向けのデータ要約ロジック。#83を参照してください
  • edgesentry-detector — 結果に署名済み監査エビデンスを添付したローカル異常検知。#84を参照してください

マイルストーン 2.3 :クロスボーダー教育プログラム 🔲 計画中

  • 日本企業がシンガポールの公共インフラプロジェクトに入札するための合同技術白書
  • #85を参照してください

マイルストーン 2.4 :サイバートラストマーク / ISO 27001 組織トラック 🔲 計画中

シンガポールのサイバートラストマークは、 2026 〜 27 年から CII (重要情報インフラ)事業者に対して義務化されます。これは製品レベルの CLS に対する組織レベルの対応物です。シンガポールの B2B および Government のお客様は、ベンダーにこのトラックへの対応をますます求めるようになるでしょう。

  • EdgeSentry-RS の実装エビデンスをサイバートラストマーク評価カテゴリにマッピング
  • ISO 27001 コントロールアライメント文書
  • #95を参照してください

マイルストーン 2.5 : CLS(MD) — 医療機器バリアント 🔲 計画中

シンガポールは 2024 年 10 月に医療機器向け CLS ( CLS(MD))を開始しました。医療 IoT を目標市場とする場合、特定のバリアント要件が適用されます。

  • 現在の実装に対する CLS(MD)ギャップ分析
  • 医療機器固有要件の特定
  • #96を参照してください

フェーズ 3 :グローバルコンバージェンス — 「ヨーロッパへの地平線」( 12 〜 24 ヶ月)

目標: EU CRA 、英国 PSTI 法、 IEC 62443-4-2 ( CII/OT )、 CCoP 2.0

マイルストーン 3.1 : EU CRA コンプライアンス調査 🔲 計画中

  • 欧州市場へのパスポートとして ETSI EN 303 645 へのフルマッピング
  • シンガポール CLS の基盤は最小限の追加作業で CRA 要件の大部分をカバー

マイルストーン 3.2 :英国 PSTI 法アライメント 🔲 計画中

英国の製品セキュリティ・通信インフラ( PSTI )法は ETSI EN 303 645 と整合しており、 2026 年 1 月に発効しました。 CLS 準拠を前提とすれば、追加実装はほぼ不要です。

  • CLS レベル 3 と英国 PSTI 要件のギャップ分析
  • PSTI コンプライアンス声明文書
  • #97を参照してください

マイルストーン 3.3 : IEC 62443-4-2 + ハードウェア RoT 🔲 計画中

IEC 62443-4-2 は、 CII (重要情報インフラ)および OT 市場向けのコンポーネントレベル要件を規定します。ハードウェア Root of Trust ( TPM/HSM )・ RBAC ・特権アクセス管理( PAM )が必要であり、 ETSI EN 303 645 とは異なる要件です。

  • IEC 62443-4-2 コンポーネント要件マッピング
  • ハードウェアバックの鍵保存( CLS レベル 4 )のためのedgesentry-bridgeによる HSM 統合
  • デプロイ担当者向け RBAC/PAM 設計ガイダンス
  • #54および#98を参照してください

マイルストーン 3.4 : CCoP 2.0 / MTCS ティア 3 🔲 計画中

シンガポールのサイバーセキュリティ実践規範 2.0 ( CCoP 2.0 )は、 CII セクター向けの運用コンプライアンス要件です。プラットフォームに政府契約を対象としたクラウドまたは SaaS コンポーネントがある場合、 MTCS ティア 3 が適用されます。

  • CCoP 2.0 運用要件マッピング
  • クラウドデプロイシナリオにおける MTCS ティア 3 適用性評価
  • #99を参照してください

マイルストーン 3.5 :形式的検証とハードニング 🔲 計画中

  • CLS レベル 4 で求められるサードパーティバイナリ分析に耐えるための、高度なメモリ安全性と脆弱性ハードニング

マイルストーン 3.6 : AI ロボティクス向けリファレンスアーキテクチャ 🔲 計画中

  • 自律移動ロボット( AMR )および点検ドローンにおける改ざん検知可能な意思決定監査のリファレンス設計

持続可能なエコシステム戦略

DuckDB モデルに倣い、プラットフォームではなくライブラリを通じて普及する、軽量な埋め込み可能コアを目指します。

  1. 「インプロセス」セキュリティ — DuckDB が Python や Java プロセスに組み込まれるように、 OS やハードウェアを問わず既存の C++アプリケーション内にライブラリとして組み込む。

  2. オープンコンプライアンス — 「セキュリティを達成する方法」の知識を OSS 化し、単一ベンダーがコンプライアンス経路を支配しないようにする。標準を公共のインフラとして位置づける。

  3. コラボレーティブラーニング — 次世代の IoT セキュリティエンジニアを育成するための、企業を超えた共同学習環境として Rust コードベースを提供する。

コンプライアンス・トレーサビリティマトリックス

このページは、シンガポール CLS / iM8 の各条項および対応する ETSI EN 303 645 の規定を、それを満たすソースコードにマッピングします。各行には Japan JC-STAR の相互参照と SS 711:2025 設計原則のアライメントが含まれています。

凡例:

  • ✅ 実装済み
  • ⚠️ 部分的
  • 🔲 計画中
  • ➖ スコープ外

SS 711:2025 設計原則カバレッジ

シンガポールの国内 IoT 標準 SS 711:2025 は 4 つの原則を定義しています。完全なモジュールマッピングについてはロードマップを参照してください。

原則SS 711:2025 要件ステータス
セキュア・バイ・デフォルト一意のデバイス同一性、署名付き OTA アップデートidentity.rsupdate.rs
防御の厳格性STRIDE の脅威モデル、改ざん検知✅ ハッシュチェーン(integrity.rs)+ STRIDE 脅威モデル
アカウンタビリティ監査証跡、操作ログ、 RBAC 設計ingest/( AuditLedger 、 OperationLog )
回復力デフォルト拒否のネットワーキング、 DoS 対策ingest/network_policy.rs


CLS レベル 3 / ETSI EN 303 645 — コア要件

CLS-01 / §5.1 — 汎用デフォルトパスワードの禁止

項目詳細
JC-STARSTAR-1 R3.1
要件デバイスは汎用デフォルト認証情報を使用してはならない
ステータス➖ スコープ外 — このプロジェクトはソフトウェア監査レコードを実装するものであり、デバイスの認証情報管理ではない

CLS-02 / §5.2 — 脆弱性報告の管理手段の実装

項目詳細
JC-STARSTAR-1 R4.1
要件SLA が定められた、公開・実行可能な脆弱性報告チャネル
ステータス✅ 実装済み
実装SECURITY.md — 対応バージョン・プライベート報告窓口(GitHub Security Advisory)・承認 SLA(3営業日)・パッチ SLA(重大/高: 30日、中/低: 90日)・スコープ定義を含む公開開示ポリシー
実装GitHub プライベート脆弱性報告機能を有効化済み — 報告者は Security Advisories フォームを使用

CLS-03 / §5.3 — ソフトウェアの最新状態維持

項目詳細
JC-STARSTAR-2 R2.2
要件ソフトウェアアップデートパッケージはインストール前に署名・検証されなければならない
ステータス✅ 実装済み
実装UpdateVerifier::verifyが BLAKE3 ペイロードハッシュと Ed25519 パブリッシャー署名をチェックしてからインストールを許可。失敗したチェックはUpdateVerificationLogUpdateVerifyDecision::Rejectedとして記録される(src/update.rs
テストtests/unit/update_tests.rs — 承認パス・改ざんペイロード・無効な署名・不明なパブリッシャー・マルチパブリッシャー分離をカバー

CLS-04 / §5.4 — 機密セキュリティパラメータの安全な保存

項目詳細
JC-STARSTAR-1 R1.2
要件秘密鍵は安全に保存されなければならない。鍵登録プロセスが存在しなければならない
ステータス✅ 実装済み
実装公開鍵レジストリ:IntegrityPolicyGate::register_devicesrc/ingest/policy.rs:20
実装鍵生成 CLI :eds keygensrc/lib.rs — generate_keypair
実装鍵検査 CLI :eds inspect-keysrc/lib.rs — inspect_key
実装プロビジョニングとローテーションのガイダンス:鍵管理
注意HSM バックの鍵保存( CLS レベル 4 )は#54で計画中

CLS-05 / §5.5 — 安全な通信

項目詳細
JC-STARSTAR-1 R1.1
要件データは真正性の保証を持って送信されなければならない
ステータス✅ 実装済み
実装 — レコード真正性すべてのAuditRecordは BLAKE3 ペイロードハッシュに対する Ed25519 署名を持つ — build_signed_recordsrc/agent.rs)、sign_payload_hashsrc/identity.rs:12
実装 — チャネル機密性(HTTP)transport-tls フィーチャー:rustls TLS 1.2/1.3 の serve_tls()、ハンドシェイク前の IP アローリスト適用、eds serve-tls --tls-cert / --tls-key CLI — closed #176src/transport/tls.rs
実装 — チャネル機密性(MQTT)transport-mqtt-tls フィーチャー:CA 証明書パスを持つ MqttTlsConfigrumqttc::TlsConfiguration::Rustls 経由の rustls ClientConfigeds serve-mqtt --tls-ca-cert CLI — closed #180src/transport/mqtt.rs

CLS-06 / §5.6 — 露出する攻撃面の最小化

項目詳細
JC-STARSTAR-1 R3.2
要件必要なインターフェースとサービスのみを公開すべき
ステータス✅ 実装済み
実装 — IP アローリストNetworkPolicyがデフォルト拒否の IP/CIDR アローリスト強制を提供(src/ingest/network_policy.rs
実装 — HTTP トランスポートingest_handlerが暗号検証の前にNetworkPolicy::check(source_ip)を強制;未登録ソースに403 Forbiddenを返す(src/transport/http.rs
実装 — MQTT トランスポートserve_mqttは単一のサブスクライブ専用トピックを公開;管理インターフェースなし;ブローカーレベルの ACL を推奨(src/transport/mqtt.rs
備考ネットワークレベルのコントロール(VPN・ファイアウォールルール)はデプロイ担当者の責任

CLS-07 / §5.7 — ソフトウェア完全性の保証

項目詳細
JC-STARSTAR-1 R1.3
要件デバイスはソフトウェアとデータの完全性を検証しなければならない
ステータス✅ 実装済み
実装 — ペイロードハッシュ生ペイロードに対する BLAKE3 ハッシュ:compute_payload_hashsrc/integrity.rs:12
実装 — ハッシュチェーンprev_record_hashが各レコードを前のレコードにリンク。挿入/削除はverify_chainで検知(src/integrity.rs:35
テストtampered_lift_demo_chain_is_detectedsrc/lib.rs:338

CLS-08 / §5.8 — 個人データの安全性の確保

項目詳細
JC-STARSTAR-2 R4.1
要件送信または保存される個人データは保護されなければならない
ステータス➖ スコープ外 — 現在の実装では監査レコードに個人データを含まない

CLS-09 / §5.9 — 障害に対するシステムの回復力

項目詳細
JC-STARSTAR-2 R3.2
要件デバイスは運用状態を維持し、優雅に回復すべき
ステータス➖ スコープ外(部分的なパスは計画中)
注意完全な HA はデプロイ担当者の責任だが、ライブラリは接続断失中に署名済みレコードを蓄積し、リンク回復時にチェーン順序で再送するオフラインバッファ/ストア&フォワードモジュールを提供できる。#74で追跡中

CLS-10 / §5.10 — システムテレメトリデータの検査

項目詳細
JC-STARSTAR-2 R3.1
要件セキュリティ関連イベントはログに記録され、リプレイ/並べ替え攻撃が検知されなければならない
ステータス✅ 実装済み
実装 — シーケンスデバイスごとの厳密な単調増加sequence。重複・順序違いのレコードはIngestState::verify_and_acceptで拒否(src/ingest/verify.rs:45
実装 — 監査証跡承認/拒否の決定はIngestServiceAuditLedger経由で永続化(src/ingest/storage.rs

CLS-11 / §5.11 — ユーザーデータの削除を容易にする

項目詳細
JC-STAR
要件ユーザーは個人データを削除できるべき
ステータス➖ スコープ外

CLS レベル 4 — 追加要件

CLS レベル 4 — ハードウェアセキュリティモジュール( HSM )

項目詳細
JC-STARSTAR-2 R1.4
要件秘密鍵は HSM 内に保存・使用されなければならない
ステータス🔲 計画中
ギャップHSM バックの鍵保存はフェーズ 3 ( IEC 62443-4-2 / CII/OT )で計画中。#54および#98を参照

JC-STAR 追加要件

STAR-1 R2.1 — リプレイ・並べ替え防止

項目詳細
CLSCLS-10
要件リプレイ攻撃は検知・拒否されなければならない
ステータス✅ 実装済み
実装IngestStateseen HashSet が重複した(device_id, sequence)ペアを拒否(src/ingest/verify.rs:56

カバレッジサマリー

レベル総条項数✅ 実装済み⚠️ 部分的🔲 計画中➖ スコープ外
CLS レベル 3115303
CLS レベル 410010
JC-STAR 追加11000

注意: 「スコープ外」の条項は、デバイスレベルの懸念事項(パスワード・ネットワークインターフェース・個人データ)をカバーしており、監査レコードライブラリではなくデプロイ担当者の責任となるものです。

STRIDE 脅威モデル

本文書は、SS 711:2025 防御の厳格性(Rigour in Defence) および IMDA IoT サイバーセキュリティガイドの脅威モデリングチェックリストに基づき、シンガポール CLS Level 3 評価向けに作成した正式な脅威モデリング成果物です。API・通信チャネル・ストレージのすべての攻撃面を対象とします。

手法: STRIDE(Microsoft) スコープ: edgesentry-rs ライブラリおよび edgesentry-bridge FFI クレート — デバイス側署名、クラウド側インジェスト、HTTP トランスポート、オペレーションログ、監査台帳 評価者参照: SS 711:2025 §4.2 防御の厳格性; IMDA IoT サイバーセキュリティガイド §3 脅威モデリングチェックリスト


システム概要

┌─────────────────────────────────────────────────────────────────┐
│  フィールドデバイス(エッジ)                                         │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │  build_signed_record()                                     │ │
│  │  payload → BLAKE3 ハッシュ → Ed25519 署名 → AuditRecord   │ │
│  └────────────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────────┘
                             │ POST /api/v1/ingest(HTTPS 上の JSON)
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  クラウドインジェスト層                                              │
│  ┌────────────────┐  ┌─────────────────┐  ┌─────────────────┐  │
│  │ NetworkPolicy  │  │ IntegrityPolicy │  │ AsyncIngest     │  │
│  │ IP/CIDR ゲート │→ │ Gate            │→ │ Service         │  │
│  │ (拒否デフォ)  │  │ (署名+チェーン │  │ (ハッシュチェーン│  │
│  └────────────────┘  │  検証)         │  │  +シーケンス)  │  │
│                      └─────────────────┘  └────────┬────────┘  │
│                                                     │           │
│            ┌────────────────────────────────────────┤           │
│            ▼                          ▼             ▼           │
│  ┌──────────────────┐  ┌─────────────────────┐  ┌──────────┐   │
│  │  Raw Data Store  │  │  Audit Ledger       │  │ Op. Log  │   │
│  │  (S3 / メモリ)   │  │  (Postgres / メモリ) │  │          │   │
│  └──────────────────┘  └─────────────────────┘  └──────────┘   │
└─────────────────────────────────────────────────────────────────┘

STRIDE 脅威分析

S — なりすまし(Spoofing)— デバイス ID

脅威: 攻撃者が device_id フィールドを偽装するか、侵害された鍵で署名したレコードをリプレイすることで、正規のフィールドデバイスになりすます。

攻撃面: POST /api/v1/ingestAuditRecord.device_id および AuditRecord.signature フィールド。

サブ脅威説明
S-1有効な device_id を持つが未登録の Ed25519 鍵でレコードを送信する
S-2正当に署名された過去のレコードをリプレイする
S-3署名鍵と一致しない偽造 device_id でレコードを送信する

緩和策:

ID緩和策コード位置
M-S-1デバイスの公開鍵はクラウド側で事前登録される。登録済み鍵で検証できない署名は IngestError::UnknownDevice として拒否されるingest/policy.rs IntegrityPolicyGate::enforce()
M-S-2単調増加するシーケンス番号と prev_record_hash チェーン継続性を強制;リプレイされたレコードは重複シーケンスとして検出されるingest/verify.rs check_sequence()
M-S-3Ed25519 署名はペイロードハッシュを秘密鍵に結びつける;偽造 device_id は署名検証で失敗するidentity.rs verify_payload_signature()

残留リスク: デバイスの秘密鍵が物理的に抽出された場合、有効な署名でレコードを偽造できる。ハードウェアバックアップ鍵ストレージ(TPM/SE)はデバイス層の管理策であり、本ライブラリのスコープ外。ロードマップに記載。


T — 改ざん(Tampering)— 監査レコード

脅威: 攻撃者が転送中または保存中の監査レコードあるいは生ペイロードを改ざんする。

攻撃面: ワイヤフォーマット(JSON ボディ)、Raw データストア(S3 オブジェクト)、監査台帳(DBレコード)。

サブ脅威説明
T-1HTTP リクエストボディの raw_payload_hex を改ざんする
T-2異なるペイロードに合わせて AuditRecord.payload_hash を改ざんする
T-3受理後に保存 S3 オブジェクトのバイトを反転させる
T-4チェーンを断ち切るまたはリダイレクトするために prev_record_hash を改ざんする

緩和策:

ID緩和策コード位置
M-T-1インジェスト毎にクラウドが BLAKE3(raw_payload) を再計算し record.payload_hash と比較;不一致は PayloadHashMismatch として拒否されるingest/storage.rs IngestService::ingest()
M-T-2payload_hash は Ed25519 署名で保護される;ハッシュが変更されると署名検証が失敗するidentity.rs verify_payload_signature()
M-T-3保存後の改ざんは、台帳のハッシュとオブジェクト内容を再検証することで検出可能;運用ランブックに記載の運用的管理策
M-T-4prev_record_hash は直前の受理済みレコードの hash() と照合される;継続性が断たれると以後のすべてのレコードが拒否されるingest/verify.rs check_chain_link()

残留リスク: 受理後の保存オブジェクト改ざんはストレージ層の問題。S3 Object Lock(WORM)やDB行レベルチェックサムをデプロイ層で有効化することで排除できる。


R — 否認(Repudiation)— オペレーションログ

脅威: デバイスまたはオペレーターが特定のインジェストイベントの発生を否定する、またはレコードが送信されなかった・拒否されたと主張する。

攻撃面: インジェスト中に書き込まれる OperationLog エントリ;監査台帳への追記操作。

サブ脅威説明
R-1デバイスがレコードを送信していないと主張する
R-2オペレーターがレコードが受理された(または拒否された)事実を否定する
R-3オペレーションログエントリを事後に削除または改ざんする

緩和策:

ID緩和策コード位置
M-R-1受理・拒否を問わず、すべてのインジェスト試行に対して device_idsequencedecisionmessage を含む OperationLogEntry が書き込まれるingest/storage.rs log_acceptance() / log_rejection()
M-R-2IngestDecision::Accepted / Rejected は決定と同時に操作ログに永続化される;レコードの署名済みハッシュが送信の暗号学的証明となるingest/storage.rs OperationLogEntry
M-R-3追記専用のオペレーションログ(Postgres は INSERT のみ;ログ行への DELETE/UPDATE なし)により事後改ざんを防止するingest/storage.rs PostgresOperationLog;DB ユーザー権限で強制

残留リスク: ライブラリはログデータを提供する;特権インサイダーによる削除からそのデータを守るには、DB 層の管理策(ロール分離、DB 層での監査ログ)が必要。


I — 情報漏えい(Information Disclosure)— ペイロードストレージ

脅威: 機密性の高い検査ペイロードデータが不正な第三者に露出する。

攻撃面: HTTP リクエストボディ(raw_payload_hex)、Raw データストア(S3)、監査台帳、オペレーションログ。

サブ脅威説明
I-1HTTP 通信チャネルへの盗聴
I-2S3 オブジェクトまたは Postgres 行への不正読み取りアクセス
I-3エラーメッセージやログにペイロードバイトが現れる

緩和策:

ID緩和策コード位置
M-I-1HTTP トランスポートは TLS ターミネーション(ロードバランサー / Nginx / Cloudflare)の後ろで動作するよう設計されている;生ペイロードは JSON ボディ内で hex エンコードされ HTTPS で転送される必要があるtransport/http.rs — TLS はデプロイ層の管理策;運用ランブックに記載
M-I-2生ペイロードは呼び出し元が指定したキーで object_ref により保存される;アクセス制御はストレージ層(S3 バケットポリシー、Postgres GRANT)で強制;ライブラリは非認証の呼び出し元に読み取り API を公開しないingest/storage.rs RawDataStore::put()
M-I-3エラーメッセージには device_idsequence が含まれるが生ペイロードバイトは含まれない;tracing スパンはペイロードバイト長のみを記録するingest/storage.rs #[instrument(skip(raw_payload))]

残留リスク: S3 オブジェクトと Postgres 行の保存時暗号化はデプロイ層の管理策(S3 SSE-KMS、Postgres pgcrypto または TDE)。インジェスト HTTP エンドポイントの TLS 1.3 はロードマップ(issue #73)で対応予定。


D — サービス拒否(Denial of Service)— ネットワークポリシー

脅威: 攻撃者がインジェストエンドポイントを大量のリクエストで溢れさせ、正規デバイスのレコード送信を妨害する。

攻撃面: POST /api/v1/ingest HTTP エンドポイント;NetworkPolicy チェック;AsyncIngestService tokio タスクプール。

サブ脅威説明
D-1信頼できない IP からの大量リクエストがハンドラーを圧倒する
D-2大きな raw_payload_hex 値がメモリを枯渇させる
D-3不正な JSON ボディが解析時間を消費する

緩和策:

ID緩和策コード位置
M-D-1NetworkPolicy 拒否デフォルト:明示的に許可リストに登録されていない限り、すべての IP と CIDR 範囲をブロック;未承認の送信元 IP は暗号処理が実行される前に 403 Forbidden を受け取るingest/network_policy.rs NetworkPolicy::check()transport/http.rs ハンドラー
M-D-2Axum のデフォルトリクエストボディサイズ制限(2 MB)がペイロードサイズを上限化するtransport/http.rs — axum デフォルトボディ制限
M-D-3JSON デシリアライズエラーは即座に 400 Bad Request を返す;後段の処理は実行されないtransport/http.rs — axum Json エクストラクター

残留リスク: ソース IP ごと・デバイスごとのレート制限はライブラリ層では未実装;本番デプロイではリバースプロキシまたは API ゲートウェイ層で追加すべき。issue #73(TLS、P2)が計画中のフォローアップマイルストーン。


E — 特権昇格(Elevation of Privilege)— インジェストゲート

脅威: 攻撃者がインジェスト検証ゲートを回避し、任意のレコードを台帳または Raw データストアに書き込む。

攻撃面: IntegrityPolicyGateingest_handler、サービス登録 API(register_device)。

サブ脅威説明
E-1攻撃者が未登録デバイスのレコードで ingest を呼び出し成功させる
E-2攻撃者が制御していないデバイスの有効なシーケンス/チェーンを持つレコードを送信する
E-3攻撃者が register_device を直接呼び出すことで悪意のあるデバイスを登録する

緩和策:

ID緩和策コード位置
M-E-1IntegrityPolicyGate::enforce() はストレージ書き込みの前に無条件で呼び出される;未知のデバイスは IngestError::UnknownDevice で失敗するingest/policy.rs
M-E-2署名検証は device_id に対して登録済みの公開鍵を使用する;デバイスの秘密鍵なしに有効なチェーンは偽造できないidentity.rs verify_payload_signature()
M-E-3register_device は起動時にアプリケーション層のみが呼び出す特権操作;HTTP インジェストハンドラーはデバイス登録をネットワーク経由で公開しないtransport/http.rs — 登録エンドポイントなし;ingest/storage.rs AsyncIngestService::register_device()

残留リスク: register_device を呼び出すアプリケーション層が侵害された場合、任意のデバイスを登録できる。これは運用セキュリティの管理策:登録は強力な認証を持つ別の特権 API の背後に置くべき。


バイナリ解析エビデンス

cargo audit — アドバイザリデータベーススキャン

コマンドと出力(アドバイザリデータベースコミット:最新):

cargo audit

結果: 検出されたアドバイザリはすべて deny.toml で事前承認済み(下表参照):

アドバイザリクレートバージョンステータス理由
RUSTSEC-2026-0049rustls-webpki0.101.7無視(#125aws-smithy-http-client のレガシー hyper-rustls 0.24rustls 0.21 チェーンに固定されている;0.101.x パッチは存在しない。0.103.x インスタンスは 0.103.10 に更新済み。
RUSTSEC-2026-0049rustls-webpki0.102.8無視(#166rumqttc 0.25rustls 0.22 チェーンに固定されている;rustls 0.23+ を採用した rumqttc のリリースが必要。コードベース内に CRL 失効 API 呼び出しは存在しない。

その他のスキャン済みクレート依存関係: 既知の CVE なし

再現手順:

cargo install cargo-audit --locked
cargo audit

cargo deny check — ポリシー強制

コマンド:

cargo deny check

結果: advisories ok, bans ok, licenses ok, sources ok

deny.toml ポリシーの強制内容:

  • アドバイザリ: 文書化された理由を持つ明示的な無視エントリを除き、すべての脆弱性をデフォルトで拒否
  • バン: 複数クレートバージョンを警告;ワイルドカード依存を警告
  • ライセンス: MIT・Apache-2.0・BSD-2-Clause・BSD-3-Clause・Unicode-3.0・CC0-1.0・Zlib のみ許可;例外 1 件: cbindgen(MPL-2.0、ビルド専用ヘッダー生成ツール — コピーレフトは生成物やソースコードに及ばない)
  • ソース: crates.io および信頼済み git ソースのみ

再現手順:

cargo install cargo-deny --locked
cargo deny check

脅威と緩和策のトレーサビリティ要約

STRIDE カテゴリ脅威 ID緩和策 IDソースファイルステータス
なりすましS-1M-S-1ingest/policy.rs
なりすましS-2M-S-2ingest/verify.rs
なりすましS-3M-S-3identity.rs
改ざんT-1M-T-1ingest/storage.rs
改ざんT-2M-T-2identity.rs
改ざんT-3M-T-3運用的管理策⚠️ デプロイ
改ざんT-4M-T-4ingest/verify.rs
否認R-1M-R-1ingest/storage.rs
否認R-2M-R-2ingest/storage.rs
否認R-3M-R-3DB 権限層⚠️ デプロイ
情報漏えいI-1M-I-1デプロイ(TLS)⚠️ #73
情報漏えいI-2M-I-2ストレージアクセス制御⚠️ デプロイ
情報漏えいI-3M-I-3ingest/storage.rs
サービス拒否D-1M-D-1ingest/network_policy.rstransport/http.rs
サービス拒否D-2M-D-2transport/http.rs(axum ボディ制限)
サービス拒否D-3M-D-3transport/http.rs
特権昇格E-1M-E-1ingest/policy.rs
特権昇格E-2M-E-2identity.rs
特権昇格E-3M-E-3transport/http.rs

凡例: ✅ ライブラリコードに実装済み — ⚠️ デプロイ層の管理策(ライブラリスコープ外)

SBOM とベンダー開示チェックリスト

このページは、シンガポール CLS Level 3 審査における IMDA IoT サイバーセキュリティガイドのライフサイクルサポートエビデンス要件を満たすものです。SBOM フォーマット、生成手順、および 5 つの必須カテゴリーに関するベンダー開示チェックリストの回答を掲載しています。


ソフトウェア部品表(SBOM)

フォーマット

EdgeSentry-RS は CycloneDX JSON 形式(仕様バージョン 1.3)の SBOM をリリース時に Cargo.lock から cargo-cyclonedx を使用して生成・公開しています。

公開アーティファクト

各 GitHub Release には 2 つの SBOM ファイルがリリースアセットとして含まれます。リリースページからダウンロードできます:

https://github.com/edgesentry/edgesentry-rs/releases/tag/v<version>
ファイルスコープ
edgesentry-rs-<version>.cdx.jsonedgesentry-rs クレートおよびすべての推移的依存関係
edgesentry-bridge-<version>.cdx.jsonedgesentry-bridge C/C++ FFI クレートとその依存関係

例として v0.1.2 の場合:

  • https://github.com/edgesentry/edgesentry-rs/releases/download/v0.1.2/edgesentry-rs-0.1.2.cdx.json
  • https://github.com/edgesentry/edgesentry-rs/releases/download/v0.1.2/edgesentry-bridge-0.1.2.cdx.json

SBOM のローカル生成手順

cargo install cargo-cyclonedx --locked
cargo cyclonedx --format json --all
# 出力: crates/edgesentry-rs/edgesentry-rs.cdx.json
#       crates/edgesentry-bridge/edgesentry-bridge.cdx.json

依存コンポーネント数の確認方法

依存関係の更新のたびに変化するため、生成後に以下で現在の数を確認してください:

cargo cyclonedx --format json --all
python3 -c "
import json
for f in ['crates/edgesentry-rs/edgesentry-rs.cdx.json',
          'crates/edgesentry-bridge/edgesentry-bridge.cdx.json']:
    bom = json.load(open(f))
    print(f\"{f}: {len(bom.get('components', []))} components\")
"

継続的なサプライチェーン監視

  • cargo-audit — すべての CI ビルドと PR で実行。RustSec Advisory Database に対してすべての依存関係をチェック
  • cargo-deny — すべての CI ビルドでライセンスポリシーと禁止事項を強制
  • Dependabot — 週次の依存関係バージョン更新 PR を自動作成

ベンダー開示チェックリスト

IMDA IoT サイバーセキュリティガイドは 5 つのカテゴリーにわたる回答を要求しています。以下の表は EdgeSentry-RS の各カテゴリーにおける状況を文書化したものです。

1. 暗号化サポート

項目回答
使用アルゴリズムEd25519(署名)、BLAKE3(ハッシュ)
鍵長Ed25519: 256 ビット;BLAKE3 出力: 256 ビット
乱数生成rand::OsRng 経由の OS CSPRNG — カスタム RNG なし
転送暗号化レコードレベル:ペイロードハッシュへの Ed25519 署名。ネイティブ TLS トランスポートを提供:eds serve-tls --tls-cert / --tls-key(rustls TLS 1.2/1.3、HTTP)および eds serve-mqtt --tls-ca-cert(MQTT over TLS)。トレーサビリティマトリックスの CLS-05 を参照。
鍵の保管パブリックキーはメモリ内レジストリ(IntegrityPolicyGate);プライベートキーはデプロイ側が管理。HSM 対応は計画中:#54
実装crates/edgesentry-rs/src/identity.rscrates/edgesentry-rs/src/integrity.rs

2. 識別と認証

項目回答
デバイス認証方式Ed25519 非対称鍵ペア:デバイスが各レコードに秘密鍵で署名し、クラウドが登録済み公開鍵で検証
認証情報の保管秘密鍵はデバイス上にのみ保管;公開鍵は IntegrityPolicyGate::register_device でクラウド側に登録
デフォルト認証情報なし — 各デバイスが eds keygen でユニークなキーペアを生成
ブルートフォース対策署名検証は単一の定時間演算;認証情報ベースのログイン面は存在しない
ルート同一性強制IngestService::ingestcert_identity パラメーター — TLS クライアント証明書の同一性と record.device_id の不一致は即時拒否
実装crates/edgesentry-rs/src/identity.rscrates/edgesentry-rs/src/ingest/policy.rs

3. データ保護

項目回答
転送中のデータすべての AuditRecord が BLAKE3 ペイロードハッシュへの Ed25519 署名を保持 — トランスポートに関わらずレコードレベルの真正性を保証
保存データ生ペイロードは RawDataStore(S3/MinIO)経由で保管;監査レコードは AuditLedger(PostgreSQL)。保存時の暗号化はデプロイ側の責務(S3 SSE、Postgres 列暗号化)
個人データAuditRecord は設計上個人データフィールドを持たない — object_ref はストレージキーへの参照;ペイロード本体は別途保管
データ最小化監査メタデータ(payload_hashsignatureprev_record_hash)とペイロード本体を分離 — クラウドはハッシュチェーンのみ保管;生データは object_ref 経由で独立して保管
実装crates/edgesentry-rs/src/record.rscrates/edgesentry-rs/src/ingest/storage.rs

4. ネットワーク保護

項目回答
不要なポート/サービスライブラリのみ — edgesentry-rs はネットワークサービスを直接開放しない。トランスポートはデプロイ側の責務
デフォルト拒否ネットワークポリシーNetworkPolicy が IP/CIDR アローリストを強制;check(source_ip) は暗号演算の前に呼び出され、リスト外のすべての送信元を拒否
DoS 耐性NetworkPolicy ゲートがリスト外送信元を暗号処理前に拒否し、攻撃面を制限。完全なレート制限はデプロイ側の責務
実装crates/edgesentry-rs/src/ingest/network_policy.rs
CLS 参照CLS-06 / ETSI EN 303 645 §5.6

5. ライフサイクルサポート

項目回答
脆弱性報告GitHub プライベート脆弱性報告を有効化。SECURITY.md 参照 — SLA:承認 3 営業日;パッチ 30 日(重大/高)、90 日(中/低)
SBOM の提供各 GitHub Release に CycloneDX JSON を添付(上記参照)
依存関係のアドバイザリスキャンcargo-audit を CI ビルドおよび PR ごとに RustSec Advisory DB に対して実行
サポート終了ポリシーedgesentry-rs v0.x:現行バージョンをサポート。セキュリティ更新はパッチリリースで提供
ソフトウェア更新の完全性UpdateVerifier が更新適用前に BLAKE3 ペイロードハッシュと Ed25519 パブリッシャー署名を確認 — CLS-03 参照
対応バージョンSECURITY.md 参照
CLS 参照CLS-02 / ETSI EN 303 645 §5.2

トレーサビリティ

このドキュメントはロードマップのマイルストーン 1.4 を満たします。条項別のコンプライアンスマッピングの詳細はコンプライアンス・トレーサビリティマトリックスを参照してください。

edgesentry-rs のコンセプト

このドキュメントは、このリポジトリで使用されているコアコンセプトをまとめたものです。

1. 改ざん検知可能な設計

主な目的は「完全な改ざん防止」ではなく、「確実な改ざん検知」です。

  • 元のペイロードからハッシュを計算する
  • デバイスの秘密鍵でハッシュに署名する
  • ハッシュチェーンでレコードを連結する

これらのメカニズムを組み合わせることで、改ざん・なりすまし・レコードの並べ替えを検知します。

2. AuditRecord

エビデンスの基本単位はAuditRecordです。主なフィールド:

  • device_id:送信元デバイスの同一性
  • sequence:単調増加するシーケンス番号
  • timestamp_ms:イベントのタイムスタンプ
  • payload_hash:生ペイロードデータのハッシュ
  • signaturepayload_hashに対する署名
  • prev_record_hash:前の監査レコードのハッシュ
  • object_ref:生ペイロードストレージへの参照(例:s3://...

3. ハッシュと署名

3.1 ハッシュ(完全性)

  • 目的:ペイロードコンテンツのフィンガープリント
  • 特性:ペイロードが 1 バイトでも変わると異なるハッシュが生成される

3.2 署名(真正性)

  • 目的:ペイロードハッシュが信頼済みデバイス鍵によって生成されたことを証明する
  • 検証:登録されたデバイス公開鍵で検証する

4. ハッシュチェーンの連続性

レコードはprev_record_hashによって連結されます。

  • 最初のレコード:prev_record_hash = zero_hash
  • 後続のレコード:前のレコードのhash()と一致しなければならない

これにより、チェーン内への挿入・削除・置換を検知します。

5. シーケンスポリシー

sequenceはデバイスごとに 1, 2, 3, …と増加しなければなりません。

  • シーケンス値の重複は拒否される
  • ギャップや順序違いのシーケンスは拒否される

6. ソフトウェアアップデートの完全性

デバイスがファームウェアまたはソフトウェアのアップデートを適用する前に、edgesentry_rs::update::UpdateVerifierを通じて 2 つのチェックをパスしなければなりません。

  1. ペイロードハッシュBLAKE3(raw_payload)SoftwareUpdateマニフェストに埋め込まれたハッシュと一致しなければならない
  2. パブリッシャー署名 — そのハッシュに対する Ed25519 署名が、登録済みの信頼するパブリッシャー鍵で検証できなければならない

試行結果(承認・拒否を問わず)はすべて監査のためにUpdateVerificationLogに追記されます。これは CLS-03 / ETSI EN 303 645 §5.3 / JC-STAR STAR-2 R2.2 を満たします。

7. ネットワークポリシー(デフォルト拒否)

edgesentry_rs::ingest::NetworkPolicyは、受信接続に対してデフォルト拒否の IP/CIDR アローリストを強制します。呼び出し元はレコードをIngestServiceに渡す 前にNetworkPolicy::check(source_ip)を呼び出します。リストにないアドレスからの接続は、暗号チェックに達することなく拒否されます。

ルールは追加的です:allow_ip(addr)で完全一致、allow_cidr("10.0.0.0/8")で CIDR ブロック( IPv4 ・ IPv6 両対応)を許可します。空のポリシーはすべてを拒否します。

8. データ取り込み時の検証

edgesentry_rs::ingestは永続化前のトラストチェックを完了させる責任を持ちます。

レコードをデータ取り込みする際の完全なチェック順序は次のとおりです。

  1. ネットワークゲートNetworkPolicy::check(source_ip)が暗号処理の前にリストにない送信元を拒否する
  2. ペイロードハッシュIngestServiceが生ペイロードとrecord.payload_hashの一致を検証する
  3. ルート同一性cert_identityが存在する場合、record.device_idと一致しなければならない
  4. 署名 — ペイロードハッシュが登録済みデバイス鍵で署名されていなければならない
  5. シーケンス — デバイスごとに厳密に単調増加かつ重複なし
  6. 前レコードのハッシュ — 最後に承認されたレコードのハッシュからチェーンが続いていなければならない

ステップ 3 〜 6 はIntegrityPolicyGateが強制し、ステップ 2 はゲートを呼び出す前にIngestServiceが強制します。

9. ストレージモデル

データ取り込みが承認されると、システムは次のものを保存します。

  • 生データ(ペイロード本体)
  • 監査台帳(監査レコードのストリーム)
  • 操作ログ(承認/拒否の決定)

この分離により、エビデンスのメタデータとペイロードストレージを独立して管理できます。

10. デモモード

10.1 ライブラリサンプル( DB/MinIO 不要)

  • 実行:cargo run -p edgesentry-rs --example lift_inspection_flow
  • インメモリストアを使用
  • 署名・データ取り込み検証・改ざん拒否を素早く確認できる

10.2 インタラクティブローカルデモ( DB/MinIO 必要)

  • 実行:bash scripts/local_demo.sh
  • PostgreSQL + MinIO + CLI を使ったエンドツーエンドのフロー
  • 永続化された監査レコードと操作ログを確認できる

11. トラスト境界

  • デバイス側:ファクトに署名してコンパクトな監査メタデータを送出する
  • クラウド側:データを受け入れる前に厳格な検証ルールを強制する

この分割により、エッジとクラウドの責任を明確かつ監査可能に保ちます。

12. 品質とリリースのコンセプト

  • 静的解析:clippy
  • OSS ライセンスポリシー検証:cargo-deny
  • アドバイザリスキャン:cargo-audit( RustSec アドバイザリ DB に対する CVE チェック)
  • リリース準備: CI + リリースワークフロー
  • タグ駆動リリース:vX.Y.Z

実行手順についてはコントリビューションビルドとリリースを参照してください。

13. STRIDE の脅威モデル

SS 711:2025 と IMDA IoT サイバーセキュリティガイドは、 CLS レベル 3 評価のために記録された STRIDE ベースの脅威モデルアーティファクトを必要とします。 6 つの脅威カテゴリは、 EdgeSentry-RS の攻撃面に次のようにマッピングされます。

脅威攻撃面緩和策
S poofing (なりすまし)デバイス同一性Ed25519 署名 — 登録された公開鍵だけがレコードを検証できる
T ampering (改ざん)監査レコード・ペイロードストレージBLAKE3 ハッシュチェーン — いかなる変更もチェーンの連続性を破壊する
R epudiation (否認)データ取り込み決定OperationLogがすべての承認/拒否決定を理由とともに記録する
I nformation Disclosure (情報漏洩)生ペイロードストレージobject_refの分離により、ペイロード本体が監査メタデータストリームに含まれないよう保つ
D enial of Service (サービス拒否)データ取り込みエンドポイントNetworkPolicyのデフォルト拒否が、暗号処理の前にリストにない送信元を拒否する
E levation of Privilege (特権昇格)データ取り込みゲートIntegrityPolicyGateがデータを受け入れる前にデバイス登録と署名を検証する

CLS レベル 3 評価向けの正式な設計アーティファクトの作成は#93で追跡されています。

14. SBOM (ソフトウェア部品表)

ソフトウェア部品表( SBOM )は、製品で使用されているすべてのソフトウェアコンポーネントとそのバージョンを一覧にしたものです。 IMDA IoT サイバーセキュリティガイドは、ベンダー開示チェックリストのライフサイクルサポートカテゴリの一部として、 SBOM の提供を必須 CLS レベル 3 エビデンスアーティファクトとして要求しています。

Rust プロジェクトでは、cargo-sbomcargo-cyclonedxなどのツールを使ってCargo.lockから SBOM を生成し、すべてのクレートとその推移的な依存関係の機械可読なインベントリを作成します。

SBOM の生成・公開とベンダー開示チェックリストの整備は#92で追跡されています。

アーキテクチャ

デバイス側とクラウド側

このシステムは、フィールドデバイス(例:エレベーター点検デバイス)が点検エビデンスをクラウドサービスに送信する、公共インフラ向け IoT デプロイを想定しています。

デバイス側(リソース制約のあるエッジ)

デバイス側の責務はedgesentry_rs::build_signed_recordおよび関連関数によって実装されます。

  • 点検イベントのペイロードを生成する(ドアチェック・振動チェック・非常ブレーキチェック)
  • payload_hashを計算する( BLAKE3 )
  • Ed25519 秘密鍵でハッシュに署名する
  • レコードがチェーンを形成するように、各イベントを前のレコードハッシュ(prev_record_hash)に連結する
  • エッジ側のコストを抑えるために、コンパクトな監査メタデータとオブジェクト参照(object_ref)のみを送信する

クラウド側(検証とトラスト強制)

クラウド側の責務はedgesentry_rs::ingestおよび関連モジュールによって実装されます。

  • 承認済み IP アドレスおよび CIDR レンジへの受信接続をゲートする(NetworkPolicy::check)— デフォルト拒否
  • デバイスが既知であることを検証する(device_id -> 公開鍵)
  • 受信レコードごとに署名の有効性を検証する
  • シーケンスの単調増加を強制し、重複を拒否する
  • ハッシュチェーンの連続性を強制する(prev_record_hashは前のレコードハッシュと一致しなければならない)
  • 改ざん・リプレイ・並べ替えされたデータを永続化前に拒否する

共有トラストロジック

すべてのハッシュと検証ルールは同じedgesentry-rsクレート内に置かれ、エッジとクラウドの両方で使用する際のロジックが同一であることを保証します。

リソース制約デバイスの設計

デバイス側の設計は意図的に軽量にされており、 Cortex-M クラスの環境への適用が可能です。

  • 小さな暗号フットプリント: レコードは固定サイズのハッシュ([u8; 32])と署名([u8; 64])を保存する
  • 最小限の計算パス: ハッシュと署名のみ。デバイス上に重いサーバー側検証ロジックは不要
  • コンパクトなワイヤフォーマットへの対応: レコード構造は決定的でシリアライズ可能(コア部分でserde + postcardをサポート)
  • 重い処理をクラウドにオフロード: 重複検知・シーケンスポリシーチェック・フルチェーン検証はクラウドの責務
  • 設計による改ざん検知: 1 バイトの変更で署名チェックまたはチェーンの連続性が壊れる

具体的な設計フロー

  1. デバイスがイベントペイロードDを作成する。
  2. デバイスがH = hash(D)を計算し、Hに署名して署名Sを得る。
  3. デバイスがAuditRecord { device_id, sequence, timestamp_ms, payload_hash=H, signature=S, prev_record_hash, object_ref }を送出する。
  4. クラウドが登録済み公開鍵で署名を検証する。
  5. クラウドがシーケンスと前ハッシュのリンクを検証する。
  6. いずれかのチェックが失敗した場合、インジェストは拒否される。すべてのチェックが通過した場合、レコードは受け入れられる。

要約すると、エッジはファクトに署名し、クラウドが連続性と真正性を強制します。

公証メタデータスキーマ

AI 推論結果を法的に有効な証拠として扱うには(BCA/CONQUAS 検査レポート、MPA 船舶証明書、国土交通省の近接目視検査同等性証明)、暗号的完全性に加えて、5 種類のプロベナンス(来歴)メタデータを監査レコードのペイロードに含める必要があります。これが公証コネクタの目標スキーマです。

カテゴリフィールド目的
センサーsensor_idcalibration_tsfirmware_versionsampling_rate計測機器がキャプチャ時に校正済みかつ仕様範囲内で動作していたことを証明する
AI モデルmodel_uuidmodel_archweight_sha256prompt_version同一入力から同一推論出力を第三者が再現できることを担保する(AI Verify アウトカム 3.1 / 3.5)
計算環境device_typeos_versiondependency_hasheshw_temp_cランタイムの完全な再現性。ハードウェア温度は推論タイミングに影響するサーマルスロットリングを検出する
コンテキストntp_tsgps_lat_lon(または屋内測位)、input_data_hashレコードを特定の物理的場所と時刻に紐付ける。input_data_hash はペイロードの差し替えを防ぐ
推論プロセスconfidence_scorepreprocessing_algoguardrail_actionsヒューマン・イン・ザ・ループのトリアージを支援する(AI Verify アウトカム 4.5)。信頼度が低いレコードは手動レビューに回すことができる

これらのフィールドはドメイン固有の検出データと共に payload オブジェクトに格納されます。AuditRecordpayload_hash はペイロード全体を対象とするため、メタデータフィールドを 1 つでも変更すると署名が無効になります。

ALCOA+ との対応: この 5 カテゴリは規制当局への提出に求められる ALCOA+ データ整合性フレームワークに直接対応します。帰属性(センサー・モデル識別情報)、判読性(構造化 JSON)、同時性(ntp_ts)、原本性(input_data_hash)、正確性(weight_sha256calibration_ts)、加えて完全性・一貫性・耐久性・可用性(WORM ストレージコネクタが担保)。

インジェストサービス:同期・非同期パス

edgesentry-rs はフィーチャーフラグで選択できるクラウド側インジェスト用オーケストレーションサービスを 2 種類提供します。

フィーチャーフラグスレッドモデル用途
IngestService(常に利用可能)ブロッキング / 同期組み込み・CLI ツール・組み込みランタイム
AsyncIngestServiceasync-ingestasync/await(tokio)HTTP サーバー・非同期パイプライン

同期パス(IngestService

同期サービスはデフォルトであり、追加フィーチャーは不要です。S3 書き込み(s3 フィーチャーが有効な場合)は組み込みの tokio::runtime::Runtime 内で block_on して実行されます。シングルスレッドツールや組み込み環境に適しています。

#![allow(unused)]
fn main() {
let mut svc = IngestService::new(policy, raw_store, ledger, op_log);
svc.register_device("lift-01", verifying_key);
svc.ingest(record, payload, None)?;
}

非同期パス(AsyncIngestService

features = ["async-ingest"] で有効化します。すべてのストレージ呼び出しが .await を使用するため、呼び出しスレッドがブロックされず、高並行パイプラインを実現します。ポリシーゲートは tokio::sync::Mutex でラップされているため、Arc 経由でタスク間で共有できます。

#![allow(unused)]
fn main() {
let svc = Arc::new(AsyncIngestService::new(policy, raw_store, ledger, op_log));
svc.register_device("lift-01", verifying_key).await;
svc.ingest(record, payload, None).await?;
}

s3async-ingest が両方有効な場合、S3CompatibleRawDataStore は AWS SDK フューチャーを直接呼び出して AsyncRawDataStore を実装します。組み込みランタイムは不要です。

フィーチャーフラグ一覧

フラグ追加されるもの
async-ingestAsyncRawDataStoreAsyncAuditLedgerAsyncOperationLogStore トレイト;AsyncIngestService;インメモリ非同期ストア;tokio(sync + macros)
s3S3CompatibleRawDataStore(同期);async-ingest と組み合わせると AsyncRawDataStore も実装
postgresPostgresAuditLedgerPostgresOperationLog(同期)
transport-httptransport::http::serve() — axum ベースの POST /api/v1/ingest サーバー;eds serve CLI サブコマンド
transport-mqtttransport::mqtt::serve_mqtt() — 非同期 rumqttc イベントループ;トピックをサブスクライブし、レコードを AsyncIngestService にルーティングし、承認/拒否レスポンスを発行

トランスポート層

transport モジュールは AsyncIngestService の上に構築されたネットワーク向けインジェストエンドポイントを提供します。

HTTP(transport-http フィーチャー)

features = ["transport-http"] で有効化します。axum 0.8 を取り込み、単一の POST /api/v1/ingest エンドポイントを公開します。

リクエスト / レスポンス

フィールド説明
recordAuditRecord(JSON)デバイスからの署名済み監査レコード
raw_payload_hexString16 進数エンコードされた生ペイロードバイト
ステータス意味
202 Acceptedレコードがすべてのチェックを通過し、保存された
400 Bad Requestraw_payload_hex が有効な 16 進数でない
403 Forbiddenクライアント IP が NetworkPolicy の許可リストにない
422 Unprocessable Entityレコードの署名・ハッシュ・チェーン検証に失敗した

使用例

#![allow(unused)]
fn main() {
use edgesentry_rs::{
    AsyncIngestService, AsyncInMemoryRawDataStore, AsyncInMemoryAuditLedger,
    AsyncInMemoryOperationLog, IntegrityPolicyGate, NetworkPolicy,
};
use edgesentry_rs::transport::http::serve;

let mut policy = IntegrityPolicyGate::new();
policy.register_device("lift-01", verifying_key);

let mut network_policy = NetworkPolicy::new();
network_policy.allow_cidr("10.0.0.0/8").unwrap();

let service = AsyncIngestService::new(
    policy,
    AsyncInMemoryRawDataStore::default(),
    AsyncInMemoryAuditLedger::default(),
    AsyncInMemoryOperationLog::default(),
);

let addr = "0.0.0.0:8080".parse().unwrap();
serve(service, network_policy, addr).await?;
}

CLI

eds serve \
  --addr 0.0.0.0:8080 \
  --allowed-sources 10.0.0.0/8,127.0.0.1 \
  --device lift-01=<pubkey_hex>

MQTT(transport-mqtt フィーチャー)

features = ["transport-mqtt"] で有効化します。rumqttc を取り込み、serve_mqtt() を公開します。serve_mqtt() は MQTT ブローカーに接続し、設定可能なインジェストトピックをサブスクライブし、受信メッセージを AsyncIngestService にルーティングする完全非同期イベントループです。

メッセージ形式は HTTP トランスポートと同じ JSON エンベロープです:

{ "record": { "device_id": "...", "sequence": 1, ... }, "raw_payload_hex": "deadbeef..." }

承認/拒否の結果は <topic>/response に発行されます:

{ "device_id": "...", "sequence": 1, "status": "accepted" }
{ "device_id": "...", "sequence": 1, "status": "rejected", "error": "..." }

使用例

#![allow(unused)]
fn main() {
use edgesentry_rs::transport::mqtt::{MqttIngestConfig, serve_mqtt};
use edgesentry_rs::{
    AsyncIngestService, AsyncInMemoryRawDataStore, AsyncInMemoryAuditLedger,
    AsyncInMemoryOperationLog, IntegrityPolicyGate,
};

let service = AsyncIngestService::new(
    IntegrityPolicyGate::new(),
    AsyncInMemoryRawDataStore::default(),
    AsyncInMemoryAuditLedger::default(),
    AsyncInMemoryOperationLog::default(),
);

let config = MqttIngestConfig::new("mqtt.example.com", "devices/+/ingest", "edgesentry-cloud");
serve_mqtt(config, service).await?;
}

serve_mqtt はブローカー接続が切断されるまで実行し、MqttServeError::EventLoop を返します。自動再接続にはリトライループでラップしてください。

主な動作

動作詳細
不正な JSONメッセージはログに記録され破棄される;イベントループは継続する
無効な 16 進数ペイロードメッセージはログに記録され破棄される;イベントループは継続する
インジェスト拒否"status": "rejected" を含むレスポンスを <topic>/response に発行する
レスポンス発行失敗警告としてログに記録される;イベントループは停止しない

ライブラリの使い方

ライブラリ API を直接使って実装されたエンドツーエンドのエレベーター点検サンプルを実行します。

前提条件:

  • Rust ツールチェーン(cargo
  • このサンプルには PostgreSQL / MinIO は 不要 です(インメモリストアを使用)
cargo run -p edgesentry-rs --example lift_inspection_flow

サンプルが対象とするシナリオ:

  1. IntegrityPolicyGateに 1 台のエレベーターデバイスの公開鍵を登録する
  2. build_signed_recordで 3 件の署名済み点検レコードを生成する
  3. IngestService経由ですべてのレコードをインジェストする(承認パス)
  4. 1 件のレコードを改ざん(payload_hash)して拒否されることを確認する
  5. 保存された監査レコードと操作ログを出力する

デモの内容:

  • edgesentry_rs::build_signed_recordによるレコード署名
  • edgesentry_rs::ingest::IngestServiceによるインジェスト検証
  • 改ざん拒否(変更されたpayload_hash
  • 監査レコードと操作ログの出力

ソース:

  • crates/edgesentry-rs/examples/lift_inspection_flow.rs

3 ロール分散デモ

エッジからクラウドへのフローをより現実的に確認するために、 3 つのサンプルを順番に実行できます。それぞれのサンプルはちょうど 1 つのロールを担います。

サンプルロール外部依存
edge_deviceレコードに署名し/tmp/eds_*.jsonに書き込むなし
edge_gatewayレコードをルーティングするが暗号検証は行わないなし
cloud_backendNetworkPolicy + IngestService + ストレージなし(インメモリ)または PostgreSQL + MinIO (--features s3,postgres

順番に実行:

cargo run -p edgesentry-rs --example edge_device
cargo run -p edgesentry-rs --example edge_gateway
cargo run -p edgesentry-rs --example cloud_backend

各サンプルは前のサンプルの出力ファイルを/tmp/から読み込みます。実際のバックエンドを使用したフル実行( Docker が必要 — インタラクティブデモを参照):

cargo run -p edgesentry-rs --example edge_device
cargo run -p edgesentry-rs --example edge_gateway
cargo run -p edgesentry-rs --features s3,postgres --example cloud_backend

このシーケンスのデモ内容:

  • edge_devicebuild_signed_recordによるデバイス側署名。拒否デモ用の改ざんコピーも書き込む
  • edge_gateway — ゲートウェイはレコードを受信するが署名を検証しない(ルーティング専任)
  • cloud_backendNetworkPolicy::checkがすべてのIngestService::ingestの前に実行される。承認・拒否されたレコードの両方が確認できる

ソース:

  • crates/edgesentry-rs/examples/edge_device.rs
  • crates/edgesentry-rs/examples/edge_gateway.rs
  • crates/edgesentry-rs/examples/cloud_backend.rs

S3 / MinIO の切り替え

edgesentry-rss3フィーチャーフラグで切り替え可能な S3 互換生データバックエンドをサポートしています。

  • S3Backend::AwsS3: AWS S3 を使用(デフォルトの AWS 認証情報チェーン、またはオプションの静的キー)
  • S3Backend::Minio: MinIO を使用(カスタムエンドポイント + 静的アクセスキー/シークレット)

インジェストレイヤーは共通の生データストレージ抽象化に対してコーディングされており、具体的な設定によってインジェストのビジネスロジックを変更せずに AWS S3 または MinIO を選択します。

edgesentry_rsから以下の型を使用します。

  • S3ObjectStoreConfig::for_aws_s3(...)
  • S3ObjectStoreConfig::for_minio(...)
  • S3CompatibleRawDataStore::new(config)

S3 フィーチャーを有効にしてビルド・テスト:

cargo test -p edgesentry-rs --features s3

ライブ MinIO インスタンスに対して S3 統合テストを実行するには、環境変数を設定して専用のテストファイルを実行します。

TEST_S3_ENDPOINT=http://localhost:9000 \
TEST_S3_ACCESS_KEY=minioadmin \
TEST_S3_SECRET_KEY=minioadmin \
TEST_S3_BUCKET=bucket \
cargo test -p edgesentry-rs --features s3 --test integration -- --nocapture

4 つのTEST_S3_*変数のいずれかが未設定の場合、テストは自動的にスキップされます。

インタラクティブローカルデモ

注意:ライブラリのみのサンプルとは異なり、本デモには PostgreSQL と MinIO が 必要 です。

3 ロールモデル

EdgeSentry-RS は 3 つの明確なロールを中心に設計されています。デモ出力を正しく読むためには、各ステップがどのロールに属するかを理解することが重要です。

ロール責務本デモでの担当
エッジデバイスEd25519 秘密鍵で点検レコードに署名し、クラウドへ送出するexamples/edge_device.rs
エッジゲートウェイ署名済みレコードをデバイスから HTTPS/MQTT 経由でクラウドへ転送する。コンテンツは検証しないexamples/edge_gateway.rs — HTTP トランスポートはスコープ外。ディスク上のファイルがトランスポートをシミュレートする
クラウドバックエンドNetworkPolicy( CLS-06 )を強制し、IntegrityPolicyGate(ルート同一性 → 署名 → シーケンス → ハッシュチェーン)を実行し、承認されたレコードを永続化するexamples/cloud_backend.rs--features s3,postgres付き)

本デモの内容

スクリプトは Docker サービスを起動し、 3 つのロールサンプルを順番に実行します。

ステップロール内容
1 〜 3インフラDocker Compose で PostgreSQL + MinIO を起動。ヘルスチェックを待機
4エッジデバイスedge_device — 3 件のレコードに署名し/tmp/eds_*.jsonに書き込む
5エッジゲートウェイedge_gateway — デバイスの出力を読み込み、変更せずに/tmp/eds_fwd_*.jsonへ転送する
6クラウドバックエンドcloud_backendNetworkPolicyチェック → IngestService → PostgreSQL + MinIO 。改ざん拒否も表示
7クラウドバックエンドPostgreSQL から永続化された監査レコードと操作ログを照会する
8インフラDocker サービスを停止する

前提条件:

  • Docker / Docker Compose
  • Rust ツールチェーン(cargo

エンドツーエンドデモを実行:

bash scripts/local_demo.sh

スクリプトは各ステップの後に一時停止し、 Enter キー(またはOK)を押すまで次のステップへ進みません。 フロー終了時にシャットダウンステップ(docker compose -f docker-compose.local.yml down)が実行されます。

ロールサンプルの個別実行

各サンプルは Docker なしでスタンドアロンで実行することも可能です(クラウドバックエンドにインメモリストレージを使用)。

# ステップ1:エッジデバイスがレコードに署名する
cargo run -p edgesentry-rs --example edge_device

# ステップ2:エッジゲートウェイがレコードを転送する
cargo run -p edgesentry-rs --example edge_gateway

# ステップ3a:クラウドバックエンド(インメモリ — Docker不要)
cargo run -p edgesentry-rs --example cloud_backend

# ステップ3b:クラウドバックエンド(PostgreSQL + MinIO — Dockerが必要)
cargo run -p edgesentry-rs --features s3,postgres --example cloud_backend

各サンプルは前のサンプルの出力ファイルを/tmp/から読み込みます。順番に実行してください。

手動での確認

ステップ 6 の後に PostgreSQL に接続:

docker exec -it edgesentry-rs-postgres psql -U trace -d trace_audit

psql内で:

SELECT id, device_id, sequence, object_ref, ingested_at FROM audit_records ORDER BY sequence;
SELECT id, decision, device_id, sequence, message, created_at FROM operation_logs ORDER BY id;

MinIO のエンドポイント:

  • API: http://localhost:9000
  • コンソール: http://localhost:9001
  • デフォルト認証情報: minioadmin / minioadmin
  • セットアップコンテナが作成するバケット: bucket

ローカルバックエンドの手動停止(スクリプトを途中で中断した場合のみ):

docker compose -f docker-compose.local.yml down

次のステップ

ローカルデモから本番環境へ移行する準備ができたら、本番デプロイガイドを参照してください。TLS 証明書管理、PostgreSQL チューニング、S3/MinIO ライフサイクルルール、systemd サービスユニット、水平スケーリングについて説明しています。

本番デプロイガイド

このガイドでは、ローカル Docker Compose デモから eds serve(HTTP/TLS)および eds serve-mqtt の本番グレードデプロイへの移行について説明します。ローカルのクイックスタートはインタラクティブデモを、オブザーバビリティ・アラート・バックアップ/リストア手順については運用ランブックを参照してください。


前提条件

コンポーネント最小バージョン備考
edgesentry-rs バイナリ現行 mainHTTPS には --features transport-http,transport-tls でビルド;MQTT には transport-mqtt を追加
PostgreSQL14監査台帳および操作ログ
S3 互換ストアAWS S3、MinIO ≥ RELEASE.2023、または Cloudflare R2
(任意)MQTT ブローカーMosquitto ≥ 2.0eds serve-mqtt に必要な場合のみ

1 — TLS 証明書管理

1.1 Let’s Encrypt によるプロビジョニング(推奨)

# certbot をインストール
apt install certbot

# イングレストエンドポイント用の証明書を発行
certbot certonly --standalone \
  -d ingest.example.com \
  --agree-tos --non-interactive \
  -m ops@example.com

# 証明書の出力先:
#   /etc/letsencrypt/live/ingest.example.com/fullchain.pem  (証明書 + チェーン)
#   /etc/letsencrypt/live/ingest.example.com/privkey.pem    (秘密鍵)

1.2 TLS を有効にした eds serve-tls の起動

eds serve-tls \
  --addr 0.0.0.0:8443 \
  --tls-cert /etc/letsencrypt/live/ingest.example.com/fullchain.pem \
  --tls-key  /etc/letsencrypt/live/ingest.example.com/privkey.pem \
  --allowed-sources 10.0.0.0/8 \
  --device lift-01=<PUBLIC_KEY_HEX>

eds serve-tls は rustls 経由で TLS 1.2 最小・TLS 1.3 優先を適用します。追加設定は不要です。

1.3 証明書のローテーション(ゼロダウンタイム)

eds serve-tls は起動時にのみ証明書ファイルを読み込みます。ダウンタイムなしのローテーション手順:

# 1. 証明書を更新
certbot renew --quiet

# 2. 実行中プロセスに SIGTERM を送信(systemd が再起動を処理)
systemctl reload edgesentry
# — または systemd を使わない場合 —
kill -TERM $(pidof eds)
# プロセスが正常終了し、スーパーバイザー/systemd が再起動して新しい証明書を読み込む

cron/systemd タイマーを追加して更新を自動化:

# /etc/systemd/system/certbot.timer
[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target
systemctl enable --now certbot.timer

1.4 自己署名証明書(内部/エアギャップ環境)

# 10 年の自己署名証明書を生成
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
  -nodes -keyout server.key -out server.crt \
  -subj "/CN=ingest.internal" \
  -addext "subjectAltName=IP:10.0.1.5,DNS:ingest.internal"

server.crt を信頼 CA としてすべてのエッジデバイスに配布してください。


2 — PostgreSQL: スキーマ・インデックス・接続数設定

2.1 スキーママイグレーション

スキーマは db/init/001_schema.sql にあります。本番データベースに適用してください:

psql "$DATABASE_URL" -f db/init/001_schema.sql

スキーマは冪等(CREATE TABLE IF NOT EXISTS)であり、再実行しても安全です。

2.2 推奨インデックス

ベーススキーマには UNIQUE (device_id, sequence) 制約が含まれており、これが B-tree インデックスを兼ねてデータベースレベルでリプレイ攻撃を拒否します。一般的なクエリパターン向けに以下のインデックスを追加してください:

-- デバイスごとの最新レコード検索(チェーンヘッドクエリ)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_device_seq
    ON audit_records (device_id, sequence DESC);

-- コンプライアンスレポート用の時間範囲クエリ
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_ingested_at
    ON audit_records (ingested_at);

-- 決定種別によるオペレーションログフィルタリング
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_oplog_decision_device
    ON operation_logs (decision, device_id, created_at DESC);

CONCURRENTLY を指定することで、本番環境でテーブルをロックせずにインデックスを作成できます。

2.3 接続プールのサイジング

PostgresAuditLedgerPostgresOperationLog はそれぞれ postgres クレート経由で 1 つの同期接続を開きます。マルチノードデプロイ(§5 参照)では各 eds プロセスが 2 つの接続を保持します。postgresql.confmax_connections を以下のように設定してください:

max_connections = 2 × <eds インスタンス数> + 10   # psql・監視用のヘッドルーム

高速なイングレストレート(500 レコード/秒超)の場合、カスタム AsyncAuditLedger 実装として sqlx + PgPool を使用した非同期接続プールに置き換えてください。

2.4 長期保持のためのパーティショニング

テーブルが 1 億行を超えると予想される場合は、ingested_ataudit_records をパーティション分割してください:

-- 範囲パーティションテーブルに変換(データ蓄積前に一度実行)
CREATE TABLE audit_records_new (LIKE audit_records INCLUDING ALL)
    PARTITION BY RANGE (ingested_at);

CREATE TABLE audit_records_2026_q1
    PARTITION OF audit_records_new
    FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');

-- アタッチ、スワップ、削除
ALTER TABLE audit_records RENAME TO audit_records_old;
ALTER TABLE audit_records_new RENAME TO audit_records;
DROP TABLE audit_records_old;

3 — オブジェクトストレージ: バケットポリシーとライフサイクルルール

3.1 AWS S3 — バケットポリシー(最小権限)

イングレストサービス専用の IAM ロールを書き込み専用アクセスで作成してください:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "IngestWriteOnly",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::edgesentry-audit/*"
    },
    {
      "Sid": "ListBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::edgesentry-audit"
    }
  ]
}

コンプライアンス監査者には別の読み取り専用ロールを付与してください。

3.2 ライフサイクルルール(保持期間 + コスト管理)

{
  "Rules": [
    {
      "Id": "TransitionToIA",
      "Status": "Enabled",
      "Filter": { "Prefix": "" },
      "Transitions": [
        { "Days": 90,  "StorageClass": "STANDARD_IA" },
        { "Days": 365, "StorageClass": "GLACIER_IR" }
      ]
    },
    {
      "Id": "ExpireOldObjects",
      "Status": "Enabled",
      "Filter": { "Prefix": "" },
      "Expiration": { "Days": 2555 }
    }
  ]
}

CLI で適用:

aws s3api put-bucket-lifecycle-configuration \
  --bucket edgesentry-audit \
  --lifecycle-configuration file://lifecycle.json

3.3 MinIO(オンプレミス)

# オブジェクトロック付きでバケットを作成(コンプライアンス向け不変性)
mc mb --with-lock minio/edgesentry-audit

# ライフサイクル設定: 90 日後に低コストティアへ移行
mc ilm import minio/edgesentry-audit <<EOF
{
  "Rules": [{
    "ID": "expire-3-years",
    "Status": "Enabled",
    "Expiration": { "Days": 1095 }
  }]
}
EOF

# 保存時のサーバーサイド暗号化
mc encrypt set sse-s3 minio/edgesentry-audit

4 — プロセス管理

4.1 systemd サービスユニット(HTTP + TLS)

# /etc/systemd/system/edgesentry.service
[Unit]
Description=EdgeSentry-RS ingest server
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=exec
User=edgesentry
Group=edgesentry
ExecStart=/usr/local/bin/eds serve-tls \
    --addr 0.0.0.0:8443 \
    --tls-cert /etc/edgesentry/server.crt \
    --tls-key  /etc/edgesentry/server.key \
    --allowed-sources 10.0.0.0/8 \
    --device lift-01=<PUBLIC_KEY_HEX>
Restart=on-failure
RestartSec=5
Environment=RUST_LOG=edgesentry_rs=info

# セキュリティ強化
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/edgesentry
PrivateTmp=true
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target
# インストールと起動
install -m 755 target/release/eds /usr/local/bin/eds
useradd --system --no-create-home edgesentry
mkdir -p /var/log/edgesentry && chown edgesentry:edgesentry /var/log/edgesentry

systemctl daemon-reload
systemctl enable --now edgesentry
systemctl status edgesentry

4.2 systemd サービスユニット(MQTT)

# /etc/systemd/system/edgesentry-mqtt.service
[Unit]
Description=EdgeSentry-RS MQTT ingest subscriber
After=network-online.target mosquitto.service
Wants=network-online.target

[Service]
Type=exec
User=edgesentry
Group=edgesentry
ExecStart=/usr/local/bin/eds serve-mqtt \
    --broker 10.0.1.10 \
    --port 1883 \
    --topic edgesentry/ingest \
    --client-id eds-prod-1 \
    --device lift-01=<PUBLIC_KEY_HEX>
Restart=on-failure
RestartSec=10
Environment=RUST_LOG=edgesentry_rs=info

[Install]
WantedBy=multi-user.target

4.3 ヘルスチェック

eds serve 自体は /health エンドポイントを公開しません。ロードバランサーまたは監視エージェントで TCP チェックを設定してください:

# TLS ポートが接続を受け入れていることを確認
openssl s_client -connect ingest.example.com:8443 -verify_return_error </dev/null
echo $?   # 0 = 正常

Kubernetes では tcpSocket liveness probe を使用してください:

livenessProbe:
  tcpSocket:
    port: 8443
  initialDelaySeconds: 5
  periodSeconds: 15

5 — 水平スケーリング

5.1 アーキテクチャ

                      ┌─────────────────┐
Edge devices  ──TLS──►│  ロードバランサー │
                      │  (nginx /       │
                      │   AWS ALB など) │
                      └────────┬────────┘
                               │  ラウンドロビン
                ┌──────────────┼──────────────┐
                ▼              ▼              ▼
         ┌────────────┐ ┌────────────┐ ┌────────────┐
         │  eds serve │ │  eds serve │ │  eds serve │
         │  ノード 1  │ │  ノード 2  │ │  ノード 3  │
         └──────┬─────┘ └──────┬─────┘ └──────┬─────┘
                └──────────────┼──────────────┘
                               │
                ┌──────────────┼──────────────┐
                ▼              ▼              ▼
         ┌─────────┐    ┌──────────┐   ┌─────────┐
         │Postgres │    │  S3 /    │   │ MinIO   │
         │(プライマリ)│    │  バケット │   │ クラスター │
         └─────────┘    └──────────┘   └─────────┘

5.2 主要な特性

  • IngestState はプロセスごと。eds serve ノードは独自のインメモリシーケンス/ハッシュチェーン状態を保持します。PostgreSQL の UNIQUE (device_id, sequence) 制約がクロスノードのリプレイフェンスとなり、重複インサートは unique-violation エラーとなって PostgresAuditLedger がストアエラーとして通知し、イングレストが拒否・ログ記録されます。
  • スティッキーセッション不要。 シーケンス強制は DB レベルで行われ、どのノードもどのデバイスのリクエストも処理できます。
  • S3/MinIO への書き込みはステートレス。 すべてのノードが同一バケットに書き込みます。オブジェクトキーは object_ref から導出され、エッジデバイスが設定し、慣例として(例:<device_id>/<sequence>.bin)グローバルに一意です。

5.3 nginx TLS 終端 + アップストリームプロキシ

upstream edgesentry_nodes {
    least_conn;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
    server 10.0.1.13:8080;
}

server {
    listen 443 ssl;
    server_name ingest.example.com;

    ssl_certificate     /etc/letsencrypt/live/ingest.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ingest.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    location /api/v1/ingest {
        proxy_pass         http://edgesentry_nodes;
        proxy_set_header   X-Forwarded-For $remote_addr;
        proxy_read_timeout 10s;
    }
}

各ノードで eds serve(プライベートポートでのプレーン HTTP)を実行し、nginx に TLS 終端を任せてください。--allowed-sources には nginx アップストリームの IP 範囲を指定します。リバースプロキシを使わずに組み込み TLS を利用する場合は eds serve-tls を使用してください。

注意: TLS がロードバランサーで終端される場合、eds serve はデバイスの IP ではなく LB の IP を認識します。--allowed-sources を LB の内部アドレス範囲に設定し、デバイスごとのソース制御は LB 側のアローリストに委ねてください。

5.4 レポート用 PostgreSQL リードレプリカ

書き込みパス(イングレスト):プライマリのみ。 読み取りパス(コンプライアンスクエリ、チェーン検証):リードレプリカに直接アクセス。

# コンプライアンスツール用のリードレプリカ接続
psql "postgres://audit_ro:pass@pg-replica:5432/audit?sslmode=require"

6 — オブザーバビリティ

構造化ログとトレーシングは tracing ファサードで処理されます。JSON ログフォーマット、ライブラリが出力する構造化イベントフィールド、Prometheus メトリクス導出、OpenTelemetry スパン設定を含む詳細なセットアップは運用ランブック — オブザーバビリティを参照してください。

クイックスタート: stdout への JSON ログ出力(Loki / CloudWatch 向け)

# バイナリラッパーの Cargo.toml
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
# JSON ログで eds を実行
RUST_LOG=edgesentry_rs=info eds serve ... 2>&1 | \
  promtail --stdin --client.url http://loki:3100/loki/api/v1/push

アラート対象の主要ログフィールド

フィールドアラート条件
message"MQTT record rejected" / "record rejected"5 分間の拒否率 > 1 %
reason"invalid signature"任意の発生 — 改ざんの試みの可能性
reason"unknown device"継続的な発生 — 未登録デバイスの探索
message"MQTT event loop error"任意の発生 — ブローカー接続が切断

Prometheus アラートルールについては運用ランブック — アラート定義を参照してください。


関連ドキュメント

CLI リファレンス

eds は EdgeSentry の統合 CLI です。監査コマンドはすべて eds audit サブコマンド配下にあり、スキャン点検コマンドは eds inspect 配下にあります。

eds audit <command>    — 改ざん検知付き監査レコード操作
eds inspect <command>  — 3D スキャン vs. IFC 偏差・AI 検出パイプライン

インストール

エンドユーザー向け — ビルド済みバイナリ

最新リリースを GitHub Releases ページ からダウンロードしてください。

プラットフォームファイル
Linux (x86-64)eds-{version}-x86_64-unknown-linux-gnu.tar.gz
macOS (Apple Silicon)eds-{version}-aarch64-apple-darwin.tar.gz
Windows (x86-64)eds-{version}-x86_64-pc-windows-msvc.zip

展開して eds バイナリを PATH に追加してください:

# Linux / macOS
tar -xzf eds-{version}-{target}.tar.gz
sudo mv eds /usr/local/bin/
eds --help
# Windows(PowerShell)
Expand-Archive eds-{version}-x86_64-pc-windows-msvc.zip
# eds.exe を PATH が通ったディレクトリに移動してください
eds --help

開発者向け — ソースからインストール

Rust(stable ツールチェーン)が必要です。

cargo install --git https://github.com/edgesentry/edgesentry-rs --locked --bin eds

オプションのトランスポートフィーチャーを含めてインストールする場合:

cargo install --git https://github.com/edgesentry/edgesentry-rs --locked --bin eds \
  --features transport-http,transport-tls

インストールの確認:

eds --version
eds --help

デバイスプロビジョニング

新しいデバイス用に Ed25519 キーペアを生成:

eds audit keygen

ファイルに直接保存:

eds audit keygen --out device-lift-01.key.json

既存の秘密鍵から公開鍵を導出:

eds audit inspect-key \
  --private-key-hex 0101010101010101010101010101010101010101010101010101010101010101

完全なプロビジョニングおよびローテーションのワークフローについては鍵管理を参照してください。


CLI の使い方

ヘルプを表示:

eds --help
eds audit --help

署名済みレコードを作成してrecord1.jsonに保存:

eds audit sign-record \
  --device-id lift-01 \
  --sequence 1 \
  --timestamp-ms 1700000000000 \
  --payload "door-open" \
  --object-ref "s3://bucket/lift-01/1.bin" \
  --private-key-hex 0101010101010101010101010101010101010101010101010101010101010101 \
  --out record1.json

1 件のレコードの署名を検証:

eds audit verify-record \
  --record-file record1.json \
  --public-key-hex 8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c

JSON 配列ファイルからチェーン全体を検証:

eds audit verify-chain --records-file records.json

エレベーター点検シナリオ( CLI エンドツーエンド)

このシナリオは 3 つのチェックによるリモートエレベーター点検をシミュレートします。

  1. ドア開閉サイクルチェック
  2. 振動チェック
  3. 非常ブレーキ応答チェック

1) 1 回の点検セッション用の署名済みチェーン全体を生成する

eds audit demo-lift-inspection \
  --device-id lift-01 \
  --out-file lift_inspection_records.json

期待される出力:

DEMO_CREATED:lift_inspection_records.json
CHAIN_VALID

2) ファイルからチェーンの完全性を検証する

eds audit verify-chain --records-file lift_inspection_records.json

期待される出力:

CHAIN_VALID

2.1) チェーンファイルを改ざんして検知されることを確認する

最初のレコードのハッシュ値をインプレースで変更:

python3 - <<'PY'
import json

path = "lift_inspection_records.json"
with open(path, "r", encoding="utf-8") as f:
  records = json.load(f)

records[0]["payload_hash"][0] ^= 0x01

with open(path, "w", encoding="utf-8") as f:
  json.dump(records, f, indent=2)
print("tampered", path)
PY

再度チェーン検証を実行:

eds audit verify-chain --records-file lift_inspection_records.json

期待される結果:コマンドが非ゼロコードで終了し、chain verification failed: invalid previous hash ...のようなエラーを出力します。

3) 1 件の署名済み点検イベントを作成して検証する

1 件の署名済みイベントを生成:

eds audit sign-record \
  --device-id lift-01 \
  --sequence 1 \
  --timestamp-ms 1700000000000 \
  --payload "scenario=lift-inspection,check=door,status=ok" \
  --object-ref "s3://bucket/lift-01/door-check-1.bin" \
  --private-key-hex 0101010101010101010101010101010101010101010101010101010101010101 \
  --out lift_single_record.json

署名を検証:

eds audit verify-record \
  --record-file lift_single_record.json \
  --public-key-hex 8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c

期待される出力:

VALID

3.1) 1 件のレコードの署名を改ざんして拒否されることを確認する

署名の 1 バイトを変更:

python3 - <<'PY'
import json

path = "lift_single_record.json"
with open(path, "r", encoding="utf-8") as f:
  record = json.load(f)

record["signature"][0] ^= 0x01

with open(path, "w", encoding="utf-8") as f:
  json.dump(record, f, indent=2)
print("tampered", path)
PY

再度署名を検証:

eds audit verify-record \
  --record-file lift_single_record.json \
  --public-key-hex 8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c

期待される出力:

INVALID

サーバーコマンド

eds audit serve — HTTP インジェストサーバー

transport-http Cargo フィーチャーが必要です。

フラグデフォルト説明
--addr0.0.0.0:8080バインドするソケットアドレス
--allowed-sources127.0.0.1接続を許可する CIDR / IP のカンマ区切りリスト
--device ID=PUBKEY_HEX(なし)デバイスを登録;複数デバイスは繰り返し指定
eds audit serve \
  --addr 0.0.0.0:8080 \
  --allowed-sources 10.0.0.0/8 \
  --device lift-01=<PUBLIC_KEY_HEX>

ポート 8080 でプレーン HTTP を提供します。TLS 終端リバースプロキシの背後で使用するか、組み込み TLS には eds audit serve-tls を使用してください。


eds audit serve-tls — HTTPS インジェストサーバー(TLS 1.2/1.3)

transport-tls Cargo フィーチャーが必要です。

フラグデフォルト説明
--addr0.0.0.0:8443バインドするソケットアドレス
--allowed-sources127.0.0.1接続を許可する CIDR / IP のカンマ区切りリスト
--device ID=PUBKEY_HEX(なし)デバイスを登録;複数デバイスは繰り返し指定
--tls-cert(必須)PEM 証明書チェーンのパス(リーフ証明書を先頭に)
--tls-key(必須)PEM 秘密鍵のパス(PKCS #8 または PKCS #1 RSA)
eds audit serve-tls \
  --addr 0.0.0.0:8443 \
  --allowed-sources 10.0.0.0/8 \
  --device lift-01=<PUBLIC_KEY_HEX> \
  --tls-cert /etc/edgesentry/server.crt \
  --tls-key  /etc/edgesentry/server.key

rustls の TLS 1.2/1.3 を使用します。ネットワークポリシー(IP アローリスト)は TLS ハンドシェイク前の TCP 接続受け入れ時点で適用されます。


eds audit serve-mqtt — MQTT インジェストサブスクライバー

transport-mqtt Cargo フィーチャーが必要です。MQTTS には transport-mqtt-tls も追加してください。

フラグデフォルト説明
--brokerlocalhostMQTT ブローカーホスト
--port1883MQTT ブローカーポート(MQTTS には 8883 を使用)
--topicedgesentry/ingestインジェストレコードを受信するトピック
--client-ideds-serverMQTT クライアント識別子
--device ID=PUBKEY_HEX(なし)デバイスを登録;複数デバイスは繰り返し指定
--tls-ca-cert(なし)MQTTS ブローカー検証用 PEM CA 証明書のパス(transport-mqtt-tls のみ)
# プレーン MQTT(ポート 1883)
eds audit serve-mqtt \
  --broker broker.example.com \
  --port 1883 \
  --topic edgesentry/ingest \
  --device lift-01=<PUBLIC_KEY_HEX>

# MQTTS(ポート 8883、transport-mqtt-tls フィーチャーが必要)
eds audit serve-mqtt \
  --broker broker.example.com \
  --port 8883 \
  --tls-ca-cert /etc/edgesentry/ca.crt \
  --device lift-01=<PUBLIC_KEY_HEX>

レスポンスは <topic>/responsestatus: "accepted" または status: "rejected" の JSON として発行されます。


インジェストデモ( PostgreSQL + MinIO )

s3およびpostgresの Cargo フィーチャーと、実行中の PostgreSQL + MinIO インスタンスが必要です(docker compose -f docker-compose.local.yml up -dを使用)。

1) ペイロードファイル付きのチェーンを生成する

eds audit demo-lift-inspection \
  --device-id lift-01 \
  --out-file lift_inspection_records.json \
  --payloads-file lift_inspection_payloads.json

2) IngestService 経由でレコードをインジェストする

eds audit demo-ingest \
  --records-file lift_inspection_records.json \
  --payloads-file lift_inspection_payloads.json \
  --device-id lift-01 \
  --pg-url postgresql://trace:trace@localhost:5433/trace_audit \
  --minio-endpoint http://localhost:9000 \
  --minio-bucket bucket \
  --minio-access-key minioadmin \
  --minio-secret-key minioadmin \
  --reset

--resetはインジェスト前にaudit_recordsoperation_logsを truncate します。既存の実行に追記するにはこのオプションを省略してください。

同じIngestServiceを通じた改ざんされたチェーンの拒否もデモするには--tampered-records-file <path>を渡してください。

PostgreSQL と MinIO を使った完全なガイド付きウォークスルーはインタラクティブデモを参照してください。

鍵管理

このページでは、 EdgeSentry-RS が使用する Ed25519 デバイス鍵のライフサイクル全体を説明します。 鍵の生成・安全な保存・公開鍵の登録・ローテーションが対象です。

関連標準: Singapore CLS-04 / ETSI EN 303 645 §5.4 / JC-STAR STAR-1 R1.2 。


1. 鍵の生成

eds CLI を使って新しい Ed25519 キーペアを生成:

eds audit keygen

出力例:

{
  "private_key_hex": "ddca9848801c658d62a010c4d306d6430a0cdc2c383add1628859258e3acfb93",
  "public_key_hex": "4bb158f302c0ad9261c0acfa95e17144ae7249eb0973bbfaeae4501165887a77"
}

ファイルに保存:

eds audit keygen --out device-lift-01.key.json

各デバイスは 一意の キーペアを持たなければなりません。デバイスをまたいで鍵を再利用しないでください。


2. 既存の秘密鍵から公開鍵を導出する

private_key_hexをすでに持っており、対応する公開鍵を確認したい場合:

eds audit inspect-key --private-key-hex <64-hex-char-private-key>

例:

eds audit inspect-key \
  --private-key-hex 0101010101010101010101010101010101010101010101010101010101010101

出力:

{
  "private_key_hex": "0101010101010101010101010101010101010101010101010101010101010101",
  "public_key_hex": "8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c"
}

3. 秘密鍵の安全な保存

秘密鍵はデバイス上で秘密にしておかなければなりません。推奨される実践:

環境推奨ストレージ
開発 / CI環境変数(DEVICE_PRIVATE_KEY_HEX)— バージョン管理にコミットしないこと
本番(ソフトウェア)暗号化されたシークレットストア(例: HashiCorp Vault 、 AWS Secrets Manager 、 Azure Key Vault )
本番(ハードウェア)ハードウェアセキュリティモジュール( HSM )またはトラステッド実行環境( TEE )— 計画中の HSM パスについては#54を参照

ファイルベースのストレージ(開発環境のみ):

chmod 600 device-lift-01.key.json

private_key_hexをログ・ HTTP レスポンス・エラーメッセージに含めないでください。


4. 公開鍵の登録(クラウド側)

キーペアを生成した後、レコードがインジェストされる前にデバイスの公開鍵をIntegrityPolicyGateに登録します:

#![allow(unused)]
fn main() {
use edgesentry_rs::{IntegrityPolicyGate, parse_fixed_hex};
use ed25519_dalek::VerifyingKey;

let public_key_bytes = parse_fixed_hex::<32>(&public_key_hex)?;
let verifying_key = VerifyingKey::from_bytes(&public_key_bytes)?;

let mut gate = IntegrityPolicyGate::new();
gate.register_device("lift-01", verifying_key);
}

register_deviceに渡すdevice_id文字列は、そのデバイスが署名するすべてのAuditRecorddevice_idフィールドと完全に一致しなければなりません。

未知のdevice_idからのレコードはIngestError::UnknownDeviceで拒否されます。


5. 鍵のローテーション

以下の場合にデバイス鍵をローテーションしてください。

  • 秘密鍵が漏洩した可能性がある
  • デバイスが廃棄されて再プロビジョニングされる
  • セキュリティポリシーで定期的なローテーションが求められている

ローテーション手順:

  1. 新しいデバイス設定用に新しいキーペアを生成:

    eds audit keygen --out device-lift-01-v2.key.json
    
  2. 新しい公開鍵を古い鍵と並べて登録します(ゲートはdevice_idあたり複数の鍵をまだサポートしていません。移行ウィンドウ中はlift-01-v2のような新しいdevice_idで登録してください)。

  3. デバイスを更新して、新しい秘密鍵と新しいdevice_idで新しいレコードに署名するようにします。

  4. 古い鍵で署名されたすべての処理中のレコードがインジェストされて検証されたら、ポリシーゲートから古いデバイス登録を削除します。

  5. すべてのストレージから古い秘密鍵を安全に削除または失効させます。

注意: 同じdevice_idで古い鍵と新しい鍵を同時に許可するマルチキー・パー・デバイスのサポートは#57で追跡されています。


6. ソフトウェアアップデートのパブリッシャー鍵

ソフトウェアアップデートの検証には、デバイス署名鍵とは別の Ed25519 鍵セットを使用します。 パブリッシャー鍵 はファームウェアまたはソフトウェアパッケージに署名するエンティティに属し、 デバイス署名鍵 は監査レコードに署名する個々のデバイスに属します。これらのロールを混在させないでください。

6.1 鍵の生成と保存

デバイスキーペアと同じ方法でパブリッシャーキーペアを生成:

eds audit keygen --out publisher-acme-firmware.key.json

秘密鍵 は高セキュリティのオフライン環境( HSM ・エアギャップワークステーション、または厳格なアクセス制御付きのシークレットマネージャー)に保管しなければなりません。リリースアーティファクトへの署名のためにビルド時にのみ使用し、デバイス自体には置きません。

公開鍵 は製造時にデバイスのファームウェアイメージに埋め込まれ、実行時にUpdateVerifierにロードされます:

#![allow(unused)]
fn main() {
use edgesentry_rs::update::UpdateVerifier;
use ed25519_dalek::VerifyingKey;

let public_key_bytes: [u8; 32] = /* bytes baked into firmware */;
let verifying_key = VerifyingKey::from_bytes(&public_key_bytes)?;

let mut verifier = UpdateVerifier::new();
verifier.register_publisher("acme-firmware", verifying_key);
}

6.2 パブリッシャー ID と鍵は 1 対 1 で

各鍵を異なるpublisher_idの下に登録してください。脅威モデルで明示的に要求されない限り、同一の鍵を複数の ID で登録したり、同一の ID で複数の鍵を登録したりすることは避けてください。

#![allow(unused)]
fn main() {
// 正しい:パブリッシャーごとに1つの鍵
verifier.register_publisher("acme-firmware", firmware_key);
verifier.register_publisher("acme-config",   config_key);

// 避けること:パブリッシャー間で鍵を共有すると、
// あるパッケージタイプの署名が別のタイプで受け入れられる可能性がある
verifier.register_publisher("acme-firmware", shared_key); // ⚠
verifier.register_publisher("acme-config",   shared_key); // ⚠
}

6.3 鍵混同攻撃

鍵混同攻撃 は、あるパッケージタイプ向けに生成された署名が別のパッケージタイプの有効な署名として提出されるときに発生します。UpdateVerifierは以下の理由でこれを防ぎます:

  1. 呼び出し元はverify()に明示的なpublisher_idを渡す。
  2. 検証器はその正確な ID の下に登録された鍵を検索する。
  3. acme-configの鍵による署名はacme-firmwareの鍵で検証できない。

これは各パブリッシャーが一意の鍵を持つ場合にのみ成立します。§6.2 で説明したように鍵がパブリッシャー間で共有される場合、この分離は破れます。

6.4 パブリッシャー鍵のローテーション

秘密鍵が漏洩した可能性がある場合、またはセキュリティポリシーで定期的なローテーションが求められる場合にパブリッシャー鍵をローテーションしてください。

  1. オフラインで新しいキーペアを生成する。
  2. 新しい秘密鍵で次のファームウェアリリースに署名する。
  3. 新しい公開鍵を埋め込んで新しい鍵でregister_publisherを呼び出すファームウェアアップデートを配布する。移行ウィンドウ中は古い鍵と新しい鍵の両方を含め、どちらのファームウェアバージョンのデバイスもアップデートを検証できるようにする。
  4. すべてのデバイスが新しいファームウェアに移行したら、古い鍵の登録を削除する。
  5. 古い秘密鍵を安全に破棄する。

6.5 FFI ( C/C++デバイス)

C/C++ FFI ブリッジ経由で統合するデバイスでは、パブリッシャー鍵の検証がeds_verify_updateとして公開される予定です(#80で追跡中)。この関数が利用可能になるまで、 C/C++デバイスはシンラッパー経由で Rust を呼び出すか、アプリケーション層でパブリッシャー検証を処理しなければなりません。

eds_verify_updateに渡す公開鍵バイト列は、上記と同じ 32 バイトの Ed25519 公開鍵です。製造時にデバイスへプロビジョニングし、読み取り専用フラッシュ領域またはセキュアエレメントに保存してください。


7. HSM パス( CLS レベル 4 )

CLS レベル 4 および高保証デプロイメントでは、秘密鍵は抽出可能なバイト配列として存在すべきではありません。代わりに、署名操作は HSM または TEE 内部で実行し、秘密鍵材料がセキュア境界から外に出ないようにする必要があります。

計画中のedgesentry-bridge C/C++ FFI レイヤー(#53 )と HSM 統合(#54 )では、生の鍵バイトをアプリケーションコードに公開することなく、 Ed25519 のsign操作を HSM バックのプロバイダーに委譲する署名インターフェースを提供します。

C/C++ FFI ブリッジ

edgesentry-bridgeは、 Ed25519 署名と BLAKE3 ハッシュチェーン検証を安定した C ABI として公開する独立した Rust クレートです。 C および C++のファームウェアやゲートウェイは、全面的な書き直しなしに Rust ライブラリと同じセキュリティロジックを呼び出せます。


ライブラリのビルド

cargo build -p edgesentry-bridge --release

生成されるファイル:

プラットフォームファイル
macOStarget/release/libedgesentry_bridge.dylibおよび.a
Linuxtarget/release/libedgesentry_bridge.soおよび.a

ヘッダーcrates/edgesentry-bridge/include/edgesentry_bridge.hbuild.rscbindgenを使って自動的に再生成します。


C/C++からのリンク

macOS:

cc -o my_app main.c \
   -I path/to/edgesentry-bridge/include \
   -L path/to/target/release \
   -ledgesentry_bridge \
   -framework Security -framework CoreFoundation

Linux:

cc -o my_app main.c \
   -I path/to/edgesentry-bridge/include \
   -L path/to/target/release \
   -ledgesentry_bridge \
   -lpthread -ldl

既製のMakefilecrates/edgesentry-bridge/examples/c_integration/に用意されています。


API リファレンス

エラーコード

定数意味
EDS_OK0成功
EDS_ERR_NULL_PTR-1必須ポインタが NULL だった
EDS_ERR_INVALID_UTF8-2文字列引数が有効な UTF-8 でない
EDS_ERR_INVALID_KEY-3鍵またはハッシュバッファが無効
EDS_ERR_STRING_TOO_LONG-4文字列が固定バッファサイズを超えている
EDS_ERR_CHAIN_INVALID-5ハッシュチェーン検証に失敗
EDS_ERR_PANIC-6予期しない内部エラー
EDS_ERR_HASH_MISMATCH-7ペイロードハッシュが期待値と一致しない
EDS_ERR_BAD_SIGNATURE-8Ed25519 署名が無効

負のエラーコードを返す呼び出しの後は、eds_last_error_message() を呼び出すと失敗の人間が読めるメッセージを取得できます。

レコード構造体

typedef struct {
    uint64_t sequence;           /* monotonic record index (starts at 1) */
    uint64_t timestamp_ms;       /* Unix epoch in milliseconds           */
    uint8_t  payload_hash[32];   /* BLAKE3 hash of the raw payload        */
    uint8_t  signature[64];      /* Ed25519 signature over payload_hash   */
    uint8_t  prev_record_hash[32]; /* hash of preceding record (zero for first) */
    uint8_t  device_id[256];     /* null-terminated device identifier     */
    uint8_t  object_ref[512];    /* null-terminated storage reference     */
} EdsAuditRecord;

EdsAuditRecord呼び出し元が確保 します。 Rust はmallocを呼び出したりヒープポインタを返したりしません。そのため_free関数は不要です。

関数

/* Generate an Ed25519 keypair via OS CSPRNG.
   private_key_out and public_key_out must each point to 32 bytes. */
int32_t eds_keygen(uint8_t *private_key_out, uint8_t *public_key_out);

/* Hash payload with BLAKE3, sign with Ed25519, fill *out.
   Pass NULL for prev_record_hash to use the zero hash (first record). */
int32_t eds_sign_record(const char    *device_id,
                        uint64_t       sequence,
                        uint64_t       timestamp_ms,
                        const uint8_t *payload,
                        size_t         payload_len,
                        const uint8_t *prev_record_hash,
                        const char    *object_ref,
                        const uint8_t *private_key,
                        EdsAuditRecord *out);

/* Compute the per-record hash (used as prev_record_hash for the next record).
   hash_out must point to 32 bytes. */
int32_t eds_record_hash(const EdsAuditRecord *record, uint8_t *hash_out);

/* Verify Ed25519 signature. Returns 1 valid, 0 invalid, negative on error. */
int32_t eds_verify_record(const EdsAuditRecord *record,
                          const uint8_t *public_key);

/* Verify the entire hash chain. Returns EDS_OK or EDS_ERR_CHAIN_INVALID. */
int32_t eds_verify_chain(const EdsAuditRecord *records, size_t count);

/* インストール前にソフトウェアアップデートを検証する(CLS-03 / STAR-2 R2.2)。
   BLAKE3(payload) == payload_hash を確認し、次に payload_hash に対する
   Ed25519 パブリッシャー署名を検証する。
   payload_hash は 32 バイト、signature は 64 バイト、
   publisher_key は 32 バイトを指す必要がある。
   成功時 EDS_OK、失敗時 EDS_ERR_HASH_MISMATCH / EDS_ERR_BAD_SIGNATURE、
   入力不正時 EDS_ERR_INVALID_KEY / EDS_ERR_NULL_PTR を返す。 */
int32_t eds_verify_update(const uint8_t *payload,
                          size_t         payload_len,
                          const uint8_t *payload_hash,
                          const uint8_t *signature,
                          const uint8_t *publisher_key);

/* スレッドローカルに保存された最後のエラーメッセージを返す。
   ポインタは同スレッドで次の eds_* 呼び出しまで有効。
   エラーがない場合は "" を返す。NULL を返すことはない。 */
const char *eds_last_error_message(void);

最小限の C サンプル

#include "edgesentry_bridge.h"
#include <string.h>
#include <assert.h>

int main(void) {
    uint8_t priv_key[32], pub_key[32];
    if (eds_keygen(priv_key, pub_key) != EDS_OK) {
        fprintf(stderr, "keygen failed: %s\n", eds_last_error_message());
        return 1;
    }

    const char *payload = "check=door,status=ok";
    EdsAuditRecord rec;
    memset(&rec, 0, sizeof(rec));

    int rc = eds_sign_record("lift-01", 1, 1700000000000ULL,
                             (const uint8_t *)payload, strlen(payload),
                             NULL,              /* zero hash — first record */
                             "lift-01/1.bin",
                             priv_key, &rec);
    if (rc != EDS_OK) {
        fprintf(stderr, "sign_record failed: %s\n", eds_last_error_message());
        return 1;
    }

    assert(eds_verify_record(&rec, pub_key) == 1);
    return 0;
}

完全なサンプルはcrates/edgesentry-bridge/examples/c_integration/main.cを参照してください。


メモリ安全規約

規約詳細
ヒープ確保なしEdsAuditRecordは呼び出し元が確保。 Rust はmallocを呼び出さない
NULL チェックすべてのポインタ引数はチェック済み。失敗時はEDS_ERR_NULL_PTRを返す
固定長文字列device_idは最大 255 文字、object_refは最大 511 文字。超過入力はEDS_ERR_STRING_TOO_LONGを返す
パニック安全性すべての FFI 関数をstd::panic::catch_unwindでラップ。 Rust パニックは C 境界を越えてアンワインドする代わりにEDS_ERR_PANICを返す
鍵サイズprivate_keypublic_keyはちょうど 32 バイトを指していなければならない。ハッシュバッファは 32 バイト、署名バッファは 64 バイト

HSM パス

CLS レベル 4 では、秘密鍵は抽出可能なバイト配列として存在すべきではありません。計画中の HSM 統合(#54)では、鍵バイトを呼び出し元に公開することなくeds_sign_record操作を HSM バックのプロバイダーに委譲します。

プロジェクトへの貢献

整合性チェック

コード・テスト・スクリプト・ドキュメントのいずれかを変更するたびに、 3 つの層が同期していることを確認してください。

  1. コード → ドキュメント: モジュール・関数・ CLI コマンド・動作を追加・削除・名前変更した場合は、それを参照するすべてのドキュメントを更新してください(concepts.mdarchitecture.mdcli.mdquickstart.mddemo.mdtraceability.md)。
  2. ドキュメント → コード: ドキュメントが機能やコマンドを説明している場合は、それが存在し、説明通りに動作することを確認してください。古くなったサンプルや誤ったテストターゲット名は CI 失敗の原因になります。
  3. スクリプト → コード: テストファイルや Cargo フィーチャーの名前を変更した場合は、それを参照するすべてのスクリプトとワークフローを更新してください(例:scripts/integration_test.sh.github/workflows/)。
  4. トレーサビリティ: コンプライアンスコントロールを実装または変更した場合は、docs/src/traceability.mdのステータスを更新してください(✅ / ⚠️ / 🔲)。

PR 作成前の簡単な grep チェック:

# Find docs that mention a symbol you changed
grep -r "<old-name>" docs/ scripts/ .github/

Issue ラベル

すべての Issue には 1 つの タイプ ラベル、 1 つの 優先度 ラベル、 1 つ以上の カテゴリ ラベルを付けてください。

タイプラベル

ラベル使用場面
bug何かが壊れているか、誤った動作をしている
enhancement新機能または既存動作の改善
documentationドキュメントのみの変更 — プロダクションコードへの影響なし

優先度ラベル

ラベル意味
priority:P0必須 — 対象標準( CLS ・ JC-STAR ・ CRA )を満たすために直接必要。解決するまで作業はブロックされる壊れた署名検証、欠けたハッシュチェーンリンク、失敗している完全性ゲート
priority:P1望ましい — コンプライアンスの態勢や開発者体験を強化するが、標準適合のハードブロッカーではない鍵ローテーションツール、 CI 強化、トレーサビリティマトリックス、 FFI ブリッジ
priority:P2ベストエフォート — ストレッチゴール・あると良いもの・専用ハードウェアを必要とするもの。余裕があれば取り組むHSM 統合、教育白書、リファレンスアーキテクチャ

判断に迷う場合は「標準が明示的にこれを要求しているか?」と問いかけてください。 Yes なら P0 。標準で義務付けられていないが役立つなら P1 。ストレッチゴール・望ましい追加・ハードウェア依存の作業なら P2 。

カテゴリラベル

ラベル使用場面
coreコアセキュリティコントロール — 署名・ハッシュ・完全性ゲート・インジェストパイプライン
compliance-governanceコンプライアンスエビデンス・トレーサビリティマトリックス・開示プロセス
devsecopsCI/CD パイプライン・サプライチェーンセキュリティ・静的解析・監査ツール
platform-operationsインフラ・デプロイメント・運用準備
hardware-needed物理ハードウェアまたはハードウェアバックのインフラが必要(常にpriority:P2と組み合わせること)

プルリクエストの規約

プルリクエストを作成する際は、常にブランチを作成したユーザーをアサインしてください。

gh pr create --assignee "@me" --title "..." --body "..."

必須:コード変更後は必ずテストを実行する

毎回 のコード変更後に実行:

cargo test --workspace

すべてのテストが通過するまで変更が完了したとみなさないでください。

ユニットテスト

前提条件( macOS )

まず Rust ツールチェーンをインストール:

brew install rustup-init
rustup-init -y
source "$HOME/.cargo/env"
rustup default stable

cargo-denyをインストール( OSS ライセンスチェックに必要):

cargo install cargo-deny
source "$HOME/.cargo/env"
cargo deny --version

テストの実行

すべてのユニットテストを実行:

cargo test --workspace

特定のクレートのテストを実行:

cargo test -p edgesentry-rs

S3 互換バックエンドフィーチャーを有効にしてedgesentry-rsクレートを実行:

cargo test -p edgesentry-rs --features s3

ライブ MinIO インスタンスに対して S3 統合テストを実行(以下の環境変数が設定されている必要があります):

TEST_S3_ENDPOINT=http://localhost:9000 \
TEST_S3_ACCESS_KEY=minioadmin \
TEST_S3_SECRET_KEY=minioadmin \
TEST_S3_BUCKET=bucket \
cargo test -p edgesentry-rs --features s3 --test integration -- --nocapture

4 つのTEST_S3_*変数のいずれかが未設定の場合、テストは自動的にスキップされます。

ユニットテストと OSS ライセンスチェックを 1 コマンドで実行:

./scripts/run_unit_and_license_check.sh

静的解析と OSS ライセンスチェック

リリース前に以下のチェックを使用してください。

1) 静的解析(clippy

cargo clippy --workspace --all-targets --all-features -- -D warnings

2) 依存関係セキュリティアドバイザリチェック(cargo-audit

一度だけインストール:

cargo install cargo-audit

実行:

cargo audit

3) 商用利用 OSS ライセンスチェック(cargo-deny

一度だけインストール:

cargo install cargo-deny

ライセンスチェックを実行(ポリシーはdeny.tomlに定義):

cargo deny check licenses

完全な依存関係ポリシーチェック(オプション):

cargo deny check advisories bans licenses sources

このチェックが失敗した場合は、違反しているクレートを調べ、法務/セキュリティレビューの後にのみ依存関係またはポリシーを更新してください。


main とのコンフリクトを避ける

コンフリクトは、フィーチャーブランチが main から分岐した後、同じファイルに触れる他の PR が main にマージされると発生します。このリポジトリで最もコンフリクトしやすいファイルはscripts/local_demo.shdocs/src/demo.md.github/copilot-instructions.mdです。

作業開始前に

git fetch origin
git checkout main && git pull origin main
git checkout -b <your-branch>

ブランチを最新の状態に保つ — 特に PR を開く前に、定期的に main にリベースしてください:

git fetch origin
git rebase origin/main

リベース中のコンフリクトの解消

  1. コンフリクトしているファイルを特定:git diff --name-only --diff-filter=U
  2. 各ファイルについて、どちら側を保持するかを決定:
    • 自分のバージョンを採用: git checkout --theirs <file>
    • main のバージョンを採用: git checkout --ours <file>
    • 手動でマージ: <<<<<<< / ======= / >>>>>>>マーカーを削除するようにファイルを編集する
  3. 解消したファイルをステージ:git add <file>
  4. 続行:GIT_EDITOR=true git rebase --continue
  5. 次のコミットで再度コンフリクトが発生した場合は、ステップ 1 から繰り返す。

解消後、リベースしたブランチを force-push :

git push --force-with-lease origin <your-branch>

最もコンフリクトしやすいファイル — 編集前に調整してください:

ファイル頻繁にコンフリクトする理由
scripts/local_demo.sh複数の PR がステップを追加したりデモフローを再構成したりする
docs/src/demo.mdデモスクリプトの変更を反映する
.github/copilot-instructions.md新しいモジュールやサンプルが追加されるたびに構造セクションが更新される
crates/edgesentry-rs/examples/lift_inspection_flow.rsクイックスタートの改善とロール境界の両方の作業で触れられる

ビルドとリリース

リリース成果物のビルド

cargo build --workspace --release

特定のクレートのみをビルド:

cargo build -p edgesentry-rs --release

crates.io への公開

  1. まず品質ゲートを検証:
./scripts/run_unit_and_license_check.sh
cargo clippy --workspace --all-targets --all-features -- -D warnings
  1. 一度だけログイン:
cargo login <CRATES_IO_TOKEN>
  1. ドライラン公開:
cargo publish --dry-run -p edgesentry-rs
  1. 公開:
cargo publish -p edgesentry-rs

GitHub Actions リリース自動化( macOS / Windows / Linux )

このリポジトリには.github/workflows/release.ymlが含まれています。

  • トリガー:v0.1.0のようなタグをプッシュ
  • 品質ゲート:ビルド・ユニットテスト・ライセンスチェック・ clippy
  • edgesentry-rsを crates.io に公開
  • Linux ・ macOS ( x64 + arm64 )・ Windows 向けのedsバイナリをビルド
  • パッケージ済みバイナリを GitHub Release アセットにアップロード

注意:.github/workflows/ci.ymledgesentry-rscargo publish --dry-runを実行します。

必要な GitHub シークレット:

  • CRATES_IO_TOKENcargo publishが使用する crates.io API トークン

マージ後の自動バージョンインクリメント

このリポジトリには.github/workflows/auto-version-tag.ymlも含まれています。

  • トリガー:mainで CI が成功したとき
  • アクション:Cargo.tomlworkspace.package.versionを更新し、vX.Y.Zタグを作成・プッシュ
  • その後:そのタグによってrelease.ymlがトリガーされ、完全なリリースパイプラインが実行される

バージョンバンプルール( Conventional Commits ):

  • fix: -> パッチバンプ(x.y.z -> x.y.(z+1)
  • feat: -> マイナーバンプ(x.y.z -> x.(y+1).0
  • !またはBREAKING CHANGE -> メジャーバンプ(x.y.z -> (x+1).0.0

運用ランブック

このページでは、EdgeSentry-RS の本番環境デプロイにおけるオブザーバビリティの設定、アラート閾値、バックアップ・リストア手順を説明します。


オブザーバビリティ

tracing を使った構造化ログ

EdgeSentry-RS は tracing ファサードを使用しています。サブスクライバーはバンドルされていません。デプロイ側がアプリケーション起動時に任意のバックエンドを接続します。サブスクライバーが登録されていない場合、ライブラリのオーバーヘッドはゼロです。

本番環境向け推奨サブスクライバー(JSON を stdout に出力し、Loki / CloudWatch に取り込む):

# ホストアプリケーションの Cargo.toml
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
use tracing_subscriber::{fmt, EnvFilter};

fn main() {
    fmt()
        .json()
        .with_env_filter(EnvFilter::from_default_env()) // RUST_LOG=edgesentry_rs=info
        .init();
    // ...
}

本番環境では RUST_LOG=edgesentry_rs=info、インシデント調査時は edgesentry_rs=debug を設定してください。

ライブラリが出力する構造化ログイベント

すべてのイベントにはモジュールパスが target として含まれます。主要なイベントの一覧:

レベルターゲットイベント主要フィールド
DEBUGedgesentry_rs::agentsigning recorddevice_id, sequence, payload_bytes
DEBUGedgesentry_rs::ingest::storageingest starteddevice_id, sequence, object_ref, payload_bytes
WARNedgesentry_rs::ingest::storagepayload hash mismatch — record rejecteddevice_id, sequence
WARNedgesentry_rs::ingest::storageintegrity policy rejected recorddevice_id, sequence, reason
ERRORedgesentry_rs::ingest::storageraw data store write faileddevice_id, sequence, error
ERRORedgesentry_rs::ingest::storageaudit ledger append faileddevice_id, sequence, error
ERRORedgesentry_rs::ingest::storageoperation log write faileddevice_id, sequence, error
INFOedgesentry_rs::ingest::storagerecord accepteddevice_id, sequence, object_ref
DEBUGedgesentry_rs::ingest::verifysignature verification faileddevice_id, sequence
DEBUGedgesentry_rs::ingest::verifyduplicate record rejecteddevice_id, sequence
DEBUGedgesentry_rs::ingest::verifysequence out of orderdevice_id, expected, actual
DEBUGedgesentry_rs::ingest::verifyprev_record_hash mismatch — chain brokendevice_id, sequence
DEBUGedgesentry_rs::ingest::verifyrecord verified and accepteddevice_id, sequence

推奨 Prometheus メトリクス(ログから導出)

ログ→メトリクスパイプライン(Promtail + Loki、または Vector など)を使い、構造化ログイベントからカウンターを導出します:

メトリクス導出方法アラート閾値
edgesentry_ingest_accepted_totalINFO "record accepted" イベントをカウント
edgesentry_ingest_rejected_total{reason}WARN 拒否イベントを reason フィールドでラベル付けしてカウント10件/分を継続 → P1 アラート
edgesentry_ingest_error_total{component}ERROR ストレージ障害イベントをコンポーネント別にカウント1件でも発生 → P0 アラート
edgesentry_chain_break_totalDEBUG "prev_record_hash mismatch" イベントをカウント1件でも発生 → P0 アラート
edgesentry_signature_fail_totalDEBUG "signature verification failed" イベントをカウント5件/分を継続 → P1 アラート

OpenTelemetry(トレーシングスパン)

IngestService::ingest メソッドは tracing スパンを出力します。OTLP エクスポーターに接続することで分散トレーシングが利用できます:

opentelemetry = "0.26"
opentelemetry-otlp = { version = "0.26", features = ["grpc-tonic"] }
tracing-opentelemetry = "0.27"

アラート定義

アラート条件重要度対応
IngestStorageErrorERROR レベルのストレージ障害が発生P0DB/S3 接続を確認し、ディスクと認証情報を検証する
ChainBreakprev_record_hash mismatch イベントが発生P0改ざんまたはリプレイを調査し、再起動前にログを保全する
HighRejectionRate拒否率が 5 分間 10件/分を超過P1デバイスファームウェアを確認し、署名鍵のローテーション設定を調査する
SignatureFailureSurge署名失敗が 5 分間 5件/分を超過P1鍵の漏洩またはアクティブなスプーフィングの可能性を調査する
AuditLedgerLagPostgres operation_logs 挿入レイテンシの p99 が 2 秒超P1DBのクエリプランと autovacuum の競合を確認する

復旧目標

目標ターゲット根拠
RTO(復旧時間目標)30 分以内pg_basebackup + WAL リプレイによるリストア時間
RPO(復旧時点目標)5 分以内5 分間隔の継続的 WAL アーカイブ

バックアップ手順

PostgreSQL — 監査台帳・操作ログ

前提条件: WAL アーカイブが有効化されていること(archive_mode = onarchive_command で S3 等に転送)。

1. ベースバックアップの取得

pg_basebackup \
  --host=<DB_HOST> \
  --username=<DB_USER> \
  --pgdata=/backup/pg_base_$(date +%Y%m%d_%H%M%S) \
  --format=tar \
  --gzip \
  --wal-method=stream \
  --checkpoint=fast \
  --progress

2. バックアップの検証

pg_restore --list /backup/pg_base_<timestamp>/base.tar.gz | head -20

3. WAL の継続アーカイブ

postgresql.confarchive_command が WAL セグメントを耐久性のあるストレージ(例:S3)に転送していることを確認します:

archive_command = 'aws s3 cp %p s3://<BUCKET>/wal/%f'

4. 保持ポリシー

バックアップ種別保持期間
ベースバックアップ30 日
WAL アーカイブ30 日
論理ダンプ(pg_dump7 日(週次)

S3 / MinIO — ペイロード生データストア

バケットでバージョニングクロスリージョンレプリケーションを有効化します:

# バージョニングの有効化
aws s3api put-bucket-versioning \
  --bucket <BUCKET> \
  --versioning-configuration Status=Enabled

# レプリケーションの有効化(宛先バケットと IAM ロールを別途設定した上で)
aws s3api put-bucket-replication \
  --bucket <BUCKET> \
  --replication-configuration file://replication.json

最低限のレプリケーション先:別リージョン 1 か所。CLS Level 3 のエビデンス完全性を確保するため、オブジェクトロックまたはバージョニングを有効化し、ペイロードが上書き削除されないようにします。


リストア手順

PostgreSQL — ポイントインタイムリカバリ(PITR)

# 1. Postgres サービスを停止
systemctl stop postgresql

# 2. ベースバックアップを展開
tar -xzf /backup/pg_base_<timestamp>/base.tar.gz -C /var/lib/postgresql/data/

# 3. リカバリ設定を作成
cat > /var/lib/postgresql/data/recovery.conf <<EOF
restore_command = 'aws s3 cp s3://<BUCKET>/wal/%f %p'
recovery_target_time = '<TARGET_TIMESTAMP>'
recovery_target_action = 'promote'
EOF

# 4. Postgres を起動 — 指定時刻まで WAL をリプレイする
systemctl start postgresql

# 5. 確認:デバイスごとの最終受理シーケンスを確認
psql -U <DB_USER> -d <DB_NAME> \
  -c "SELECT device_id, MAX(sequence) FROM audit_records GROUP BY device_id;"

復旧確認チェックリスト

  • デバイスごとの最終シーケンスがインシデント前のスナップショットと一致する
  • ハッシュチェーンの連続性を確認:eds verify-chain <exported-records.json>
  • 操作ログにリカバリ対象時刻前後のギャップがないことを確認する
  • 確認完了後、アラート抑制を解除する

S3 / MinIO — オブジェクトのリストア

# 特定バージョンのオブジェクトをリストア
aws s3api get-object \
  --bucket <BUCKET> \
  --key <OBJECT_KEY> \
  --version-id <VERSION_ID> \
  <OUTPUT_FILE>

障害訓練スケジュール

以下の訓練を四半期ごとに実施し、ランブックの正確性を検証します:

訓練手順合格基準
DB フェイルオーバープライマリ Postgres を停止してレプリカを昇格30 分以内にインジェストが再開
DB リストアステージング環境で 1 時間前への PITR を実施30 分以内にチェーン連続性を確認
S3 オブジェクト復旧削除したテストオブジェクトをリストアバイト単位で元のオブジェクトと一致
アラート発火テストハーネスで不正な署名を注入2 分以内に P1 アラートが発火