fix(forge): the chain hosts keep time, and the converge scripts default to the production CA #768

Merged
binjovi-bot merged 1 commit from fix/host-clock into trunk 2026-09-14 20:30:58 +00:00
Owner

What

Both chain hosts now keep time, reproducibly, and the converge scripts default to the production CA.

  • scripts/freebsd/forge-jail.shHost clock (ntpd): ntpd_enable, ntpd_sync_on_start (steps a large offset), start if stopped. The public address is also on lo0; ntpd 4.2.8p18 fails the duplicate bind with EADDRINUSE and then dies with SIGSEGV, so ntp.conf gets interface ignore lo0. One interface listen rule makes ntpd ignore every unnamed address (measured: with only loopback named, em0 got no socket and the clock stayed 36 s out), so loopback and the default-route NIC are named explicitly. Each rule is appended once.
  • scripts/omnios/forge-zone.shHost clock (chrony): pool line and makestep 1.0 3 ensured in /etc/inet/chrony.conf; enabled without -s (a first enable reports unsatisfied dependencies and exits 4 while they come up) and the script waits for online itself; config edits restart the service.
  • tests/forge-packet-town.sh, tests/forge-kri-pt.sh — assert the time service and at most 5 s of skew against the machine running the guard.
  • ACME_CA defaults to letsencrypt. Both identifiers were approved for LE-prod earlier today; the staging default fired on a routine converge and re-issued STAGING on both tiers (every TLS client, the converge's own account step included, stopped verifying). Re-issue happens only when the installed CA or CN differs.
  • README (measured behaviour) and CHANGELOG.

Measured

host before after
omnios-hel1 −117 s, chrony installed but disabled chrony online, 0 s
freebsd-fsn1 −34 s, ntpd disabled ntpd running with peers reaching, <1 s

Both converges ran to EXIT=0 with production certificates back (CN=YE2); both guards pass. LE-prod issuance today: two per identifier (limit five per 168 h).

https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6

## What Both chain hosts now keep time, reproducibly, and the converge scripts default to the production CA. - `scripts/freebsd/forge-jail.sh` — **Host clock (ntpd)**: `ntpd_enable`, `ntpd_sync_on_start` (steps a large offset), start if stopped. The public address is also on `lo0`; ntpd 4.2.8p18 fails the duplicate bind with `EADDRINUSE` and then dies with SIGSEGV, so `ntp.conf` gets `interface ignore lo0`. One `interface listen` rule makes ntpd ignore every unnamed address (measured: with only loopback named, `em0` got no socket and the clock stayed 36 s out), so loopback and the default-route NIC are named explicitly. Each rule is appended once. - `scripts/omnios/forge-zone.sh` — **Host clock (chrony)**: pool line and `makestep 1.0 3` ensured in `/etc/inet/chrony.conf`; enabled without `-s` (a first enable reports unsatisfied dependencies and exits 4 while they come up) and the script waits for `online` itself; config edits restart the service. - `tests/forge-packet-town.sh`, `tests/forge-kri-pt.sh` — assert the time service and at most 5 s of skew against the machine running the guard. - `ACME_CA` defaults to `letsencrypt`. Both identifiers were approved for LE-prod earlier today; the staging default fired on a routine converge and re-issued STAGING on both tiers (every TLS client, the converge's own account step included, stopped verifying). Re-issue happens only when the installed CA or CN differs. - README (measured behaviour) and CHANGELOG. ## Measured | host | before | after | |---|---|---| | omnios-hel1 | −117 s, chrony installed but disabled | chrony online, 0 s | | freebsd-fsn1 | −34 s, ntpd disabled | ntpd running with peers reaching, <1 s | Both converges ran to `EXIT=0` with production certificates back (`CN=YE2`); both guards pass. LE-prod issuance today: two per identifier (limit five per 168 h). https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6
Measured 2026-09-14: freebsd-fsn1 ran 34 s and omnios-hel1 117 s behind UTC.
Nothing synchronised either clock, and snapshot names, certificate validity
and token lifetimes on both tiers read it.

scripts/freebsd/forge-jail.sh gains a "Host clock" step: ntpd_enable and
ntpd_sync_on_start (-g, so a large offset is stepped rather than refused),
started if not running. The host carries its public address on lo0 as well
as em0 (k3s and pf plumbing); ntpd 4.2.8p18 binds every address per
interface, fails the lo0 duplicate with EADDRINUSE and then dies with
SIGSEGV, so ntp.conf gets `interface ignore lo0`. One `interface listen` rule
makes ntpd ignore every address no rule names (measured: with only loopback
listed, em0 got no socket, the pool associations never reached, and the
clock stayed 36 s out), so loopback and the default-route NIC are named
explicitly. Idempotent: each rule is appended once.

scripts/omnios/forge-zone.sh gains the same step with chrony, which OmniOS
ships (service/network/ntp is renamed away): the pool line and `makestep
1.0 3` are ensured in /etc/inet/chrony.conf, the service is enabled without
-s -- on a first enable svcadm reports "unsatisfied dependencies" and exits 4
while they are still coming up, and chrony is online seconds later -- and
the script waits for online itself. Configuration edits restart the
service; nothing else touches it.

Both guards assert the time service (ntpd running / chrony online) and at
most 5 s of skew against the machine running them, which macOS keeps within
milliseconds of NTP.

ACME_CA now defaults to letsencrypt. Both identifiers were approved for
LE-prod earlier today and the first converge issued them; the staging
default was a live footgun, and it fired: one routine converge without
ACME_CA re-issued STAGING certificates on both tiers, and every TLS client
-- the converge's own account step included -- stopped verifying. Re-issue
happens only when the installed CA or CN differs, never on every run
(LE-prod allows five certificates per identifier per 168 h; this used two
of each today).

Both converges ran to EXIT=0 with production certificates back; both guards
pass with the skew now under a second.

Claude-Session: https://claude.ai/code/session_01XdBRc9CHgFhAxCyHbdVtt6
sean force-pushed fix/host-clock from be67c19020
Some checks failed
binjovi/ci Binjovi failed the frozen plan
to 853bf66127
Some checks failed
binjovi/ci Binjovi failed the frozen plan
2026-09-14 20:27:27 +00:00
Compare
sean force-pushed fix/host-clock from 853bf66127
Some checks failed
binjovi/ci Binjovi failed the frozen plan
to aee8fb7ba3
All checks were successful
binjovi/ci Binjovi completed the frozen plan
2026-09-14 20:29:28 +00:00
Compare
binjovi-bot deleted branch fix/host-clock 2026-09-14 20:30:59 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/seanfarm!768
No description provided.