github netresearch/ofelia v0.25.0

4 hours ago

Highlights

A security-focused minor release: the Go 1.26.3 toolchain clears six stdlib advisories, three silent-downgrade vectors in TLS handling are closed, and tcp+tls:// Docker hosts work again with the cert material the operator declared. MaxRuntime-bounded jobs now properly clean up their containers, and the [global] config story is finally consistent across INI and Docker labels.

๐Ÿ” Security

  • Go toolchain 1.26.2 โ†’ 1.26.3 clears net/mail, html/template, net, and net/http advisories reachable from this codebase (GO-2026-4986, -4982, -4980, -4977, -4971, -4918). Post-bump, only the two unfixable upstream moby advisories on docker/docker v28.5.2 remain. (#662)
  • No more silent TLS downgrades in three places where Ofelia previously masked an operator misconfiguration as fail-open: HTTPS Docker daemons with broken cert material, SMTP middleware on servers that don't advertise STARTTLS, and webhook URL allow-lists collapsed to ["*"] by a typo. Each one now fails closed with a typed sentinel and a clear log line at startup. (#660, #646)
  • โš ๏ธ Operator impact if you relied on OpportunisticStartTLS for SMTP relays that don't advertise STARTTLS (legacy local relays, MailHog dev fixtures): set the new smtp-tls-policy = opportunistic (or = none for test fixtures only) to restore the previous behavior. The new default is mandatory. See docs/TROUBLESHOOTING.md for migration recipes.
  • Remote preset fetches (webhook-allow-remote-presets = true) now route through the same TransportFactory() the webhook stack uses, instead of the implicit http.DefaultClient. (#624)

๐Ÿณ Docker daemon connectivity

  • tcp+tls:// is back on the DOCKER_HOST allow-list now that the TLS plumbing landed โ€” DOCKER_CERT_PATH / DOCKER_TLS_VERIFY (and the equivalent ClientConfig overrides) are wired into the HTTP transport, and tcp+tls:// without cert material now fails loud instead of silently dialing TLS with the system CA pool. (#625)
  • DOCKER_HOST schemes are now validated against an allow-list (unix://, tcp://, tcp+tls://, http://, https://, npipe://) and normalized to lowercase. Unsupported values (ssh://, fd://, typos) fail at startup with a clear error instead of falling through to plain-TCP. (#612)
  • DOCKER_HOST=tcp://... plus DOCKER_CERT_PATH now actually negotiates TLS end-to-end โ€” the SDK URL is silently upgraded to https:// so the configured cert and pinned CA apply on the wire (mirroring the docker CLI). Previously the cert was loaded but the SDK still spoke plain TCP. (#647)
  • Startup is no longer hostage to a wedged daemon: NegotiateAPIVersion, periodic health/ready pings, the doctor diagnostic, and remaining unbounded Docker SDK calls are all wrapped in context.WithTimeout. (#611, #636)

๐Ÿงน Correctness

  • MaxRuntime cancellation now stops and removes the container or swarm service. The deadline-wiring fix in #651 returned control to the wrapper, but the deferred cleanup reused the already-cancelled parent context โ€” so stop/remove API calls were rejected before they reached the daemon. The cleanup path now uses a fresh context.WithTimeout(context.Background(), jobCleanupTimeout) and RunServiceJob mirrors the same fix. Operators previously seeing Exited containers piling up should see them properly cleaned after this release. (#659)
  • [global] Docker label keys now reach the live config across every subsystem. Setting e.g. ofelia.smtp-host=mail.example.com on a service container previously decoded into a scratch struct that was discarded after the per-job merge โ€” Slack, Mail, Save, scheduling, and the runtime knobs (log-level, notification-cooldown) all silently kept their INI defaults. New per-subsystem helpers and an applyAllowListedGlobals aggregator wire every allow-listed non-webhook global through both the boot and reconcile paths with the same "INI wins when set; label only fills empty/default" precedence as mergeWebhookGlobals. (#661)
  • Pervasive nil-guard sweep across the Docker adapter family. Every public method on every *ServiceAdapter (Container, Exec, Image, Event, Network, Swarm, System) returns a typed sentinel (ErrNilDockerClient, ErrNilExecConfig, ErrNilContainerConfig) instead of panicking if its internals are misconstructed; every convertTo* / convertFrom* helper nil-guards its pointer argument and returns a zero value. (#626, #639, #648, #658)

๐Ÿ“– Docs and DX

  • Webhook config has one source of truth now: c.WebhookConfigs.Global aliases &c.Global.WebhookGlobalConfig once in NewConfig(), eliminating the dual-store anti-pattern that needed a hand-rolled syncGlobalWebhookConfig shim. INI live-reload of webhook-allowed-hosts now also re-runs WebhookManager.InitManager() so the URL validator picks up changes at runtime. (#637)
  • Docker label ofelia.webhooks deprecated in favor of ofelia.webhook-webhooks to match the documented INI [global] key name. The legacy form still works and logs a one-shot deprecation warning per process โ€” migrate before the next major release. (#620)
  • Documentation reconciled with reality across Slack middleware (removed never-supported keys), Save middleware (documented restore-history), webhook globals (webhook-webhooks, webhook-trusted-preset-sources, webhook-preset-cache-dir), the poll-interval split, and web-trusted-proxies. New TestConfigGlobalKeysAreDocumented walks the Config.Global reflection tree and asserts every mapstructure key is mentioned in at least one operator-facing docs file, so this drift class is now caught mechanically. (#621, #635, #656)
  • Unified Docker host / scheme resolution behind a single resolveDockerHost seam, with the dispatch table and the allow-list derived from one source so they cannot drift. (#629)

Dependencies

Go toolchain 1.26.2 โ†’ 1.26.3. Direct deps: docker/cli 29.4.2 โ†’ 29.4.3, golang.org/x/crypto 0.50 โ†’ 0.51, golang.org/x/term 0.42 โ†’ 0.43, golang.org/x/text 0.36 โ†’ 0.37. Indirect-graph refresh via go get -u all (otelhttp 0.67 โ†’ 0.68, grpc 1.80 โ†’ 1.81, x/sys/mod/net/tools bumps, genproto date refresh, docker-credential-helpers 0.9.5 โ†’ 0.9.7, morikuni/aec 1.0 โ†’ 1.1, plus tertiary bumps).

Thanks

Special thanks to the external reporters whose issues drove substantive fixes in this release:

  • @techsolo12 for #604 โ€” reporting that webhook-allow-remote-presets (and the other documented webhook-* [global] keys) emitted "Unknown configuration key" warnings and silently fell back to defaults. Fixed in #618.
  • @groknt for #605 โ€” reporting that DOCKER_HOST=tcp://... configurations failed to start because the HTTP transport's dialer was hard-pinned to unix:///var/run/docker.sock. The trail from that report cascaded into the wider TLS/scheme rework that landed across #606, #612, #613, and #629.

Container image

ghcr.io/netresearch/ofelia:0.25.0
ghcr.io/netresearch/ofelia:0.25
ghcr.io/netresearch/ofelia:0

Verify your download

Per-asset cosign bundles. Verify any single file:

cosign verify-blob \
  --bundle ofelia-linux-amd64.bundle \
  --certificate-identity-regexp "https://github.com/netresearch/.*" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ofelia-linux-amd64

Each binary also ships an SPDX SBOM (.spdx.json) with its own attached cosign bundle.

Don't miss a new ofelia release

NewReleases is sending notifications on new releases.