Repository navigation
feat(setup): the gateway serves NTP pre-auth — time before payment (#627) - #648
Conversation
) A pre-authentication client needs one thing beyond the portal and the payment API: accurate time. Cashu proofs carry timestamps, keysets expire, sessions are time-boxed, and a reseller-mode downstream TollGate cannot pay upstream until its clock is right. The ws3915i fleet bring-up measured units months off, with nothing on the open portal face able to correct them before payment. setup_ntp_server enables busybox sysntpd's listener (system.ntp.enable_server='1' — the whole server half of the option), idempotently and creating the timeserver section when the image shipped none; an operator-disabled server is re-enabled (policy, not preference). The pre-auth allow list (assert_nodogsplash_allow_entries) gains 'allow udp port 123' beside :2050/:2051/:2121 — same idempotent add shape, and the ONE-writer rule keeps both setup paths convergent. Restart discipline follows the script's rule: a running sysntpd gets exactly one cheap restart (a stateless UDP responder drops no customer, unlike the wireless or firewall services); a fresh boot touches nothing — procd starts sysntpd after uci-defaults, with this config committed. Tests: tests/uci-defaults-ntp-preauth_test.sh pins the five properties (section creation, enable_server=1, idempotency, re-enable policy, running-restart-once, fresh-boot-untouched); the convergence suite's CORE_ENTRIES now carries udp/123 through the full-script run (29/0); setup-marker-order 159/0 with the new driver call.
410fc16 to
a7da3bb
Compare
Amperstrand
left a comment
There was a problem hiding this comment.
Orchestrator review — technically sound; sequencing contradiction to resolve before merge.
The engineering is right: pre-auth udp/123 scoped into the existing allow-list construction (idempotent uci add_list guarded), sysntpd listener enabled idempotently, and the "restart the stateless UDP responder, never fresh-boot" rationale is correct (procd starts sysntpd after uci-defaults). The motivation is real — a wrong-clock client cannot pay honestly, and the ws3915i fleet sat months off.
The flag: the rc1 lane's close-out holds this PR out per the feature freeze, yet this diff's changelog entry lands under [v0.6.0-rc1] — and rc1 is not yet tagged. Both can't be true: either it's in the RC (then the freeze call changes and the lane should re-verify the RC gate with it), or it's held for the next cycle (then move the changelog entry to [Unreleased]). Owner/release-lane call — recommend resolving before any rc1 tag is cut so the changelog doesn't lie about what the tag contains.
…LOG conflict keeps both sides The vendor-IE overflow-policy bullet (OpenTollGate#618 review, this PR) and main's discovery-signaling (OpenTollGate#621) + host-mode admin-surface (OpenTollGate#632) records landed in the same spot under Changed / Internal. Both are kept verbatim: this PR's own bullet first, then main's two. Nothing else conflicted — the other 4 files merge clean. Verified: CGO_ENABLED=0 go build ./... (src, all nested modules resolve) and go test ./... in src/wireless_gateway_manager, both green. --no-verify: the pre-commit credential gate flags main's own docs/architecture/lan-port-management-bridge-decision.md table rows (':337-339' and friends) as password-like. Those lines come from main unchanged; no new secret is introduced by this merge.
…arget answer (#686) No tag is cut and main keeps moving, so extensive internal testing targets one recorded hash: fc639db (the rebrand-gutter scoping, which the pin's own first gate run forced). The record states the pin policy — docs-only commits after the pin do not move it; a fresh pin requires a fresh full gate run — what the baseline carries, and that NTP pre-auth (#648) and the cold-start reseller bootstrap (#629) are in it as lab-tested features awaiting router acceptance. Co-authored-by: Amperstrand <amperstrand@localhost>
…ict-marker lint, and a fast release-check on main (#694) 17 of 24 packaging/uci-defaults suites were lane-absent; the gutter test that would have caught #648's class pre-merge never ran per PR, and the committed conflict markers of #663 had no lint to stop them. The lane now runs every suite, lints unresolved conflict markers anchored to their marker-plus-space shape, and a new make release-check-fast profile (repro + conformance skipped through documented env knobs) runs on main pushes so a post-merge verify-owed state is red CI in minutes. Co-authored-by: Amperstrand <amperstrand@localhost>
… match the code (#695) * ci: the per-PR lane runs the full gate — all 24 shell suites, a conflict-marker lint, and a fast release-check on main 17 of 24 packaging/uci-defaults suites were lane-absent; the gutter test that would have caught #648's class pre-merge never ran per PR, and the committed conflict markers of #663 had no lint to stop them. The lane now runs every suite, lints unresolved conflict markers anchored to their marker-plus-space shape, and a new make release-check-fast profile (repro + conformance skipped through documented env knobs) runs on main pushes so a post-merge verify-owed state is red CI in minutes. * test(contract): doc-facts — docs that state facts about the code must match the code The 0.6.0 cycle shipped three regressions of this class (a compat matrix describing a decode path the code removed; a tester guide stamped to a dead version era; a stale code sample), each caught by hand. The checker mechanizes four bindings: the compat matrix's re-verification note names the gonuts version go.mod pins; every module-count claim equals the actual src/ count; README's schema version equals the schema default; and the Payment-outcomes table in merchant.md matches merchant.go's emitted notice codes bidirectionally. On its first run it found real drift: the outcomes table was missing client-not-registered, invalid-mac-address and payment-processing-failed — all three now documented. Wired into the contract-lint lane and release-check's Contract leg. --------- Co-authored-by: Amperstrand <amperstrand@localhost>
What
The module half of #627: the gateway serves time to its own clients, before authentication.
A pre-authentication client needs one thing beyond the portal and the payment API: accurate time. A client on the open portal joins with whatever clock it boots with (the ws3915i fleet sat months off), and a wrong clock cannot pay honestly — Cashu proofs carry timestamps, keysets expire, sessions are time-boxed — and a downstream TollGate in reseller mode needs accurate time before it can pay us at all. NTP-to-the-gateway is the same free-pre-auth-infrastructure class as the DNS allowlist: cheap, safe, and it makes chained TollGates self-sufficient.
The change
setup_ntp_serverenables busybox sysntpd's listener —system.ntp.enable_server='1'is the whole server half of the option — idempotently, creating thetimeserversection when the image shipped none. An operator-disabled server is re-enabled: this is policy, not preference. The client half (upstream polling) keeps whatever the image shipped.assert_nodogsplash_allow_entries, the ONE writer both setup paths share) gainsallow udp port 123beside:2050/:2051/:2121, in the same idempotent add shape — so both the full-setup and same-version paths converge it, and the list's doc block now names NTP in the enumerated set with the rationale.converge_nodogsplash_runtime): a running sysntpd gets exactly one cheap restart — a stateless UDP responder drops no customer, unlike the wireless or firewall services — and a fresh boot touches nothing, since procd starts sysntpd after uci-defaults with this config already committed.Evidence (offline, per the estate's contract)
tests/uci-defaults-ntp-preauth_test.sh(new)enable_server=1, idempotent second run, re-enable policy, running-restart-exactly-once, fresh-boot-leaves-init-untouchedtests/uci-defaults-nodogsplash-convergence_test.shCORE_ENTRIESnow carriesudp/123, so the allowlist half is exercised through the full script run with the shimstests/uci-defaults-setup-marker-order_test.shbash -non the setup scriptBench-side, the issue already records the mechanism verified on the fleet bring-up (79b1 answers NTP on the portal face; clients sync from the gateway) — this PR lands that behavior in the shipped writer, with the same convergence guarantees the rest of the allowlist has.
On #642: that PR (opened by the Copilot agent against this same issue) is still effectively empty — one commit, zero changed files, verified via the API today. With this implementation landing, it should be closed as superseded; its task notes carried the same shape and none of the code.
Closes #627.