Repository navigation
feat(ingestor): accept client RX coverage only from configured sources (#265) - #274
Conversation
Trust in meshcore/client/{PUBLIC_KEY}/packets rests entirely on the broker
binding the topic pubkey to the publisher holding that key. An instance that
reads several brokers can mix one that enforces that binding with a legacy
username/password broker where many accounts may write meshcore/#: enabling
coverage there trusts the weakest source, and any account on it could inject
coverage under any companion pubkey with any GPS position.
clientRxCoverage.sources is an optional allowlist of mqttSources[].name. When
set and non-empty, the client namespace is handled only for messages that
arrived on a listed source; a message from any other source is dropped before
any write (no client_receptions, no client_observers, and no observer row — the
namespace still always returns), logged per source with a throttle and a line
cap. Absent or empty keeps every source accepted, so the default is unchanged.
A name matching no configured source is reported once at startup, since an
allowlist that can never match would otherwise drop all coverage silently.
The blacklist check still runs first, so it holds whatever the source.
Relates to #265
…is off The boot line reported "coverage restricted to N MQTT source(s)" even with clientRxCoverage.enabled false, where the allowlist is inert and no coverage is ingested from any source. Name that state instead of implying the listed sources are contributing. Relates to #265
Rapport — CS-Macmini PR#274 #265 — head e75c190Status: Implemented and verified locally; draft PR open, CI green (one unrelated E2E flake re-run once, detailed below). Evidence tags: [T] test, [A] analysis/code reading, [K] command output. Acceptance criteria
Behaviour summary"mqttSources": [
{ "name": "device-auth", "broker": "mqtts://…" },
{ "name": "legacy", "broker": "mqtts://…" }
],
"clientRxCoverage": { "enabled": true, "sources": ["device-auth"] }Matching is on the source name, trimmed and case-insensitive; a source with no Local verification [K]
Mutants run one at a time against the committed tests, then reverted [K]:
CI per jobRun 37352751184, head
One flaky E2E failure, re-run once. The first E2E attempt failed on a single step in Remaining / notes
|
Review — CS-pve-agent1 PR#274 — head e75c190Dom: APPROVE with nits Independent, read-only review. Evidence tags: [T] test I ran, [A] analysis / code reading, [K] command output. Everything was run on the head archive and on the merged tree Findings
Answers to the review points
Always-checks:
Acceptance criteria → test, red before / green afterOn base the PR tests do not compile (no
Tests and mutantsOn the merged tree:
Note: my first parallel run of both suites hit Go's default 10-minute My own mutants, one at a time, run against the PR tests plus
E2E against a local Go server, merged tree,
Default config (coverage off, as in CI):
Config with
CI (run 37352751184, attempt 2, head Not verified
No pushes, merges, ready-state changes or issue creation; all work was in scratch copies. |
Relates to #265
Plan
clientRxCoverage.sourcesallowlist (ofmqttSources[].name) to the ingestor config, with a trimmed, case-insensitive matcher that returns "allow everything" when the list is absent, empty, or blank-only.meshcore/client/...dispatch inhandleMessageon that matcher — after the observer-blacklist check, before any coverage write — and return from the client branch on a drop, so the namespace still never falls through to the observer path.observerIATAWhitelistwarning (iata_drop_warn.go). Throttle state lives on theConfig, so there is no package-global to reset.sourcesname that matches no configured source — an allowlist that can never match would otherwise drop all coverage in silence.docs/client-rx-coverage.mdandconfig.example.json.Why
Trust in
meshcore/client/{PUBLIC_KEY}/packetsrests entirely on the broker binding the topic pubkey to the publisher that holds that key (docs/client-rx-coverage.md, "Trust"). An instance can read several brokers with different authentication — one that binds the topic to a per-device identity, plus a legacy username/password broker where many accounts may writemeshcore/#. Without this option, enabling coverage trusts the weakest of them: any account on the legacy broker could inject coverage under any companion pubkey, with any GPS position. The allowlist keeps the client namespace on the sources that actually enforce the binding, while the other source keeps contributing ordinary observer traffic unchanged.Behaviour
sourcesset and non-empty →meshcore/client/...is handled only from a listed source. From any other source the message is dropped before any write: noclient_receptionsrow, noclient_observersrow, and no observer row (the client namespace still always returns from its branch).sourcesabsent, empty, or blank-only → every source is accepted, exactly as before. Upstream-compatible default.namecan never be listed, so it is rejected whenever an allowlist is set.Files
cmd/ingestor/config.go—ClientRxCoverageConfig.Sources,ClientRxCoverageSources(),ClientRxSourceAllowed(), per-Configthrottle field.cmd/ingestor/client_rx_sources.go(new) — throttled/bounded drop warning +checkClientRxSourcesstartup validation.cmd/ingestor/main.go— the dispatch guard and the startup call.cmd/ingestor/client_rx_sources_test.go(new) — tests, all ingest assertions through the realhandleMessagepath with a named source.docs/client-rx-coverage.md,config.example.json— documentation.cmd/serveris untouched (it ignores the new field; its read endpoints stay gated byenabledalone). No newmap[string]interface{}. Fork guards unchanged: 9 indeploy.yml, 1 inrelease-fast-path.yml.