Confiança no GNSS como telemetria de segurança
kshana trust-telemetry transforma o fluxo de confiança no GNSS por época (pontuação 0-100, faixa, motivos) em sinais que um centro de operações ou de segurança já ingere: métricas Prometheus, eventos syslog em CEF ou LEEF para um SIEM e, opcionalmente, OpenTelemetry. Ele lê o fluxo; nunca altera a avaliação de confiança e não transmite nada além de telemetria.
Execute#
# 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
A entrada é o esquema JSON-lines ao vivo v1.1 documentado em docs/MARITIME-TRUST.md. Lê: t_s; state (calibrating|nominal|degraded|untrusted, a faixa); score (0-100 ou nulo); time (o tempo da época como declarado); gate (off|passed|withheld); position (lat_deg, lon_deg, height_m, ou nulo); os motivos são os monitores em deductions, depois quaisquer outros em alarms; a partir do esquema 1,3, geometry (status, hdop, pdop, n_used) e o class de cada dedução. Outras chaves são ignoradas, já que o esquema só cresce por chaves acrescentadas. Uma linha que não é interpretada é contada em kshana_trust_input_errors_total e descartada; pontuações fora de 0-100 são rejeitadas, não truncadas, para que uma mudança de formato seja notada. O único código que conhece esse formato é src/telemetry/sample.rs.
/metrics é servido sem autenticação e tem por padrão 127.0.0.1:9464. Vincular a outro endereço imprime um aviso; coloque-o atrás do seu próprio controle de acesso. O endpoint atende uma conexão por vez com limite de leitura de 2 s, de modo que um cliente ocioso pode atrasar uma coleta em até esse tempo; destina-se a um único coletor, não à rede aberta.
Home Assistant e dispositivos NTP (MQTT)#
--print-mqtt imprime, e --mqtt-tcp <host:port> publica, um documento de estado JSON e mensagens de descoberta MQTT do Home Assistant. A descoberta (retida, sob homeassistant/...) cria três entidades: pontuação de confiança, faixa de confiança (com os motivos como atributos) e um sensor de problema untrusted. O documento de estado vai para <prefix>/state (padrão kshana/trust/state, retido): state, score (nulo enquanto não há), untrusted, t_s, time, reasons, gate e a declaração consultiva; a posição nunca é incluída. Opções: --mqtt-prefix, --mqtt-node, --mqtt-discovery.
O publicador fala MQTT 3.1.1 com QoS 0 e lê apenas o CONNACK do broker. Não tem credenciais nem TLS, portanto use um broker na rede local (ou um retransmissor local que as acrescente). A entrega roda em sua própria thread, atrás de uma fila de 1024: um broker morto descarta mensagens, contadas e informadas no final, e nunca trava o fluxo. Um dispositivo NTP ou qualquer outro consumidor que queira JSON simples pode ler a mesma linha de estado. Nada é escrito em um receptor ou em um relógio. Python, WebAssembly e MCP recebem format = "mqtt" e devolvem as mesmas linhas topic<TAB>payload (nada é publicado ali).
Pelo Python, WebAssembly e MCP#
Os codificadores também estão disponíveis como chamadas de texto de entrada e texto de saída, sem processo, soquete ou relógio: Python kshana.trust_telemetry(input, format, host="", input_kind="jsonl", expose_position=False, syslog_all=False, unix_nano=0), a exportação WebAssembly trust_telemetry(input, format, host, input_kind, options_json) e a ferramenta MCP trust_telemetry (comando de plugin /kshana-trust-telemetry). format é prometheus, cef, leef ou otlp; input_kind é jsonl (o fluxo) ou result (um resultado em lote). Devolvem o mesmo texto que a linha de comando imprime (--print-metrics, --print-syslog), com os mesmos limites: as linhas syslog não trazem marca de tempo, host tem no máximo 255 caracteres ASCII imprimíveis sem espaços (recusado caso contrário, aqui e na linha de comando), e uma resposta maior que o limite da superfície (4 MiB no MCP) é recusada, porque CEF e LEEF com syslog_all escrevem um evento por época. Nada é enviado, servido ou gravado; os exportadores em execução continuam sendo um processo na linha de comando.
A entrega nunca trava a avaliação#
A entrega por syslog e OpenTelemetry roda em threads próprias. O laço de ingestão apenas lhes entrega uma mensagem ou um instantâneo e nunca espera, de modo que um coletor morto, lento ou que não lê não consegue pressionar o fluxo ao vivo nem congelar /metrics:
- toda conexão e toda escrita têm um limite de 2 s; uma conexão que falha é descartada e refeita de forma preguiçosa, com recuo exponencial de 1 s até 60 s;
- uma mensagem que não pode ser entregue (coletor fora do ar, escrita expirou, fila de 1024 cheia) é descartada e contada em
kshana_trust_syslog_send_failures_total; alerte sobre sua taxa; - o exportador OpenTelemetry recebe uma cópia do registro, de modo que nenhum bloqueio é mantido durante a E/S de rede; se uma exportação ainda estiver em andamento, o novo instantâneo é ignorado (o seguinte traz os mesmos totais cumulativos).
No fim da entrada, as threads de envio recebem seus limites de tempo restantes para terminar.
Métricas Prometheus#
| Métrica | Tipo | Rótulos | Significado |
|---|---|---|---|
kshana_build_info |
gauge | version |
constante 1 |
kshana_trust_score |
gauge | pontuação mais recente 0-100; ausente se a fonte não fornece | |
kshana_trust_band |
gauge | band |
1 para a faixa atual, 0 para as demais |
kshana_trust_gate |
gauge | gate |
off, passed, withheld, unknown: 1 para o estado atual, 0 para os demais; ausente sem uma comporta (gate) |
kshana_trust_position_latitude_degrees, ..._longitude_degrees, ..._height_meters |
gauge | posição informada pelo receptor; somente com --expose-position (o endpoint não tem autenticação e uma posição pode identificar um local ou embarcação) |
|
kshana_trust_reason_active |
gauge | reason |
1 se presente na época mais recente |
kshana_trust_epochs_total |
contador | band |
épocas recebidas |
kshana_trust_reason_epochs_total |
contador | reason |
épocas em que o motivo estava presente |
kshana_trust_input_errors_total |
contador | linhas de entrada não interpretáveis | |
kshana_trust_reason_overflow_total |
contador | ocorrências de motivo reunidas em reason="other" (veja abaixo) |
|
kshana_trust_syslog_send_failures_total |
contador | eventos syslog não entregues | |
kshana_trust_epoch_offset_seconds |
gauge | deslocamento da época mais recente em relação ao início do fluxo | |
kshana_trust_last_sample_timestamp_seconds |
gauge | hora de recebimento no relógio de parede; alerte sobre sua idade | |
kshana_trust_geometry |
gauge | status |
geometria dos satélites na época mais recente, good, degraded, unknown: 1 para a atual (entradas do esquema 1,3) |
kshana_trust_geometry_degraded |
gauge | 1 degradada, 0 boa; ausente quando a geometria é desconhecida (1.3) | |
kshana_trust_geometry_signal_loss |
gauge | 1 quando satélites à vista não são rastreados embora sua geometria seja boa (o sinal foi perdido, não o céu; geometry_degraded ainda indica 1), senão 0; ausente quando a geometria é desconhecida ou a entrada não informa (1.3) |
|
kshana_trust_geometry_hdop, ..._pdop, ..._used_satellites |
gauge | HDOP, PDOP e satélites usados na época mais recente; cada um ausente quando não há (1.3) | |
kshana_trust_geometry_epochs_total |
contador | status |
épocas por status de geometria (1.3) |
kshana_trust_geometry_label |
gauge | label |
rótulo de geometria na época mais recente, good, fair, poor, unknown: 1 para o atual; nunca pontuado (a pontuação deduz apenas por poor); a partir de entradas que trazem o rótulo (0,36) |
kshana_trust_geometry_vdop |
gauge | VDOP na época mais recente; ausente quando não há (0.36) |
O rótulo de geometria é o destaque. Faça gráficos e alertas sobre kshana_trust_geometry_label (OTLP kshana.geometry.label, CEF o terceiro campo cs6, LEEF geoLabel): poor é o único rótulo pelo qual a pontuação deduz. O status de geometria é descritivo: degraded inclui o caso informativo sky-blocked, que pode vir com DOP bom ou razoável, portanto não acione plantão só por kshana_trust_geometry_degraded.
A partir de entradas do esquema 1,3, kshana_trust_reason_active também traz um rótulo class (geometry, environment, inconsistency) em cada motivo que veio com um: a classe da dedução na época mais recente. As séries de geometria vêm depois de todas as outras e somente quando uma época de entrada traz geometry (esquema ao vivo 1,3, um resultado do mecanismo 0,36 ou posterior), de modo que um fluxo ou resultado 0,35 é renderizado byte a byte como antes. Os valores dos rótulos são conjuntos fixos (um status desconhecido aparece como unknown; uma classe desconhecida é descartada). Alerte sobre kshana_trust_reason_active{class="inconsistency"} e não apenas sobre a faixa, para que um céu ruim não dispare plantão como se fosse um ataque; uma classe geometry é uma afirmação sobre o céu, não uma constatação de que nenhum ataque ocorreu (veja docs/RECEIVER-TRUST.md).
O rótulo class faz parte da identidade de uma série: quando a classe de um motivo muda entre épocas (loss-of-lock de environment para geometry quando os satélites perdidos eram baixos, ou geometry para inconsistency quando o HDOP declarado discorda do céu declarado), a série antiga deixa de ser exportada e uma nova começa. Consulte por reason com max by (reason) quando a classe não importa, e espere que essa troca pareça uma série terminando e outra começando. As séries do esquema 1,3, o rótulo class e os campos de geometria de CEF/LEEF são verificados pelos testes do próprio mecanismo e pelas referências independentes de CEF (pycef, Logstash); as referências independentes externas de Prometheus e OTLP ainda não têm fluxo 1,3.
Os nomes de motivo são cortados em 128 caracteres, e no máximo 64 motivos distintos recebem série própria; os posteriores são contados em reason="other" e em kshana_trust_reason_overflow_total, de modo que uma entrada hostil ou com defeito não consegue criar cardinalidade ilimitada de rótulos.
band é um entre calibrating, nominal, degraded, untrusted, unknown. Os nomes de motivo são os nomes dos monitores do fluxo (kinematic, heading-course, ...; para o resultado em lote, cn0-drop, agc, raim).
Um painel de exemplo está em deploy/grafana/kshana-gnss-trust.json (importe-o e escolha sua fonte de dados Prometheus).
Syslog: CEF and LEEF#
Os eventos são enviados em um envelope RFC 5424 (facility local0, app kshana, id de mensagem GNSSTRUST) sobre UDP (um datagrama cada; IPv4 ou IPv6) ou TCP, terminados em nova linha por padrão ou, com --syslog-octet-counting, com enquadramento <length> <message> como na RFC 6587. Por padrão um evento é enviado a cada mudança de faixa; --syslog-all envia um por época. O fornecedor é escrito Ashforde OU (ASCII) para que nenhum SIEM o decodifique errado.
| Conceito | Severidade do syslog | Severidade do CEF | 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 |
Mapeamento de campos (o id do evento é gnss-trust.<band>):
| Significado | CEF | LEEF 2,0 (delimitador ^) |
|---|---|---|
| categoria | cat=gnss-trust |
cat |
| host de envio | dvchost |
devHost |
| faixa | cs1 (rótulo band) |
band |
| motivos, separados por vírgula | cs2 (reasons) |
reasons |
| faixa anterior | cs3 (previousBand) |
previousBand |
| pontuação | cfp1 (trustScore), omitido se não houver |
trustScore |
| deslocamento da época, s | cfp2 (epochOffsetSeconds) |
epochOffsetSeconds |
| rótulo de tempo do registro | cs4 (logTime) |
logTime |
| estado da comporta (gate) | cs5 (gate) |
gate |
status de geometria, depois ; e as classes das deduções da época, separadas por vírgula (por exemplo degraded;environment,geometry) (entradas 1,3); a partir de entradas que trazem o rótulo (0,36), um terceiro campo, ; e o rótulo, com o campo de classes então sempre presente (por exemplo degraded;;poor, good;;fair) |
cs6 (geometry) |
geoStatus (somente status), geoClass (classes) e geoLabel (rótulo) |
| HDOP (1.3) | cfp3 (geometryHdop) |
geoHdop |
| PDOP (1.3) | cfp4 (geometryPdop) |
geoPdop (0,36, com o rótulo) |
| VDOP (0,36, com o rótulo) | não transportado: os quatro campos personalizados de ponto flutuante já estão ocupados | geoVdop |
| satélites usados (0,36, com o rótulo) | cn1 (geometryUsedSatellites), um inteiro |
geoUsed |
| perda de sinal: satélites à vista não rastreados enquanto sua geometria é boa (1.3) | não transportado como campo (nenhuma chave do dicionário CEF está livre para isso): o status aparece como degraded e a classe da dedução de geometria environment em cs6 |
geoSignalLoss=1, somente enquanto vale |
Em CEF, a geometria usa apenas campos personalizados do dicionário (sem chaves inventadas); o rótulo viaja em cs6 em vez de flexString1 porque o codec CEF do Logstash não mapeia os campos flexString. Os eventos de geometria 1,3, inclusive os casos de rótulo, fazem parte da linha CEF VALIDATED: ambos os analisadores usados como referência independente os leem de volta como foram escritos (tests/cef_reference.rs, emendas 4 e 5, cada uma registrada antes de qualquer analisador ser executado em seus casos). Os campos de rótulo, VDOP e contagem de satélites são acrescentados após todo campo e série anteriores, e somente para entradas que trazem o rótulo, de modo que uma entrada sem rótulo é renderizada exatamente como antes.
Campos vazios são omitidos (sem cs2= quando não há motivos). O nome do host é cortado em 255 caracteres. Os valores de cabeçalho CEF escapam \ e |; os valores de extensão escapam \, = e quebras de linha. Nos valores LEEF, ^ e quebras de linha são substituídos por espaços.
OpenTelemetry (desativado por padrão)#
Compile com --features otlp e passe --otlp http://host:4318/v1/metrics. O exportador envia por POST JSON OTLP/HTTP (kshana.trust.score, kshana.trust.band, kshana.trust.epochs, kshana.trust.reason.epochs, e para entradas do esquema 1,3 kshana.geometry.status, kshana.geometry.degraded, kshana.geometry.signal_loss, kshana.geometry.hdop, kshana.geometry.pdop, kshana.geometry.used_satellites e, a partir de entradas que trazem o rótulo (0,36), kshana.geometry.label (atributo label) e kshana.geometry.vdop; as somas cumulativas trazem startTimeUnixNano) a cada --otlp-every épocas (padrão 10) e no fim da entrada. Não acrescenta dependência e não tem TLS: envie a um coletor no mesmo host ou em um segmento confiável, que repassa adiante por TLS. Endpoints https:// são recusados.