Confianza en GNSS como telemetría de seguridad
kshana trust-telemetry convierte el flujo de confianza GNSS por época (puntuación de 0-100, banda, motivos) en señales que un centro de operaciones o de seguridad ya ingiere: métricas de Prometheus, eventos de syslog en CEF o LEEF para un SIEM y, opcionalmente, OpenTelemetry. Lee el flujo; nunca cambia la evaluación de confianza y no transmite nada salvo telemetría.
Ejecútelo#
# 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
La entrada es el esquema JSON-lines en vivo v1.1 documentado en docs/MARITIME-TRUST.md. Se lee: t_s; state (calibrating|nominal|degraded|untrusted, la banda); score (0-100 o null); time (la hora de la época tal como se declara); gate (off|passed|withheld); position (lat_deg, lon_deg, height_m, o null); los motivos son los monitores de deductions, y luego cualesquiera otros de alarms; a partir del esquema 1,3, geometry (status, hdop, pdop, n_used) y la class de cada deducción. Las demás claves se ignoran, ya que el esquema solo crece añadiendo claves al final. Una línea que no se analiza se cuenta en kshana_trust_input_errors_total y se omite; las puntuaciones fuera de 0-100 se rechazan, no se recortan, de modo que se advierte un cambio de formato. El único código que conoce este formato es src/telemetry/sample.rs.
/metrics se sirve sin autenticación y por defecto en 127.0.0.1:9464. Enlazar a otra dirección imprime una advertencia; póngalo detrás de su propio control de acceso. El punto de conexión atiende una conexión cada vez con un límite de lectura de 2 s, de modo que un cliente inactivo puede retrasar una recolección hasta ese tiempo; está pensado para un único recolector, no para la red abierta.
Home Assistant y dispositivos NTP (MQTT)#
--print-mqtt imprime, y --mqtt-tcp <host:port> publica, un documento de estado JSON y mensajes de descubrimiento MQTT de Home Assistant. El descubrimiento (retenido, bajo homeassistant/...) crea tres entidades: puntuación de confianza, banda de confianza (con los motivos como atributos) y un sensor de problema untrusted. El documento de estado va a <prefix>/state (por defecto kshana/trust/state, retenido): state, score (null mientras no haya ninguna), untrusted, t_s, time, reasons, gate y la declaración orientativa; la posición nunca se incluye. Opciones: --mqtt-prefix, --mqtt-node, --mqtt-discovery.
El publicador habla MQTT 3.1.1 con QoS 0 y solo lee el CONNACK del broker. No tiene credenciales ni TLS, de modo que use un broker de la red local (o un relé local que las añada). La entrega se ejecuta en su propio hilo detrás de una cola de 1024: un broker caído descarta mensajes, que se cuentan y se informan al final, y nunca bloquea el flujo. Un dispositivo NTP o cualquier otro consumidor que quiera JSON simple puede leer la misma línea de estado. No se escribe nada en un receptor ni en un reloj. Python, WebAssembly y MCP admiten format = "mqtt" y devuelven las mismas líneas topic<TAB>payload (allí no se publica nada).
Desde Python, WebAssembly y MCP#
Los codificadores también están disponibles como llamadas de texto de entrada y de salida, sin proceso, socket ni reloj: kshana.trust_telemetry(input, format, host="", input_kind="jsonl", expose_position=False, syslog_all=False, unix_nano=0) de Python, la exportación de WebAssembly trust_telemetry(input, format, host, input_kind, options_json) y la herramienta MCP trust_telemetry (comando del plugin /kshana-trust-telemetry). format es prometheus, cef, leef u otlp; input_kind es jsonl (el flujo) o result (un resultado por lotes). Devuelven el mismo texto que imprime la línea de comandos (--print-metrics, --print-syslog), con los mismos límites: las líneas de syslog no llevan marca de tiempo, host tiene como máximo 255 caracteres ASCII imprimibles sin espacios (se rechaza en caso contrario, aquí y en la línea de comandos), y se rechaza una respuesta mayor que el límite de la superficie (4 MiB en MCP), porque CEF y LEEF con syslog_all escriben un evento por época. No se envía, sirve ni escribe nada; los exportadores en ejecución siguen siendo un proceso de la línea de comandos.
La entrega nunca bloquea la evaluación#
La entrega de syslog y de OpenTelemetry se ejecuta en sus propios hilos. El bucle de ingesta solo les pasa un mensaje o una instantánea y nunca espera, de modo que un colector caído, lento o que no lee no puede ejercer contrapresión sobre el flujo en vivo ni congelar /metrics:
- cada conexión y cada escritura tiene un límite de 2 s; una conexión fallida se descarta y se reconecta de forma diferida, con retroceso exponencial de 1 s hasta 60 s;
- un mensaje que no puede entregarse (colector caído, escritura agotada por tiempo, cola de 1024 llena) se descarta y se cuenta en
kshana_trust_syslog_send_failures_total; avise según su tasa; - al exportador de OpenTelemetry se le entrega una copia del registro, de modo que no se mantiene ningún bloqueo durante la E/S de red; si una exportación sigue en curso, se omite la nueva instantánea (la siguiente lleva los mismos totales acumulados).
Al final de la entrada se da a los hilos emisores el tiempo restante que tienen como límite para terminar.
Métricas de Prometheus#
| Métrica | Tipo | Etiquetas | Significado |
|---|---|---|---|
kshana_build_info |
gauge | version |
constante 1 |
kshana_trust_score |
gauge | última puntuación 0-100; ausente si el origen no da ninguna | |
kshana_trust_band |
gauge | band |
1 para la banda actual, 0 para las demás |
kshana_trust_gate |
gauge | gate |
off, passed, withheld, unknown: 1 para el estado actual, 0 para los demás; ausente sin compuerta |
kshana_trust_position_latitude_degrees, ..._longitude_degrees, ..._height_meters |
gauge | posición informada por el receptor; solo con --expose-position (el punto de conexión no está autenticado y una posición puede identificar un emplazamiento o un buque) |
|
kshana_trust_reason_active |
gauge | reason |
1 si está presente en la última época |
kshana_trust_epochs_total |
contador | band |
épocas recibidas |
kshana_trust_reason_epochs_total |
contador | reason |
épocas en las que el motivo estuvo presente |
kshana_trust_input_errors_total |
contador | líneas de entrada que no se pueden analizar | |
kshana_trust_reason_overflow_total |
contador | apariciones de motivos agrupadas en reason="other" (véase más abajo) |
|
kshana_trust_syslog_send_failures_total |
contador | eventos de syslog no entregados | |
kshana_trust_epoch_offset_seconds |
gauge | desfase de la última época respecto del inicio del flujo | |
kshana_trust_last_sample_timestamp_seconds |
gauge | hora de recepción de reloj de pared; avise según su antigüedad | |
kshana_trust_geometry |
gauge | status |
geometría de satélites en la última época, good, degraded, unknown: 1 para la actual (entradas del esquema 1,3) |
kshana_trust_geometry_degraded |
gauge | 1 degradada, 0 buena; ausente cuando la geometría es desconocida (1.3) | |
kshana_trust_geometry_signal_loss |
gauge | 1 cuando no se siguen satélites a la vista mientras su geometría es buena (se perdió la señal, no el cielo; geometry_degraded sigue valiendo 1), y 0 en caso contrario; ausente cuando la geometría es desconocida o la entrada no lo indica (1.3) |
|
kshana_trust_geometry_hdop, ..._pdop, ..._used_satellites |
gauge | HDOP, PDOP y satélites usados en la última época; cada uno ausente cuando no hay ninguno (1.3) | |
kshana_trust_geometry_epochs_total |
contador | status |
épocas por estado de geometría (1.3) |
kshana_trust_geometry_label |
gauge | label |
etiqueta de geometría en la última época, good, fair, poor, unknown: 1 para la actual; nunca se puntúa (la puntuación solo deduce por poor); a partir de entradas que llevan la etiqueta (0,36) |
kshana_trust_geometry_vdop |
gauge | VDOP en la última época; ausente cuando no hay ninguno (0.36) |
La etiqueta de geometría es lo principal. Represente y avise según kshana_trust_geometry_label (OTLP kshana.geometry.label, el tercer campo cs6 de CEF, geoLabel de LEEF): poor es la única etiqueta por la que deduce la puntuación. El estado de la geometría es descriptivo: degraded incluye el caso informativo sky-blocked, que puede darse con un DOP bueno o aceptable, de modo que no dé aviso solo por kshana_trust_geometry_degraded.
A partir de las entradas del esquema 1,3, kshana_trust_reason_active lleva además una etiqueta class (geometry, environment, inconsistency) en cada motivo que vino con una: la clase de la deducción en la última época. Las series de geometría van después de todas las demás y solo una vez que una época de entrada lleva geometry (esquema en vivo 1,3, un resultado del motor 0,36 o posterior), de modo que un flujo o resultado 0,35 se representa byte por byte como antes. Los valores de las etiquetas son conjuntos fijos (un estado desconocido se lee unknown; una clase desconocida se descarta). Avise según kshana_trust_reason_active{class="inconsistency"} y no solo según la banda, para que un cielo malo no dé aviso como si fuera un ataque; una clase geometry es una afirmación sobre el cielo, no una constatación de que no hubo ataque (véase docs/RECEIVER-TRUST.md).
La etiqueta class forma parte de la identidad de una serie: cuando la clase de un motivo cambia entre épocas (loss-of-lock de environment a geometry cuando los satélites perdidos estaban bajos, o de geometry a inconsistency cuando el HDOP declarado discrepa del cielo declarado), la serie antigua deja de exportarse y empieza una nueva. Consulte por reason con max by (reason) cuando la clase no importe, y espere que un cambio así parezca una serie que termina y otra que empieza. Las series del esquema 1,3, la etiqueta class y los campos de geometría de CEF/LEEF se comprueban con las propias pruebas del motor y con las referencias independientes de CEF (pycef, Logstash); las referencias independientes externas de Prometheus y de OTLP todavía no tienen ningún flujo 1,3.
Los nombres de motivo se recortan a 128 caracteres, y como máximo 64 motivos distintos reciben su propia serie; los posteriores se cuentan bajo reason="other" y en kshana_trust_reason_overflow_total, de modo que una entrada hostil o con errores no puede crear una cardinalidad de etiquetas ilimitada.
band es una de calibrating, nominal, degraded, untrusted, unknown. Los nombres de motivo son los nombres de monitores del flujo (kinematic, heading-course, ...; para el resultado por lotes, cn0-drop, agc, raim).
Hay un panel de ejemplo en deploy/grafana/kshana-gnss-trust.json (impórtelo y elija su origen de datos de Prometheus).
Syslog: CEF y LEEF#
Los eventos se envían en un sobre RFC 5424 (facility local0, aplicación kshana, identificador de mensaje GNSSTRUST) por UDP (un datagrama cada uno; IPv4 o IPv6) o TCP, terminados en nueva línea por defecto o, con --syslog-octet-counting, enmarcados como <length> <message> según la RFC 6587. Por defecto se envía un evento por cada cambio de banda; --syslog-all envía uno por época. El proveedor se escribe Ashforde OU (ASCII) para que ningún SIEM lo decodifique mal.
| Concepto | Severidad de syslog | Severidad de CEF | sev de LEEF |
|---|---|---|---|
| calibrating | 6 info | 0 | 1 |
| nominal | 6 info | 1 | 1 |
| degradadas | 4 warning | 5 | 5 |
| untrusted | 2 critical | 9 | 9 |
| unknown | 5 notice | 3 | 3 |
Asignación de campos (el identificador de evento es gnss-trust.<band>):
| Significado | CEF | LEEF 2,0 (delimitador ^) |
|---|---|---|
| categoría | cat=gnss-trust |
cat |
| host emisor | dvchost |
devHost |
| banda | cs1 (etiqueta band) |
band |
| motivos, separados por comas | cs2 (reasons) |
reasons |
| banda anterior | cs3 (previousBand) |
previousBand |
| puntuación | cfp1 (trustScore), se omite si no hay ninguna |
trustScore |
| desfase de la época, s | cfp2 (epochOffsetSeconds) |
epochOffsetSeconds |
| etiqueta de hora del registro | cs4 (logTime) |
logTime |
| estado de la compuerta | cs5 (gate) |
gate |
estado de la geometría, luego ; y las clases de las deducciones de la época, separadas por comas (por ejemplo degraded;environment,geometry) (entradas 1,3); a partir de entradas que llevan la etiqueta (0,36) un tercer campo, ; y la etiqueta, y el campo de clases está entonces siempre presente (por ejemplo degraded;;poor, good;;fair) |
cs6 (geometry) |
geoStatus (solo el estado), geoClass (clases) y geoLabel (etiqueta) |
| HDOP (1.3) | cfp3 (geometryHdop) |
geoHdop |
| PDOP (1.3) | cfp4 (geometryPdop) |
geoPdop (0,36, con la etiqueta) |
| VDOP (0,36, con la etiqueta) | no se transporta: se ocupan los cuatro campos personalizados de coma flotante | geoVdop |
| satélites usados (0,36, con la etiqueta) | cn1 (geometryUsedSatellites), un entero |
geoUsed |
| pérdida de señal: satélites a la vista que no se siguen mientras su geometría es buena (1.3) | no se transporta como campo (no queda libre ninguna clave del diccionario CEF para ello): el estado indica degraded y la clase de la deducción de geometría, environment, va en cs6 |
geoSignalLoss=1, solo mientras se da |
En CEF, la geometría usa solo campos personalizados del diccionario (sin claves inventadas); la etiqueta va en cs6 en lugar de en flexString1 porque el códec CEF de Logstash no asigna los campos flexString. Los eventos de geometría 1,3, incluidos los casos de etiqueta, forman parte de la fila CEF VALIDATED: ambos analizadores de referencia independiente los leen de nuevo tal como se escribieron (tests/cef_reference.rs, enmiendas 4 y 5, cada una registrada antes de ejecutar cualquiera de los analizadores sobre sus casos). Los campos de etiqueta, VDOP y número de satélites se añaden después de todos los campos y series anteriores, y solo para entradas que llevan la etiqueta, de modo que una entrada sin ella se representa exactamente como antes.
Los campos vacíos se omiten (no hay cs2= sin motivos). El nombre del host se recorta a 255 caracteres. Los valores de la cabecera CEF escapan \ y |; los valores de extensión escapan \, = y los saltos de línea. En los valores LEEF, ^ y los saltos de línea se sustituyen por espacios.
OpenTelemetry (desactivado por defecto)#
Compile con --features otlp y pase --otlp http://host:4318/v1/metrics. El exportador envía por POST JSON de OTLP/HTTP (kshana.trust.score, kshana.trust.band, kshana.trust.epochs, kshana.trust.reason.epochs, y para entradas del esquema 1,3 kshana.geometry.status, kshana.geometry.degraded, kshana.geometry.signal_loss, kshana.geometry.hdop, kshana.geometry.pdop, kshana.geometry.used_satellites y, a partir de entradas que llevan la etiqueta (0,36), kshana.geometry.label (atributo label) y kshana.geometry.vdop; las sumas acumuladas llevan startTimeUnixNano) cada --otlp-every épocas (por defecto 10) y al final de la entrada. No añade ninguna dependencia y no tiene TLS: envíe a un colector en el mismo host o en un segmento de confianza, que reenvíe después por TLS. Los puntos de conexión https:// se rechazan.