Moves the completed change to openspec/changes/archive/2026-10-18-advertise-valid-ipps-certificate/ and syncs the printserver/cups capability into openspec/specs/: the image dependency requirement now mentions the TLS tooling, and two requirements are added for the name-valid _ipps certificate and for starting cleanly on stale runtime state.
6.3 KiB
Design
Context
See proposal.md — Why.
The relevant facts about the running system, established by inspecting a live container of this image (CUPS 2.4.2 on Debian bookworm):
- CUPS advertises the queue on both
_ipp._tcpand_ipps._tcp; macOS and iOS prefer the latter and check the certificate's subject alternative names against the name they connected to. CUPS' own clients (lpadmin,ipptool) do not perform that check. - The name clients use is the one Avahi advertises (
<host>.local, queryable over D-Bus asGetHostNameFqdn), not the container host name. The two can differ: observed example, host namehost.containers.internalserved while Avahi advertisedpodp.local. - CUPS generates a certificate on demand for the host name, so that
certificate cannot cover names the host name does not imply (in the example
above,
podp.localwas absent, and so was a.localform, because CUPS only appends.localto dot-less host names). - In CUPS 2.4,
ServerCertificate/ServerKeyare accepted and preserved bycupsctl(which rewritescupsd.conf), but cupsd still served the certificate kept at<certificate dir>/<host name>.crt. Writing our certificate to that path is what changed the served certificate. /runsurvives a container restart (podman restart, systemd restart), and/etc/cupsis a named volume that survives container replacement.
Goals / Non-Goals
Goals:
- The certificate CUPS presents for
_ippsvalidates for every name a client may use, including the Avahi-advertised one. - Restarting the container is safe and does not reissue the certificate.
- Everything stays self-contained: no external CA, no configuration file edits by the user, no new volumes.
Non-Goals:
- Obtaining a publicly trusted certificate or running a private CA.
- Obtaining the printer's own (non-AirPrint) capabilities over IPP.
- Changing how jobs are converted or delivered to the printer.
Decisions
Generate the certificate in the entrypoint rather than shipping one. A committed certificate would have a fixed key (shared by every deployment), a fixed expiry, and no knowledge of the local host name. Generating at first start keeps the key per deployment and lets the SANs reflect the actual names.
Collect SANs from several sources instead of one "server name" setting.
Clients may reach the server by the Avahi FQDN, the container host name, a
.local form, an IP address or a DNS name users add themselves. Adding all of
them is cheap and only ever widens acceptance: DNS:<host name>,
DNS:<host name>.local (when the host name has no dot), the Avahi FQDN,
localhost, localhost.localdomain, host.containers.internal,
IP:127.0.0.1, and the comma-separated CERT_EXTRA_NAMES. SANs are used
because modern clients ignore the common name; the common name is set to the
host name deliberately (see next decision).
Keep the common name equal to the host name.
CUPS' certificate handling is name-based; keeping the common name equal to the
host name keeps it consistent with CUPS' own notion of its identity, while the
SANs carry the names clients actually use. basicConstraints=CA:TRUE is set so
the file can also be imported as a trust anchor on macOS/iOS.
Install the certificate in two places. As ServerCertificate/ServerKey
(the documented way, added to cupsd.conf when the directives are absent —
which also repairs a cupsd.conf that cupsctl rewrote), and as
<name>.crt/.key for both the host name and the Avahi FQDN, because that is
where cupsd 2.4 actually loaded the certificate from in testing. Belt and
braces: either path alone would be enough on some versions, together they hold
on the version in the image. The copies are rewritten on every start, since
cupsd may replace them.
Reuse the certificate whenever it already covers the required names. Reissuing forces every client that trusted the old certificate to trust a new one, so regeneration is limited to genuinely missing names, and the recorded SAN list is compared as a set (sorted, deduplicated) so the order in which names are discovered does not matter. A transient failure to query Avahi therefore does not churn the certificate. Consequence: names are never removed from an existing certificate; that requires deleting it.
Treat TLS failures as non-fatal, but fix the restart defect properly. If the certificate directory is not writable (for example a read-only bind mount), the entrypoint warns and lets CUPS fall back to its own certificate, because plain IPP printing still works. For startup, the stale-state problem is fixed at its root: D-Bus and Avahi are started based on whether their process is running, and their leftover socket/PID files are removed first. Avahi failing to start is reported as a warning rather than ending the entrypoint, so a discovery problem cannot take the whole print server down.
Risks / Trade-offs
- [Self-signed certificate is rejected by validating clients] → Documented: trust the certificate on macOS/iOS, or keep using plain IPP. The entrypoint cannot influence a client's trust store.
- [Only the common name is known on first start if Avahi is not up yet] → The name is re-checked on every start and the certificate is reissued when a required name is missing, so the next start heals it. Host networking keeps this rare.
- [A name that later disappears stays in the certificate] → Accepted: extra SANs are harmless and churn would invalidate client trust.
- [Destroying
server.sanloses the record of covered names] → Worst case the certificate is reissued once, which is the same as losing the certificate. - [Restarting rewrites
<name>.crt, overwriting a certificate the user placed there by hand] → Accepted; this image owns the certificate directory. - [Host name changes are not picked up until a restart] → Accepted; the certificate is inspected at startup only.
Migration Plan
Deploy by rebuilding the image; no data migration. An existing /etc/cups
volume is repaired on first start: the ServerCertificate/ServerKey
directives are appended to the existing cupsd.conf and the stale
<host name>.crt is replaced. Clients that already added the queue as a
Generic PostScript printer must remove and re-add it, because macOS caches the
driver choice. Rollback is redeploying the previous image; a certificate written
by this version is ignored by it.