Skip to content

v2.3.0

Choose a tag to compare

@seitzbg seitzbg released this 08 Oct 04:39
· 3 commits to main since this release
e7dbe41

Backward-compatible fix release: everything from the 2026-10-07 whole-repo review.

Upgrade notes

  • Vantage certificates are now tracked by serial. On first start the hub records that existing vantages are legacy registrations: their deployed certificates keep working with no action needed. Revoking a vantage and re-adding it now really retires its earlier certificates; re-add a legacy vantage once to bring it under serial checking. See Certificate serial tracking in docs/federation.md.
  • FreeBSD rc.d: the extra-flags setting is renamed from smoked_flags / smoke_agent_flags to smoked_args / smoke_agent_args. A leftover _flags value still works and logs a warning. The v2.2.0 scripts could not start their services.
  • SmokePing --history: rows are now stored under the configured target's stable id. Pass --config if your targets live in YAML.

Fixed

  • Fresh container image builds work again. The collector Dockerfile and Caddy.Dockerfile pinned
    security-patched Alpine packages (libcrypto3/libssl3, and in the Caddy image curl/libcurl/
    c-ares) to exact revisions. Alpine's repository keeps only the newest revision of each package, so
    once a newer revision was published (libcrypto3/libssl3 3.5.9-r0, curl 8.22.0-r0), the pinned ones
    disappeared and every fresh build failed in the apk step. Those pins are now >= minimum
    versions: the CVE fixes still can't regress, and newer revisions install without breaking the build.
  • A delayed NTP round no longer brings back an obsolete clock offset after it was cleared. When a
    remote vantage reports an unsynchronized round, the hub clears that vantage's clock stat for the
    target, but the clear also dropped the stat's freshness timestamp. A store-and-forward replay of an
    older synchronized round could then re-set the offset, so /api/targets?vantage=… and the
    dashboard showed an out-of-date offset next to the newer round. The clear now keeps its timestamp,
    so only a round newer than it can set the stat again.
  • A config reload no longer pre-fills a redefined target's alert window with its old measurement.
    On a reload (SIGHUP or a config apply), alert windows are warm-started from stored history, which
    was matched only by host, probe, metric and step. Changing what a target measures without changing
    those, such as a TCPConnect port from 443 to 22, re-imported the old port's rounds, so a
    CheckLoss(x=3) alert fired on the first failure of the new port. A target whose measurement
    identity changed in a reload is no longer warm-started: its alerts wait for x rounds of the new
    measurement. A target that only gains an alert on reload is still warm-started. On a cold start,
    smoked has no record of a target's previous definition, so a target redefined while smoked was
    stopped can still be warm-started from its recent old rounds.
  • smoked import smokeping --history now stores history where the target's graph reads it.
    --apply gives every imported target a new stable id, but --history stored each RRD's rows
    under the SmokePing path, so the backfilled history never appeared on the configured target's
    graph or in /api/series. --history now looks each target up by path in the DB config and, when
    given, the YAML config passed with --config, and stores its rows under that target's id. A target
    that matches no configured target, or more than one, is named on stderr, counted as unresolved
    in the summary, and not imported; the run then exits 3, the same as other partial failures.
    With no --config and no targets in the DB config, rows are still keyed by SmokePing path, which
    matches a YAML target that has no id, and the run says so. History already imported under
    SmokePing paths is not moved; re-run --history to store it under the target ids.
  • The FreeBSD rc.d scripts now actually start their services. The v2.2.0 scripts in
    contrib/freebsd/ built a daemon(8) command line that could never run, for
    three independent reasons: the unquoted two-word title (-t Heliograph hub) split, so daemon(8)
    tried to run hub / vantage as the program; rc.subr hands ${name}_flags to $command, so
    smoked_flags (default -downsample) went to daemon(8), which rejected it; and rc.subr started
    daemon(8) through su as smoke, which then could not create /var/run/<name>.pid (and was also
    given -u smoke). The scripts now follow the FreeBSD ports convention: rc.subr runs daemon(8) as
    ${name}_user with no -u, the pidfile lives in a /var/run/<name>/ directory that user owns,
    and the process title and syslog tag are the service name, so output is logged as smoked /
    smoke_agent rather than daemon. Knob rename: extra flags move from smoked_flags /
    smoke_agent_flags to smoked_args / smoke_agent_args. A leftover _flags value is still used,
    with a warning. Also fixed: smoke_agent_spool="" now disables the on-disk spool as documented
    (the old default assignment replaced an empty value, so the agent always spooled), and
    smoked_args="" now drops the default -downsample. The freebsd-test CI job now installs both
    scripts in its FreeBSD VM and drives start/status/restart/stop through service(8), checking the
    service user, argv, environment and pidfile, plus smoked's API.
  • heliograph_triage covers the hub's own local vantage and works on a single-host hub. It
    took its vantage list from the remote-vantage registry (GET /api/admin/vantages), which never
    includes local. With a remote vantage registered, triage never read local measurements, so a
    target at 100% loss from the hub but healthy from the remote was reported healthy, and
    vantage=local was rejected as unknown. On a hub without the registry (no database or no admin
    password, e.g. the memory-store demo), triage failed outright with a 404. It now reads the
    vantages from each target's own vantage set in /api/targets, counts a target only at the
    vantages that measure it, and uses the registry only for stale-collector detection when the hub
    has one. Errors other than an absent registry (a 5xx, an auth failure) still fail the call.
    heliograph_vantages on a hub without the registry now explains that instead of returning
    "404 page not found".
  • config_stage_edit_target no longer deletes a target moved into its own subtree. Moving a
    target to a group path at or below itself (e.g. g/a into g/a/sub) detached it into an
    unreachable cycle, and the target vanished from the staged config with no error. The move is
    now rejected and the staged config is left unchanged.
  • MCP config change summaries report group-level changes. The config_stage_* results and
    config_review compared only host-bearing nodes, so changing a group's own settings (its step,
    probe params, vantages, alerts) was summarized as no change at all, although it changes every
    target beneath it. Summaries now list groups added, removed, or with changed settings, and count
    a target as changed when a group above it changes a setting it inherits.
  • MCP config staging validates against the hub's real file config. Local validation composed
    the staged DB fragment onto a defaults-only config, so a DB target that inherits its probe from
    the hub's file config (valid on the hub, and allowed by the dashboard) failed with "no probe set",
    and because every edit revalidates the whole fragment, one such target blocked all staging.
    Staging now derives the hub's file config from its effective config and composes onto it as the
    hub does. When that can't be reproduced locally, problems are reported as warnings instead of
    blocking, and config_apply's server-side validation stays authoritative.

