セキュリティテレメトリーとしての GNSS の信頼性
kshana trust-telemetry は、エポックごとの GNSS 信頼性ストリーム(スコア 0〜100、バンド、理由)を、運用センターやセキュリティセンターがすでに取り込んでいる信号に変換します。Prometheus メトリクス、SIEM 向けの CEF または LEEF 形式の syslog イベント、そして任意で OpenTelemetry です。ストリームを読み取るだけで、信頼性の評価を変更することはなく、テレメトリー以外は何も送信しません。
実行する#
# live stream on stdin -> /metrics on localhost:9464
kshana receiver-trust live <args> | kshana trust-telemetry
# replay a batch result (the score is the epoch's own for a vessel platform, absent otherwise)
kshana trust-telemetry --result session.result.json --print-metrics
kshana trust-telemetry --result session.result.json --print-syslog --format leef
# SIEM over UDP, one event per band change
kshana trust-telemetry --input stream.jsonl --syslog-udp siem.example:514 --format cef --host ops-gw1
入力は、docs/MARITIME-TRUST.md に記載されたライブの JSON-lines スキーマ v1.1 です。読み取る項目:t_s、state(calibrating|nominal|degraded|untrusted、バンド)、score(0〜100 または null)、time(明記されたエポックの時刻)、gate(off|passed|withheld)、position(lat_deg、lon_deg、height_m、または null)。理由は deductions にある監視で、続いて alarms にあるその他の監視です。スキーマ 1.3 以降は、geometry(status、hdop、pdop、n_used)と各控除の class も読み取ります。スキーマはキーを追加する形でのみ拡張されるため、その他のキーは無視されます。解析できない行は kshana_trust_input_errors_total に計上されてスキップされます。0〜100 の範囲外のスコアは、形式の変更に気付けるよう、丸められずに拒否されます。この形式を知っているコードは src/telemetry/sample.rs だけです。
/metrics は認証なしで提供され、既定では 127.0.0.1:9464 です。それ以外にバインドすると警告が出力されるため、ご自身のアクセス制御の背後に置いてください。このエンドポイントは一度に一つの接続しか処理せず、読み取りの制限は 2 秒です。そのため、アイドル状態のクライアントがスクレイプを最大でその時間だけ遅らせることがあります。想定しているのは一つのスクレイパーであり、公開ネットワークではありません。
Home Assistant と NTP アプライアンス(MQTT)#
--print-mqtt は JSON の状態ドキュメントと Home Assistant の MQTT ディスカバリーメッセージを出力し、--mqtt-tcp <host:port> はそれらを発行します。ディスカバリー(保持、homeassistant/... の下)は三つのエンティティを作成します。信頼スコア、信頼バンド(理由を属性として持つ)、そして untrusted の問題センサーです。状態ドキュメントは <prefix>/state(既定は kshana/trust/state、保持)に送られ、内容は state、score(なければ null)、untrusted、t_s、time、reasons、gate と助言の記述です。位置は決して含まれません。オプションは --mqtt-prefix、--mqtt-node、--mqtt-discovery です。
パブリッシャーは QoS 0 で MQTT 3.1.1 を使い、ブローカーの CONNACK だけを読みます。認証情報も TLS もないため、ローカルネットワーク上のブローカー(またはそれらを追加するローカルのリレー)を使ってください。配信は 1024 件のキューの後ろにある専用スレッドで動作します。応答しないブローカーがあるとメッセージは破棄され、その数は計数されて最後に報告されますが、ストリームが止まることは決してありません。NTP アプライアンスなど、プレーンな JSON を求めるその他の利用者も、同じ状態行を読み取れます。受信機やクロックには何も書き込みません。Python、WebAssembly、MCP は format = "mqtt" を受け取り、同じ topic<TAB>payload 行を返します(そこでは何も発行されません)。
Python、WebAssembly、MCP から#
エンコーダーは、プロセス、ソケット、クロックを伴わない、テキストを入れてテキストを返す呼び出しとしても使えます。Python の kshana.trust_telemetry(input, format, host="", input_kind="jsonl", expose_position=False, syslog_all=False, unix_nano=0)、WebAssembly のエクスポート trust_telemetry(input, format, host, input_kind, options_json)、MCP ツール trust_telemetry(プラグインコマンドは /kshana-trust-telemetry)です。format は prometheus、cef、leef、otlp のいずれかで、input_kind は jsonl(ストリーム)または result(バッチ結果)です。これらはコマンドラインが出力するのと同じテキスト(--print-metrics、--print-syslog)を同じ制限のもとで返します。syslog の行にはタイムスタンプがなく、host は空白を含まない印字可能な ASCII 文字で最大 255 文字(それ以外はここでもコマンドラインでも拒否されます)です。また、syslog_all を付けた CEF と LEEF はエポックごとに一つのイベントを書き出すため、各インターフェースの上限(MCP では 4 MiB)を超える応答は拒否されます。何も送信、提供、書き込みされることはなく、稼働中のエクスポーターはコマンドライン上のプロセスのままです。
配信が評価を止めることはありません#
syslog と OpenTelemetry の配信は、それぞれ専用のスレッドで動作します。取り込みループはメッセージまたはスナップショットを渡すだけで待機しないため、応答しない、遅い、あるいは読み取らないコレクターがあっても、ライブのストリームに背圧がかかったり /metrics が固まったりすることはありません。
- 接続と書き込みにはすべて 2 秒の制限があります。失敗した接続は破棄され、1 秒から 60 秒までの指数バックオフで遅延再接続されます。
- 配信できないメッセージ(コレクターの停止、書き込みのタイムアウト、1024 件のキューが満杯)は破棄され、
kshana_trust_syslog_send_failures_totalに計数されます。その割合に対してアラートを設定してください。 - OpenTelemetry エクスポーターにはレジストリのコピーが渡されるため、ネットワーク I/O の間にロックは保持されません。エクスポートがまだ進行中の場合は、新しいスナップショットはスキップされます(次のスナップショットには同じ累積合計が含まれます)。
入力の終了時には、送信スレッドに残り時間の制限内で終了する猶予が与えられます。
Prometheus メトリクス#
| メトリクス | 型 | ラベル | 意味 |
|---|---|---|---|
kshana_build_info |
ゲージ | version |
定数 1 |
kshana_trust_score |
ゲージ | 最新のスコア 0-100。ソースが与えない場合は欠落 | |
kshana_trust_band |
ゲージ | band |
現在のバンドは 1、それ以外は 0 |
kshana_trust_gate |
ゲージ | gate |
off、passed、withheld、unknown:現在の状態は 1、それ以外は 0。ゲートがなければ欠落 |
kshana_trust_position_latitude_degrees, ..._longitude_degrees, ..._height_meters |
ゲージ | 受信機が報告した位置。--expose-position を付けた場合のみ(エンドポイントは認証されておらず、位置から施設や船舶が特定されることがあります) |
|
kshana_trust_reason_active |
ゲージ | reason |
最新のエポックに存在すれば 1 |
kshana_trust_epochs_total |
カウンター | band |
受信したエポック |
kshana_trust_reason_epochs_total |
カウンター | reason |
その理由が存在したエポック |
kshana_trust_input_errors_total |
カウンター | 解析できない入力行 | |
kshana_trust_reason_overflow_total |
カウンター | reason="other" にまとめられた理由の出現(後述) |
|
kshana_trust_syslog_send_failures_total |
カウンター | 配信されなかった syslog イベント | |
kshana_trust_epoch_offset_seconds |
ゲージ | 最新のエポックの、ストリーム開始からのオフセット | |
kshana_trust_last_sample_timestamp_seconds |
ゲージ | 実時間での受信時刻。その経過時間に対してアラートを設定してください | |
kshana_trust_geometry |
ゲージ | status |
最新のエポックにおける衛星ジオメトリ、good、degraded、unknown:現在のものは 1 (スキーマ 1.3 の入力) |
kshana_trust_geometry_degraded |
ゲージ | 劣化は 1、良好は 0。ジオメトリが不明なときは欠落 (1.3) | |
kshana_trust_geometry_signal_loss |
ゲージ | 視界内の衛星が追跡されていないにもかかわらずジオメトリが良好な場合は 1(失われたのは空ではなく信号であり、geometry_degraded は引き続き 1 を示します)、それ以外は 0。ジオメトリが不明な場合、または入力が示さない場合は欠落 (1.3) |
|
kshana_trust_geometry_hdop, ..._pdop, ..._used_satellites |
ゲージ | 最新のエポックにおける HDOP、PDOP、使用衛星数。それぞれ、値がなければ欠落 (1.3) | |
kshana_trust_geometry_epochs_total |
カウンター | status |
ジオメトリの状態ごとのエポック (1.3) |
kshana_trust_geometry_label |
ゲージ | label |
最新のエポックにおけるジオメトリのラベル、good、fair、poor、unknown:現在のものは 1。スコアには使われません(スコアが減点するのは poor に対してのみ)。ラベルを持つ入力(0.36)からのみ |
kshana_trust_geometry_vdop |
ゲージ | 最新のエポックにおける VDOP。なければ欠落 (0.36) |
見出しとなるのはジオメトリのラベルです。kshana_trust_geometry_label(OTLP では kshana.geometry.label、CEF では三番目の cs6 フィールド、LEEF では geoLabel)をグラフ化し、アラートの対象にしてください。poor は、スコアが減点する唯一のラベルです。ジオメトリの状態は記述的なものです。degraded には、DOP が良好または普通でも生じうる、参考情報としての sky-blocked のケースが含まれるため、kshana_trust_geometry_degraded だけで呼び出してはいけません。
スキーマ 1.3 の入力では、kshana_trust_reason_active は、ラベルを伴った各理由にも class ラベル(geometry、environment、inconsistency)を付けます。これは最新のエポックにおける減点のクラスです。ジオメトリのシリーズは、他のすべてのシリーズの後に続き、入力エポックが geometry を持つ場合にのみ現れます(ライブのスキーマ 1.3、エンジン 0.36 以降の結果)。そのため、0.35 のストリームや結果は、従来どおりバイト単位で同一に出力されます。ラベルの値は固定された集合です(不明な状態は unknown と読み、不明なクラスは破棄されます)。バンドだけでなく kshana_trust_reason_active{class="inconsistency"} に対してアラートを設定してください。そうすれば、空の状態が悪いだけで攻撃として呼び出されることはありません。geometry のクラスは空についての記述であり、攻撃がなかったという所見ではありません(docs/RECEIVER-TRUST.md を参照)。
class ラベルはシリーズの識別情報の一部です。ある理由のクラスがエポックの間で変わると(失われた衛星が低い位置にあったときに loss-of-lock が environment から geometry へ、または記載された HDOP が記載された空の状態と食い違うときに geometry から inconsistency へ)、古いシリーズはエクスポートされなくなり、新しいシリーズが始まります。クラスが関係ない場合は、reason で max by (reason) を使って問い合わせてください。そのような切り替わりは、一つのシリーズが終わり、別のシリーズが始まるように見えることを想定しておいてください。スキーマ 1.3 のシリーズ、class ラベル、CEF/LEEF のジオメトリのフィールドは、エンジン自身の試験と CEF のオラクル(pycef、Logstash)で確認されています。外部の Prometheus と OTLP のオラクルには、まだ 1.3 のストリームがありません。
理由の名前は 128 文字で切り詰められ、固有のシリーズが与えられる異なる理由は最大 64 件です。それ以降のものは reason="other" と kshana_trust_reason_overflow_total に計数されるため、悪意のある入力やバグのある入力が、際限のないラベルのカーディナリティを作り出すことはできません。
band は calibrating、nominal、degraded、untrusted、unknown のいずれかです。理由の名前はストリームの監視名です(kinematic、heading-course など。バッチ結果では cn0-drop、agc、raim)。
サンプルのダッシュボードが deploy/grafana/kshana-gnss-trust.json にあります(インポートして、お使いの Prometheus データソースを選んでください)。
syslog:CEF と LEEF#
イベントは、RFC 5424 のエンベロープ(ファシリティ local0、アプリ kshana、メッセージ ID GNSSTRUST)で、UDP(一件ごとに一つのデータグラム。IPv4 または IPv6)または TCP で送られます。TCP では既定で改行終端、--syslog-octet-counting を付けると RFC 6587 のとおり <length> <message> のフレーミングになります。既定ではバンドの変化ごとに一つのイベントが送られ、--syslog-all を付けるとエポックごとに一つ送られます。どの SIEM でも誤って解読されないよう、ベンダーは Ashforde OU(ASCII)と書かれます。
| 概念 | syslog の重大度 | CEF の重大度 | LEEF sev |
|---|---|---|---|
| calibrating | 6 info | 0 | 1 |
| nominal | 6 info | 1 | 1 |
| 劣化 | 4 warning | 5 | 5 |
| untrusted | 2 critical | 9 | 9 |
| unknown | 5 notice | 3 | 3 |
フィールドの対応(イベント ID は gnss-trust.<band>):
| 意味 | CEF | LEEF 2.0(区切り文字は ^) |
|---|---|---|
| カテゴリ | cat=gnss-trust |
cat |
| 送信元ホスト | dvchost |
devHost |
| バンド | cs1(ラベル band) |
band |
| 理由(カンマ区切り) | cs2 (reasons) |
reasons |
| 直前のバンド | cs3 (previousBand) |
previousBand |
| スコア | cfp1(trustScore)、なければ省略 |
trustScore |
| エポックのオフセット(秒) | cfp2 (epochOffsetSeconds) |
epochOffsetSeconds |
| ログ時刻ラベル | cs4 (logTime) |
logTime |
| ゲートの状態 | cs5 (gate) |
gate |
ジオメトリの状態、続いて ; と、そのエポックの減点のクラス(カンマ区切り)(例:degraded;environment,geometry)(1.3 の入力)。ラベルを持つ入力(0.36)では、; とラベルから成る三番目のフィールドが付き、クラスのフィールドは常に存在します(例:degraded;;poor、good;;fair) |
cs6 (geometry) |
geoStatus(状態のみ)、geoClass(クラス)、geoLabel(ラベル) |
| HDOP (1.3) | cfp3 (geometryHdop) |
geoHdop |
| PDOP (1.3) | cfp4 (geometryPdop) |
geoPdop (0.36、ラベル付き) |
| VDOP (0.36、ラベル付き) | 持たない:四つの浮動小数点のカスタムフィールドはすでに使用されている | geoVdop |
| 使用衛星数 (0.36、ラベル付き) | cn1(geometryUsedSatellites)、整数 |
geoUsed |
| 信号喪失:視界内の衛星が追跡されていないにもかかわらず、ジオメトリが良好 (1.3) | フィールドとしては持たない(CEF の辞書に空いているキーがありません):状態は degraded、ジオメトリの減点のクラスは cs6 内の environment と読みます |
geoSignalLoss=1、成立しているときのみ |
CEF では、ジオメトリは辞書のカスタムフィールドだけを使います(独自のキーは作りません)。Logstash の CEF コーデックは flexString フィールドを対応付けないため、ラベルは flexString1 ではなく cs6 に載せます。ラベルのケースを含む 1.3 のジオメトリイベントは、VALIDATED の CEF の行に含まれます。両方のオラクルパーサーが、それらを書かれたとおりに読み戻します(tests/cef_reference.rs、修正 4 と 5。いずれも、どちらのパーサーもそれらのケースで実行される前に登録されました)。ラベル、VDOP、衛星数のフィールドは、それ以前のすべてのフィールドとシリーズの後に追加され、ラベルを持つ入力に対してのみ付くため、ラベルのない入力は従来どおり出力されます。
空のフィールドは省かれます(理由がなければ cs2= はありません)。ホスト名は 255 文字で切り詰められます。CEF のヘッダー値では \ と | をエスケープし、拡張値では \、=、改行をエスケープします。LEEF の値では、^ と改行が空白に置き換えられます。
OpenTelemetry(既定ではオフ)#
--features otlp を付けてビルドし、--otlp http://host:4318/v1/metrics を渡します。エクスポーターは、10 を既定とする --otlp-every エポックごと、および入力の終了時に、OTLP/HTTP JSON を POST します(kshana.trust.score、kshana.trust.band、kshana.trust.epochs、kshana.trust.reason.epochs、スキーマ 1.3 の入力では kshana.geometry.status、kshana.geometry.degraded、kshana.geometry.signal_loss、kshana.geometry.hdop、kshana.geometry.pdop、kshana.geometry.used_satellites、さらにラベルを持つ入力(0.36)では kshana.geometry.label(属性 label)と kshana.geometry.vdop。累積合計は startTimeUnixNano を持ちます)。依存関係は追加されず、TLS もないため、同じホストまたは信頼できるセグメントのコレクターに送り、そこから TLS で先へ転送してください。https:// のエンドポイントは拒否されます。