Доверие к GNSS как телеметрия безопасности
kshana trust-telemetry превращает поток доверия к GNSS по эпохам (оценка 0–100, диапазон, причины) в сигналы, которые центр эксплуатации или безопасности уже принимает: метрики Prometheus, события syslog в CEF или LEEF для SIEM и, по желанию, 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
Вход — это живая схема JSON-строк v1.1, описанная в docs/MARITIME-TRUST.md. Читаются: 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 выводит, а --mqtt-tcp <host:port> публикует документ состояния JSON и сообщения обнаружения Home Assistant по MQTT. Обнаружение (с удержанием, под homeassistant/...) создаёт три сущности: оценка доверия, диапазон доверия (с причинами в виде атрибутов) и датчик проблемы untrusted. Документ состояния отправляется в <prefix>/state (по умолчанию kshana/trust/state, с удержанием): state, score (null, пока её нет), untrusted, t_s, time, reasons, gate и заявление консультативного характера; положение никогда не включается. Параметры: --mqtt-prefix, --mqtt-node, --mqtt-discovery.
Издатель говорит по MQTT 3.1.1 с QoS 0 и читает только 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 — не более 255 печатаемых символов ASCII без пробелов (иначе отклоняется, здесь и в командной строке), а ответ, превышающий предел поверхности (4 МиБ в MCP), отклоняется, потому что CEF и LEEF с syslog_all записывают одно событие на эпоху. Ничего не отправляется, не обслуживается и не записывается; работающие экспортёры остаются процессом в командной строке.
Доставка никогда не останавливает оценку#
Доставка syslog и OpenTelemetry идёт в собственных потоках. Цикл приёма только передаёт им сообщение или снимок и никогда не ждёт, поэтому неработающий, медленный или нечитающий сборщик не может создать обратное давление на живой поток или заморозить /metrics:
- у каждого соединения и каждой записи есть ограничение 2 с; неудавшееся соединение отбрасывается и лениво устанавливается заново с экспоненциальной задержкой от 1 с до 60 с;
- сообщение, которое не удаётся доставить (сборщик недоступен, истекло время записи, очередь из 1024 заполнена), отбрасывается и учитывается в
kshana_trust_syslog_send_failures_total; настройте оповещение по его частоте; - экспортёру OpenTelemetry передаётся копия реестра, поэтому во время сетевого ввода-вывода блокировка не удерживается; если экспорт ещё выполняется, новый снимок пропускается (следующий несёт те же накопленные итоги).
В конце ввода потокам отправки даётся оставшееся время на завершение.
Метрики Prometheus#
| Метрика | Тип | Метки | Значение |
|---|---|---|---|
kshana_build_info |
gauge | version |
константа 1 |
kshana_trust_score |
gauge | последняя оценка 0–100; отсутствует, если источник её не даёт | |
kshana_trust_band |
gauge | band |
1 для текущего диапазона, 0 для остальных |
kshana_trust_gate |
gauge | gate |
off, passed, withheld, unknown: 1 для текущего состояния, 0 для остальных; отсутствует без шлюза |
kshana_trust_position_latitude_degrees, ..._longitude_degrees, ..._height_meters |
gauge | положение, сообщённое приёмником; только с --expose-position (конечная точка не аутентифицирована, а положение может выдать площадку или судно) |
|
kshana_trust_reason_active |
gauge | reason |
1, если присутствует в последней эпохе |
kshana_trust_epochs_total |
counter | band |
принятые эпохи |
kshana_trust_reason_epochs_total |
counter | reason |
эпохи, в которых присутствовала причина |
kshana_trust_input_errors_total |
counter | строки ввода, которые не удалось разобрать | |
kshana_trust_reason_overflow_total |
counter | вхождения причин, свёрнутые в reason="other" (см. ниже) |
|
kshana_trust_syslog_send_failures_total |
counter | события syslog, которые не доставлены | |
kshana_trust_epoch_offset_seconds |
gauge | смещение последней эпохи от начала потока | |
kshana_trust_last_sample_timestamp_seconds |
gauge | время получения по настенным часам; настройте оповещение по его возрасту | |
kshana_trust_geometry |
gauge | status |
геометрия спутников в последней эпохе, good, degraded, unknown: 1 для текущей (входные данные схемы 1,3) |
kshana_trust_geometry_degraded |
gauge | 1 ухудшена, 0 хороша; отсутствует, когда геометрия неизвестна (1.3) | |
kshana_trust_geometry_signal_loss |
gauge | 1 когда видимые спутники не отслеживаются, хотя их геометрия хороша (потерян сигнал, а не небо; geometry_degraded по-прежнему равна 1), иначе 0; отсутствует, когда геометрия неизвестна или вход этого не сообщает (1.3) |
|
kshana_trust_geometry_hdop, ..._pdop, ..._used_satellites |
gauge | HDOP, PDOP и число используемых спутников в последней эпохе; каждая отсутствует, если её нет (1.3) | |
kshana_trust_geometry_epochs_total |
counter | status |
эпохи по статусу геометрии (1.3) |
kshana_trust_geometry_label |
gauge | label |
метка геометрии в последней эпохе, good, fair, poor, unknown: 1 для текущей; никогда не оценивается (оценка снижается только за poor); из входных данных, несущих метку (0,36) |
kshana_trust_geometry_vdop |
gauge | VDOP в последней эпохе; отсутствует, если его нет (0.36) |
Главное — метка геометрии. Стройте графики и настраивайте оповещения по kshana_trust_geometry_label (OTLP kshana.geometry.label, в CEF третье поле cs6, в LEEF geoLabel): poor — единственная метка, за которую оценка снижается. Статус геометрии носит описательный характер: degraded включает информационный случай sky-blocked, который может сопровождаться хорошим или удовлетворительным DOP, поэтому не поднимайте тревогу только по 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, если потерянные спутники были низкими, или geometry на inconsistency, если заявленный HDOP расходится с заявленным небом), старый ряд больше не экспортируется и начинается новый. Запрашивайте по 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 (facility local0, приложение kshana, идентификатор сообщения GNSSTRUST) по UDP (по одной датаграмме на событие; IPv4 или IPv6) или TCP, по умолчанию с завершением переводом строки или, с --syslog-octet-counting, с кадрированием <length> <message> по RFC 6587. По умолчанию на каждое изменение диапазона отправляется одно событие; --syslog-all отправляет по одному на эпоху. Поставщик записывается как Ashforde OU (ASCII), чтобы ни один SIEM не декодировал его неверно.
| Понятие | Серьёзность syslog | Серьёзность CEF | LEEF sev |
|---|---|---|---|
| calibrating | 6 info | 0 | 1 |
| штатный | 6 info | 1 | 1 |
| ухудшены | 4 warning | 5 | 5 |
| untrusted | 2 critical | 9 | 9 |
| unknown | 5 notice | 3 | 3 |
Сопоставление полей (идентификатор события — 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, а класс вычета геометрии environment — в cs6 |
geoSignalLoss=1, только пока это верно |
В CEF геометрия использует только пользовательские поля словаря (никаких выдуманных ключей); метка передаётся в cs6, а не в flexString1, потому что кодек CEF Logstash не сопоставляет поля flexString. События геометрии 1,3, включая случаи с меткой, входят в строку CEF с уровнем VALIDATED: оба разборщика-оракула читают их обратно как записано (tests/cef_reference.rs, поправки 4 и 5, каждая зарегистрирована до того, как какой-либо из разборщиков был запущен на её случаях). Поля метки, VDOP и числа спутников добавляются после каждого более раннего поля и ряда и только для входных данных, несущих метку, поэтому вход без неё отображается точно как прежде.
Пустые поля опускаются (без cs2= при отсутствии причин). Имя хоста обрезается до 255 символов. Значения заголовка CEF экранируют \ и |; значения расширения экранируют \, = и переводы строк. В значениях LEEF ^ и переводы строк заменяются пробелами.
OpenTelemetry (по умолчанию выключен)#
Соберите с --features otlp и передайте --otlp http://host:4318/v1/metrics. Экспортёр отправляет OTLP/HTTP JSON (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) каждые --otlp-every эпох (по умолчанию 10) и в конце ввода. Он не добавляет зависимостей и не имеет TLS: отправляйте на сборщик на том же хосте или в доверенном сегменте, который передаёт данные дальше по TLS. Конечные точки https:// отклоняются.