GNSS-Vertrauen als Sicherheitstelemetrie
kshana trust-telemetry wandelt den GNSS-Vertrauensstrom pro Epoche (Wert 0-100, Band, Gründe) in Signale um, die ein Betriebs- oder Sicherheitszentrum bereits aufnimmt: Prometheus-Metriken, Syslog-Ereignisse in CEF oder LEEF für ein SIEM und optional OpenTelemetry. Es liest den Strom; es ändert nie die Vertrauensbewertung und sendet nichts außer Telemetrie.
Ausführen#
# 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
Die Eingabe ist das in docs/MARITIME-TRUST.md dokumentierte Live-JSON-Lines-Schema v1.1. Gelesen werden: t_s; state (calibrating|nominal|degraded|untrusted, das Band); score (0-100 oder null); time (die Zeit der Epoche wie angegeben); gate (off|passed|withheld); position (lat_deg, lon_deg, height_m oder null); Gründe sind die Monitore in deductions, dann weitere in alarms; ab Schema 1,3 geometry (status, hdop, pdop, n_used) und die class jedes Abzugs. Andere Schlüssel werden ignoriert, da das Schema nur durch angehängte Schlüssel wächst. Eine Zeile, die sich nicht parsen lässt, wird in kshana_trust_input_errors_total gezählt und übersprungen; Werte außerhalb von 0-100 werden abgelehnt, nicht begrenzt, damit eine Formatänderung auffällt. Der einzige Code, der dieses Format kennt, ist src/telemetry/sample.rs.
/metrics wird ohne Authentifizierung bereitgestellt und verwendet standardmäßig 127.0.0.1:9464. Das Binden an eine andere Adresse gibt eine Warnung aus; stellen Sie es hinter Ihre eigene Zugriffskontrolle. Der Endpunkt bearbeitet jeweils eine Verbindung mit einem Leselimit von 2 s, sodass ein untätiger Client einen Abruf um bis zu diese Zeit verzögern kann; er ist für einen einzelnen Scraper gedacht, nicht für das offene Netz.
Home Assistant und NTP-Geräte (MQTT)#
--print-mqtt gibt ein JSON-Zustandsdokument und Home-Assistant-MQTT-Discovery-Nachrichten aus, --mqtt-tcp <host:port> veröffentlicht sie. Die Discovery (retained, unter homeassistant/...) erzeugt drei Entitäten: Vertrauenswert, Vertrauensband (mit den Gründen als Attribute) und einen Problemsensor untrusted. Das Zustandsdokument geht an <prefix>/state (Standard kshana/trust/state, retained): state, score (null, solange keiner vorliegt), untrusted, t_s, time, reasons, gate und den Hinweis zur beratenden Software; die Position wird nie aufgenommen. Optionen: --mqtt-prefix, --mqtt-node, --mqtt-discovery.
Der Publisher spricht MQTT 3.1.1 mit QoS 0 und liest nur das CONNACK des Brokers. Er hat keine Zugangsdaten und kein TLS, verwenden Sie daher einen Broker im lokalen Netz (oder ein lokales Relay, das sie ergänzt). Die Zustellung läuft in einem eigenen Thread hinter einer Warteschlange von 1024: Ein ausgefallener Broker lässt Nachrichten fallen, was gezählt und am Ende gemeldet wird, und blockiert nie den Strom. Ein NTP-Gerät oder jeder andere Verbraucher, der reines JSON will, kann dieselbe Zustandszeile lesen. Nichts wird an einen Empfänger oder eine Uhr geschrieben. Python, WebAssembly und MCP nehmen format = "mqtt" entgegen und liefern dieselben topic<TAB>payload-Zeilen zurück (dort wird nichts veröffentlicht).
Aus Python, WebAssembly und MCP#
Die Encoder stehen auch als Aufrufe mit Text als Ein- und Ausgabe zur Verfügung, ohne Prozess, Socket oder Uhr: Python kshana.trust_telemetry(input, format, host="", input_kind="jsonl", expose_position=False, syslog_all=False, unix_nano=0), der WebAssembly-Export trust_telemetry(input, format, host, input_kind, options_json) und das MCP-Werkzeug trust_telemetry (Plugin-Befehl /kshana-trust-telemetry). format ist prometheus, cef, leef oder otlp; input_kind ist jsonl (der Strom) oder result (ein Stapelergebnis). Sie liefern denselben Text, den die Befehlszeile ausgibt (--print-metrics, --print-syslog), mit denselben Grenzen: Syslog-Zeilen tragen keinen Zeitstempel, host besteht aus höchstens 255 druckbaren ASCII-Zeichen ohne Leerzeichen (andernfalls abgelehnt, hier wie in der Befehlszeile), und eine Antwort, die größer als das Limit der Oberfläche ist (4 MiB bei MCP), wird abgelehnt, weil CEF und LEEF mit syslog_all ein Ereignis pro Epoche schreiben. Nichts wird gesendet, bereitgestellt oder geschrieben; die laufenden Exporter bleiben ein Prozess in der Befehlszeile.
Die Zustellung blockiert nie die Bewertung#
Syslog- und OpenTelemetry-Zustellung laufen in eigenen Threads. Die Eingangsschleife übergibt ihnen nur eine Nachricht oder einen Schnappschuss und wartet nie, sodass ein ausgefallener, langsamer oder nicht lesender Collector den Live-Strom nicht zurückstauen oder /metrics einfrieren kann:
- jeder Verbindungsaufbau und jeder Schreibvorgang hat ein Limit von 2 s; eine fehlgeschlagene Verbindung wird verworfen und träge neu aufgebaut, mit exponentiellem Backoff von 1 s bis zu 60 s;
- eine Nachricht, die nicht zugestellt werden kann (Collector ausgefallen, Schreibvorgang abgelaufen, Warteschlange von 1024 voll), wird verworfen und in
kshana_trust_syslog_send_failures_totalgezählt; alarmieren Sie auf deren Rate; - der OpenTelemetry-Exporter erhält eine Kopie des Registers, sodass während der Netzwerk-E/A keine Sperre gehalten wird; läuft ein Export noch, wird der neue Schnappschuss übersprungen (der nächste trägt dieselben kumulativen Summen).
Am Ende der Eingabe wird den Sender-Threads ihre verbleibende Zeit zum Abschluss gelassen.
Prometheus-Metriken#
| Metrik | Typ | Kennzeichnungen | Bedeutung |
|---|---|---|---|
kshana_build_info |
gauge | version |
konstant 1 |
kshana_trust_score |
gauge | letzter Wert 0-100; fehlt, wenn die Quelle keinen liefert | |
kshana_trust_band |
gauge | band |
1 für das aktuelle Band, 0 für die übrigen |
kshana_trust_gate |
gauge | gate |
off, passed, withheld, unknown: 1 für den aktuellen Zustand, 0 für die übrigen; fehlt ohne Gate |
kshana_trust_position_latitude_degrees, ..._longitude_degrees, ..._height_meters |
gauge | vom Empfänger gemeldete Position; nur mit --expose-position (der Endpunkt ist nicht authentifiziert, und eine Position kann einen Standort oder ein Schiff identifizieren) |
|
kshana_trust_reason_active |
gauge | reason |
1, wenn in der letzten Epoche vorhanden |
kshana_trust_epochs_total |
counter | band |
empfangene Epochen |
kshana_trust_reason_epochs_total |
counter | reason |
Epochen, in denen der Grund vorlag |
kshana_trust_input_errors_total |
counter | nicht parsbare Eingabezeilen | |
kshana_trust_reason_overflow_total |
counter | Vorkommen von Gründen, die in reason="other" zusammengefasst wurden (siehe unten) |
|
kshana_trust_syslog_send_failures_total |
counter | nicht zugestellte Syslog-Ereignisse | |
kshana_trust_epoch_offset_seconds |
gauge | Versatz der letzten Epoche vom Stromanfang | |
kshana_trust_last_sample_timestamp_seconds |
gauge | Empfangszeit nach Wanduhr; alarmieren Sie auf deren Alter | |
kshana_trust_geometry |
gauge | status |
Satellitengeometrie in der letzten Epoche, good, degraded, unknown: 1 für die aktuelle (Eingaben des Schemas 1,3) |
kshana_trust_geometry_degraded |
gauge | 1 verschlechtert, 0 gut; fehlt, wenn die Geometrie unbekannt ist (1.3) | |
kshana_trust_geometry_signal_loss |
gauge | 1, wenn sichtbare Satelliten nicht verfolgt werden, obwohl ihre Geometrie gut ist (das Signal ging verloren, nicht der Himmel; geometry_degraded zeigt weiterhin 1), sonst 0; fehlt, wenn die Geometrie unbekannt ist oder die Eingabe es nicht angibt (1.3) |
|
kshana_trust_geometry_hdop, ..._pdop, ..._used_satellites |
gauge | HDOP, PDOP und verwendete Satelliten in der letzten Epoche; jeweils fehlend, wenn keiner vorliegt (1.3) | |
kshana_trust_geometry_epochs_total |
counter | status |
Epochen nach Geometriestatus (1.3) |
kshana_trust_geometry_label |
gauge | label |
Geometrie-Label in der letzten Epoche, good, fair, poor, unknown: 1 für das aktuelle; wird nie bewertet (der Wert zieht nur bei poor ab); aus Eingaben, die das Label tragen (0,36) |
kshana_trust_geometry_vdop |
gauge | VDOP in der letzten Epoche; fehlt, wenn keiner vorliegt (0.36) |
Das Geometrie-Label ist die wichtigste Größe. Stellen Sie kshana_trust_geometry_label grafisch dar und alarmieren Sie darauf (OTLP kshana.geometry.label, CEF das dritte Feld cs6, LEEF geoLabel): poor ist das einzige Label, bei dem der Wert abzieht. Der Geometriestatus ist beschreibend: degraded umfasst den informativen Fall sky-blocked, der mit gutem oder ausreichendem DOP auftreten kann, alarmieren Sie daher nicht allein auf kshana_trust_geometry_degraded.
Bei Eingaben des Schemas 1,3 trägt kshana_trust_reason_active außerdem ein Label class (geometry, environment, inconsistency) an jedem Grund, der eines mitbrachte: die Klasse des Abzugs in der letzten Epoche. Die Geometrie-Reihen kommen nach allen anderen und nur, sobald eine Eingabeepoche geometry trägt (Live-Schema 1,3, ein Ergebnis der Engine 0,36 oder später), sodass ein Strom oder Ergebnis von 0,35 byte-genau wie zuvor ausgegeben wird. Label-Werte sind feste Mengen (ein unbekannter Status lautet unknown; eine unbekannte Klasse wird verworfen). Alarmieren Sie auf kshana_trust_reason_active{class="inconsistency"} statt allein auf das Band, damit ein schlechter Himmel nicht als Angriff alarmiert; eine Klasse geometry ist eine Aussage über den Himmel, keine Feststellung, dass kein Angriff stattfand (siehe docs/RECEIVER-TRUST.md).
Das Label class gehört zur Identität einer Reihe: Wechselt die Klasse eines Grundes zwischen Epochen (loss-of-lock von environment zu geometry, wenn die verlorenen Satelliten niedrig standen, oder geometry zu inconsistency, wenn das angegebene HDOP dem angegebenen Himmel widerspricht), wird die alte Reihe nicht mehr exportiert und eine neue beginnt. Fragen Sie nach reason mit max by (reason) ab, wenn die Klasse keine Rolle spielt, und rechnen Sie damit, dass ein solcher Wechsel wie das Ende einer Reihe und der Beginn einer anderen aussieht. Die Reihen des Schemas 1,3, das Label class und die CEF/LEEF-Geometriefelder werden durch die eigenen Tests der Engine und durch die CEF-Referenzen (pycef, Logstash) geprüft; die externen Referenzen für Prometheus und OTLP haben noch keinen Strom nach 1,3.
Namen von Gründen werden auf 128 Zeichen gekürzt, und höchstens 64 verschiedene Gründe erhalten eine eigene Reihe; spätere werden unter reason="other" und in kshana_trust_reason_overflow_total gezählt, sodass eine feindliche oder fehlerhafte Eingabe keine unbegrenzte Label-Kardinalität erzeugen kann.
band ist eines von calibrating, nominal, degraded, untrusted, unknown. Namen von Gründen sind die Monitornamen des Stroms (kinematic, heading-course, ...; beim Stapelergebnis cn0-drop, agc, raim).
Ein Beispiel-Dashboard liegt in deploy/grafana/kshana-gnss-trust.json (importieren Sie es und wählen Sie Ihre Prometheus-Datenquelle).
Syslog: CEF und LEEF#
Ereignisse werden in einem RFC-5424-Umschlag (Facility local0, App kshana, Nachrichten-ID GNSSTRUST) über UDP (je ein Datagramm; IPv4 oder IPv6) oder TCP gesendet, standardmäßig durch Zeilenumbruch abgeschlossen oder, mit --syslog-octet-counting, als <length> <message> gerahmt wie in RFC 6587. Standardmäßig wird ein Ereignis pro Bandwechsel gesendet; --syslog-all sendet eines pro Epoche. Der Hersteller wird als Ashforde OU (ASCII) geschrieben, damit kein SIEM ihn falsch dekodiert.
| Konzept | Syslog-Schweregrad | CEF-Schweregrad | LEEF sev |
|---|---|---|---|
| calibrating | 6 info | 0 | 1 |
| nominal | 6 info | 1 | 1 |
| degraded | 4 warning | 5 | 5 |
| untrusted | 2 critical | 9 | 9 |
| unknown | 5 notice | 3 | 3 |
Feldzuordnung (die Ereignis-ID ist gnss-trust.<band>):
| Bedeutung | CEF | LEEF 2,0 (Trennzeichen ^) |
|---|---|---|
| Kategorie | cat=gnss-trust |
cat |
| sendender Host | dvchost |
devHost |
| Band | cs1 (Label band) |
band |
| Gründe, durch Komma getrennt | cs2 (reasons) |
reasons |
| vorheriges Band | cs3 (previousBand) |
previousBand |
| Wert | cfp1 (trustScore), weggelassen, wenn keiner vorliegt |
trustScore |
| Epochenversatz, s | cfp2 (epochOffsetSeconds) |
epochOffsetSeconds |
| Label der Protokollzeit | cs4 (logTime) |
logTime |
| Gate-Zustand | cs5 (gate) |
gate |
Geometriestatus, dann ; und die Klassen der Abzüge der Epoche, durch Komma getrennt (zum Beispiel degraded;environment,geometry) (Eingaben 1,3); bei Eingaben, die das Label tragen (0,36), ein drittes Feld, ; und das Label, wobei das Klassenfeld dann stets vorhanden ist (zum Beispiel degraded;;poor, good;;fair) |
cs6 (geometry) |
geoStatus (nur Status), geoClass (Klassen) und geoLabel (Label) |
| HDOP (1.3) | cfp3 (geometryHdop) |
geoHdop |
| PDOP (1.3) | cfp4 (geometryPdop) |
geoPdop (0,36, mit dem Label) |
| VDOP (0,36, mit dem Label) | nicht übernommen: die vier benutzerdefinierten Gleitkommafelder sind belegt | geoVdop |
| verwendete Satelliten (0,36, mit dem Label) | cn1 (geometryUsedSatellites), eine Ganzzahl |
geoUsed |
| Signalverlust: sichtbare Satelliten werden nicht verfolgt, obwohl ihre Geometrie gut ist (1.3) | nicht als Feld übernommen (kein CEF-Wörterbuchschlüssel ist dafür frei): Der Status lautet degraded und die Klasse des Geometrieabzugs environment in cs6 |
geoSignalLoss=1, nur solange es gilt |
In CEF verwendet die Geometrie nur benutzerdefinierte Felder aus dem Wörterbuch (keine erfundenen Schlüssel); das Label läuft in cs6 statt in flexString1, weil der Logstash-CEF-Codec die flexString-Felder nicht abbildet. Die Geometrieereignisse von 1,3, einschließlich der Label-Fälle, sind Teil der CEF-Zeile der Stufe VALIDATED: Beide Referenz-Parser lesen sie wie geschrieben zurück (tests/cef_reference.rs, Änderungen 4 und 5, jeweils registriert, bevor einer der Parser an seinen Fällen lief). Die Felder für Label, VDOP und Satellitenzahl werden nach jedem früheren Feld und jeder früheren Reihe angehängt, und nur für Eingaben, die das Label tragen, sodass eine Eingabe ohne Label genau wie zuvor ausgegeben wird.
Leere Felder werden weggelassen (kein cs2= ohne Gründe). Der Hostname wird auf 255 Zeichen gekürzt. Kopfwerte in CEF maskieren \ und |; Erweiterungswerte maskieren \, = und Zeilenumbrüche. In LEEF-Werten werden ^ und Zeilenumbrüche durch Leerzeichen ersetzt.
OpenTelemetry (standardmäßig aus)#
Bauen Sie mit --features otlp und übergeben Sie --otlp http://host:4318/v1/metrics. Der Exporter sendet per POST OTLP/HTTP-JSON (kshana.trust.score, kshana.trust.band, kshana.trust.epochs, kshana.trust.reason.epochs und bei Eingaben des Schemas 1,3 kshana.geometry.status, kshana.geometry.degraded, kshana.geometry.signal_loss, kshana.geometry.hdop, kshana.geometry.pdop, kshana.geometry.used_satellites und, bei Eingaben, die das Label tragen (0,36), kshana.geometry.label (Attribut label) und kshana.geometry.vdop; die kumulativen Summen tragen startTimeUnixNano) alle --otlp-every Epochen (Standard 10) und am Ende der Eingabe. Er fügt keine Abhängigkeit hinzu und hat kein TLS: Senden Sie an einen Collector auf demselben Host oder in einem vertrauenswürdigen Segment, der über TLS weiterleitet. Endpunkte mit https:// werden abgelehnt.