Security

  • Revoking and re-adding a vantage now retires its earlier certificates. Agent authorization
    checked only the client certificate's CommonName against the registry, so after
    smoked vantage revoke <name> followed by vantage add <name> — the documented way to retire a
    credential — every certificate ever issued for that name (CA-signed, valid for ten years)
    authenticated again. The hub now records the serial of every certificate it issues and accepts a
    certificate only if its serial was issued for the vantage's current registration; revoking drops
    the recorded serials, so a re-added name accepts only certificates issued after the re-add.
    Regenerate (the dashboard's Regenerate, or vantage add for a registered name) is unchanged:
    earlier certificates keep working alongside the new one. Upgrade: vantages registered before
    this release have no recorded serials, so on the first start they become legacy registrations
    that keep accepting any hub-CA certificate bearing their name — deployed agents keep reporting
    with no action — until they are revoked. Revoke and re-add such a vantage once (and redeploy its
    new bundle) to bring it under serial checking. Certificates minted by an older smoked binary
    against an upgraded database are not recorded and are refused for vantages registered after the
    upgrade. See Certificate serial tracking in docs/federation.md.
  • golang.org/x/text bumped from v0.39.0 to v0.42.0 for GO-2026-6629
    (a panic in x/text/secure/precis, fixed in v0.41.0). govulncheck flagged it through pgx's
    SCRAM password handling. That path runs the operator-supplied database password through a
    different precis profile from the one the advisory names, so no remote trigger is known; the bump
    clears the scan. Caddy's --with golang.org/x/text floor is raised to match.
  • smoked vantage add -out writes the onboarding bundle owner-only. The .tar.gz embeds the
    vantage's client private key in agent.yaml, but was created with the default 0666-minus-umask
    mode, which is world-readable (0644) under the usual 022 umask. It is now created 0600
    regardless of umask, and an existing file that -out overwrites is tightened to 0600 before any
    key material is written to it.
  • smoked mcp no longer follows a redirect off the hub's origin. The client already refused a
    plaintext http:// hub URL when Basic Auth or an admin password is set, but it followed
    redirects: a 307 from the HTTPS login URL replayed the admin-password body to the redirect
    target, and Go forwards the Basic Auth header to the same hostname even across an
    https:// → http:// downgrade. Any redirect that leaves the configured scheme, host and port is
    now refused for every request (login, retries, ordinary reads). The hub API never redirects.

Full changelog: v2.2.0...v2.3.0