This commit is contained in:
1 parent
548ae918ea
commit
50497780c7
7 files changed
+314
-15
No files matched your search
@@ -140,8 +140,18 @@ FQDN, `localhost`, `127.0.0.1` and everything in `CERT_EXTRA_NAMES`:
|
|||||||
The certificate lives in `/etc/cups/ssl` (so that directory must be writable:
|
The certificate lives in `/etc/cups/ssl` (so that directory must be writable:
|
||||||
use a named volume, see the bind-mount caveat under *Notes and limitations*)
|
use a named volume, see the bind-mount caveat under *Notes and limitations*)
|
||||||
and is only regenerated when one of the names it must cover is missing, so a
|
and is only regenerated when one of the names it must cover is missing, so a
|
||||||
restart normally logs `TLS certificate is up to date`. Inspect what the server
|
restart normally logs `TLS certificate is up to date`.
|
||||||
actually presents:
|
|
||||||
|
It is also copied — as `<name>.crt`/`<name>.key` — for every name CUPS may look
|
||||||
|
it up by, because CUPS 2.4 does not choose the certificate by the name the client
|
||||||
|
requested: it reverse-resolves the *local* address the client connected to and
|
||||||
|
loads `<name>.crt` from `/etc/cups/ssl`. A host-networked container resolves its
|
||||||
|
own LAN address to the name Podman writes into `/etc/hosts`
|
||||||
|
(`host.containers.internal`), and IPv6 literals are used as-is, so those names
|
||||||
|
need a copy too. Otherwise CUPS mints a certificate of its own for the address,
|
||||||
|
whose SANs do not cover the advertised `.local` name.
|
||||||
|
|
||||||
|
Inspect what the server actually presents:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
podman cp printserver:/etc/cups/ssl/server.crt .
|
podman cp printserver:/etc/cups/ssl/server.crt .
|
||||||
|
|||||||
+63
-13
@@ -90,7 +90,8 @@ fi
|
|||||||
# A mismatch makes the secure add fail, and macOS then silently falls back to
|
# A mismatch makes the secure add fail, and macOS then silently falls back to
|
||||||
# its "Generic PostScript Printer" driver (no duplex, no colour, no trays).
|
# its "Generic PostScript Printer" driver (no duplex, no colour, no trays).
|
||||||
# Install our own certificate with SANs covering every name this server answers
|
# Install our own certificate with SANs covering every name this server answers
|
||||||
# to, and point cupsd at it (see ensure_cupsd_cert_directives below).
|
# to, and make it reachable under every name cupsd looks the certificate up by
|
||||||
|
# (see install_default_cert_copy below).
|
||||||
CERT_DIR="${CERT_DIR:-/etc/cups/ssl}"
|
CERT_DIR="${CERT_DIR:-/etc/cups/ssl}"
|
||||||
CERT_CRT="${CERT_DIR}/server.crt"
|
CERT_CRT="${CERT_DIR}/server.crt"
|
||||||
CERT_KEY="${CERT_DIR}/server.key"
|
CERT_KEY="${CERT_DIR}/server.key"
|
||||||
@@ -139,17 +140,63 @@ cert_common_name() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install a copy of the certificate where cupsd looks by default, i.e.
|
# Local addresses of this host (IPv4 and IPv6), excluding loopback - cupsd
|
||||||
# <name>.crt/.key in the certificate directory, for every name this server
|
# answers loopback connections with the default common name instead.
|
||||||
# could be known as (host name and Avahi FQDN). CUPS generates its own
|
local_addresses() {
|
||||||
# certificate there and, at least in CUPS 2.4, keeps serving that file even when
|
addrs="$(hostname -I 2>/dev/null || true)"
|
||||||
# ServerCertificate points elsewhere - the copies make sure the SANs are covered
|
if [ -z "${addrs}" ] && command -v ip >/dev/null 2>&1; then
|
||||||
# either way. Re-installed on every start because cupsd may have replaced them.
|
addrs="$(ip -o addr show 2>/dev/null | awk '{ print $4 }' | cut -d/ -f1)"
|
||||||
|
fi
|
||||||
|
printf '%s\n' ${addrs} | grep -vx -e '' -e '127\.0\.0\.1' -e '::1' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# The file name cupsd derives from a name. http_make_path() in
|
||||||
|
# cups/tls-openssl.c keeps only alphanumerics, '-' and '.', replacing every
|
||||||
|
# other character with '_' (an IPv6 literal thus becomes e.g.
|
||||||
|
# fd26_182_833e_0_be24_11ff_fe21_341c).
|
||||||
|
cert_cups_name() {
|
||||||
|
printf '%s' "$1" | sed 's/[^A-Za-z0-9.-]/_/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every name cupsd may look the server certificate up by. On a server TLS
|
||||||
|
# handshake CUPS 2.4 does *not* use the requested (SNI) name: it takes the
|
||||||
|
# local socket address the client connected to, reverse-resolves it and loads
|
||||||
|
# <name>.crt from the keychain directory (/etc/cups/ssl). When the lookup
|
||||||
|
# yields a numeric address cupsd keeps it unless it starts with a digit - which
|
||||||
|
# it does not for IPv6 literals. See _httpTLSStart() in cups/tls-openssl.c.
|
||||||
|
# Any name we miss makes cupsd mint a certificate of its own for it, and that
|
||||||
|
# certificate does not cover the name the client actually asked for.
|
||||||
|
cert_lookup_names() {
|
||||||
|
cert_san_list | sed -n 's/^DNS://p' | while IFS= read -r name; do
|
||||||
|
[ -n "${name}" ] || continue
|
||||||
|
printf '%s\n' "$(cert_cups_name "${name}")"
|
||||||
|
done
|
||||||
|
local_addresses | while IFS= read -r addr; do
|
||||||
|
[ -n "${addr}" ] || continue
|
||||||
|
# httpAddrString() spells an IPv6 zone as "+zone" (URI form)...
|
||||||
|
printf '%s\n' "$(cert_cups_name "$(printf '%s' "${addr}" | sed 's/%/+/')")"
|
||||||
|
bare="$(printf '%s' "${addr}" | sed 's/%.*//')"
|
||||||
|
name="$(getent hosts "${bare}" 2>/dev/null | awk 'NR == 1 { print $2 }')"
|
||||||
|
if [ -n "${name}" ]; then
|
||||||
|
printf '%s\n' "$(cert_cups_name "${name}")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install a copy of the certificate under every name cupsd looks it up by,
|
||||||
|
# i.e. <name>.crt/.key in the certificate directory. CUPS 2.4 ignores the
|
||||||
|
# ServerCertificate directive (see ensure_cupsd_cert_directives) and resolves
|
||||||
|
# the connection's local address instead, so without these copies it serves a
|
||||||
|
# certificate it generated itself whose SANs do not cover the advertised name.
|
||||||
|
# For example a host-networked container resolves its own LAN address to the
|
||||||
|
# name podman wrote into /etc/hosts ("host.containers.internal"). Re-installed
|
||||||
|
# on every start because cupsd may have written its own certificate since.
|
||||||
install_default_cert_copy() {
|
install_default_cert_copy() {
|
||||||
[ -s "${CERT_CRT}" ] || return 0
|
[ -s "${CERT_CRT}" ] || return 0
|
||||||
for name in "${CERT_HOSTNAME}" "${CERT_FQDN}"; do
|
cert_lookup_names | sort -u | while IFS= read -r name; do
|
||||||
|
[ -n "${name}" ] || continue
|
||||||
case "${name}" in
|
case "${name}" in
|
||||||
"" | */*) continue ;;
|
*/*) continue ;;
|
||||||
esac
|
esac
|
||||||
cp -f "${CERT_CRT}" "${CERT_DIR}/${name}.crt" 2>/dev/null || continue
|
cp -f "${CERT_CRT}" "${CERT_DIR}/${name}.crt" 2>/dev/null || continue
|
||||||
cp -f "${CERT_KEY}" "${CERT_DIR}/${name}.key" 2>/dev/null || continue
|
cp -f "${CERT_KEY}" "${CERT_DIR}/${name}.key" 2>/dev/null || continue
|
||||||
@@ -214,10 +261,13 @@ ensure_certificate() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Point cupsd at our certificate. Only done when the files exist: cupsd refuses
|
# Point cupsd at our certificate. CUPS 2.4 has dropped ServerCertificate/
|
||||||
# to start when ServerCertificate cannot be read. Skipped when the directives
|
# ServerKey (cups-files.conf logs an INFO and ignores them); the certificate
|
||||||
# are already present, so this also repairs a cupsd.conf previously rewritten by
|
# lookup there goes through the keychain directory, which install_default_cert_copy()
|
||||||
# cupsctl (which drops directives it does not manage).
|
# populates. The directives are kept for older CUPS, and only added when the
|
||||||
|
# files exist: cupsd refuses to start when ServerCertificate cannot be read.
|
||||||
|
# Skipped when already present, so this also repairs a cupsd.conf previously
|
||||||
|
# rewritten by cupsctl (which drops directives it does not manage).
|
||||||
ensure_cupsd_cert_directives() {
|
ensure_cupsd_cert_directives() {
|
||||||
conf=/etc/cups/cupsd.conf
|
conf=/etc/cups/cupsd.conf
|
||||||
[ -f "${conf}" ] || return 0
|
[ -f "${conf}" ] || return 0
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-10-19
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# Design
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
See `proposal.md` — Why.
|
||||||
|
|
||||||
|
The previous change assumed cupsd served the file named by `ServerCertificate`,
|
||||||
|
and only wrote `<host name>.crt`/`<Avahi FQDN>.crt` on top of that. Observing a
|
||||||
|
live deployment (CUPS 2.4.2 on Debian bookworm, host networking) and reading the
|
||||||
|
sources shows a different mechanism:
|
||||||
|
|
||||||
|
- CUPS 2.4.2 reads `ServerKeychain` (a directory, default `ssl` →
|
||||||
|
`/etc/cups/ssl`), `ServerName` (default common name) and `CreateSelfSignedCerts`
|
||||||
|
(`scheduler/conf.c`). `ServerCertificate`/`ServerKey` are dead: in
|
||||||
|
`cupsd.conf` CUPS logs "Please move … to the cups-files.conf file", and in
|
||||||
|
`cups-files.conf` it logs "is no longer supported" and ignores the value.
|
||||||
|
- In `_httpTLSStart()` (`cups/tls-openssl.c`) a server connection takes the
|
||||||
|
**local** socket address (`getsockname`), not the SNI/host name. Loopback
|
||||||
|
yields an empty name, so the lookup falls back to the default common name. Any
|
||||||
|
other address is reverse-resolved with `httpAddrLookup()`; if that fails the
|
||||||
|
numeric string is used. A result that starts with a digit or `[` is discarded
|
||||||
|
(numeric IPv4), but an IPv6 literal such as `fd26:…` is kept.
|
||||||
|
- The certificate file is built with `http_make_path()`: it keeps only
|
||||||
|
alphanumerics, `-` and `.`, turning everything else into `_`. An IPv6 literal
|
||||||
|
`fd26:182:833e:0:be24:11ff:fe21:341c` therefore becomes the file name
|
||||||
|
`fd26_182_833e_0_be24_11ff_fe21_341c.crt`.
|
||||||
|
- With host networking, Podman writes the host's address into the container's
|
||||||
|
`/etc/hosts` as `host.containers.internal`. A client connecting to the LAN
|
||||||
|
address therefore makes cupsd look for `host.containers.internal.crt`.
|
||||||
|
|
||||||
|
Observed, same cupsd and port, different certificate depending on the address
|
||||||
|
the client connected to:
|
||||||
|
|
||||||
|
| Connected to | cupsd lookup name | Served certificate |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `127.0.0.1` | default common name (`podp.internal.jacs.at`) | ours (copy existed) |
|
||||||
|
| `192.168.100.20` | `host.containers.internal` | cupsd's own, `CN=host.containers.internal` |
|
||||||
|
| `[fd26:…]:631` | IPv6 literal | cupsd's own, `CN=fd26:…` |
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- cupsd presents our certificate (with the advertised name among its SANs) no
|
||||||
|
matter which of the server's addresses the client connected to.
|
||||||
|
- The certificate content and the reuse/regeneration behaviour stay as they are,
|
||||||
|
so already-trusted clients keep working across an image upgrade.
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Adding the server's addresses to the certificate's IP SANs. Clients that use
|
||||||
|
the advertised `.local` name validate that name; IP-based URLs remain covered
|
||||||
|
by `CERT_EXTRA_NAMES`.
|
||||||
|
- Removing `ServerCertificate`/`ServerKey` entirely; they are kept for CUPS
|
||||||
|
versions before 2.4.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
**Enumerate the names cupsd can look up instead of guessing two.**
|
||||||
|
`install_default_cert_copy()` now writes a copy for every DNS name in the SAN
|
||||||
|
list *and* for each local address (the address literal with `%zone` written as
|
||||||
|
`+zone` and mangled the way `http_make_path()` does, plus the name the address
|
||||||
|
reverse-resolves to via `getent hosts`). This is derived from the running
|
||||||
|
system, so it also covers names a future Podman version writes into `/etc/hosts`
|
||||||
|
and any IPv6 literal, without hard-coding `host.containers.internal`.
|
||||||
|
|
||||||
|
**Reuse the existing helpers and regeneration logic.**
|
||||||
|
The SAN list already contains `host.containers.internal`, `localhost` and the
|
||||||
|
advertised names, so enumerating DNS SANs covers the name-based lookups; the
|
||||||
|
local-address enumeration adds the literal/IPv6 cases. Nothing in
|
||||||
|
`ensure_certificate()` changes, so the certificate is not reissued on upgrade.
|
||||||
|
|
||||||
|
**Locate local addresses with `hostname -I`, fall back to `ip -o addr`.**
|
||||||
|
Debian's `hostname` (present in `bookworm-slim`) supports `-I` and prints all
|
||||||
|
IPv4 and IPv6 addresses; `ip` from `iproute2` is not installed, so it is only a
|
||||||
|
fallback. Loopback is excluded because cupsd handles it with the default common
|
||||||
|
name.
|
||||||
|
|
||||||
|
**Keep the copies' permissions consistent with the certificate.**
|
||||||
|
Each copy gets the same mode/owner (`0644` cert, `0640` key, `root:lp`) as
|
||||||
|
`server.crt`/`server.key`, so cupsd can read them.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
- [A local address changes while the container runs] → The names are recomputed
|
||||||
|
on every start; a changed address is covered after the next restart. Addresses
|
||||||
|
are stable with host networking.
|
||||||
|
- [Link-local IPv6 addresses whose scope `hostname -I` does not print] → Their
|
||||||
|
literal copy may differ from what cupsd computes, so a client connecting over
|
||||||
|
such an address could still get cupsd's certificate. Accepted; clients use the
|
||||||
|
advertised `.local` name, which typically resolves to a global/ULA address.
|
||||||
|
- [Writing a folder full of certificate copies] → A handful of small files in
|
||||||
|
`/etc/cups/ssl`; rewrite is idempotent and cheap.
|
||||||
|
- [Comma-free, space-free names only] → `http_make_path()` collapses any other
|
||||||
|
character to `_`, so the name we write matches what cupsd computes by
|
||||||
|
construction.
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
Rebuild and redeploy the image; no volume reset. On first start the missing
|
||||||
|
`<name>.crt`/`.key` copies (notably `host.containers.internal`) appear and cupsd
|
||||||
|
serves them from then on. The certificate itself is unchanged, so no client
|
||||||
|
needs to re-trust it. Rollback is redeploying the previous image; the extra
|
||||||
|
copies are simply ignored.
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Proposal
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The previous change (`advertise-valid-ipps-certificate`) made the container
|
||||||
|
*generate* a certificate with the right subject alternative names, but clients
|
||||||
|
still get a certificate they do not trust. From the workstation,
|
||||||
|
`curl https://podp.local:631/` fails with `SSL certificate problem: unable to get
|
||||||
|
local issuer certificate`, and the certificate the server presents has
|
||||||
|
`CN=host.containers.internal` — not the certificate the entrypoint installed.
|
||||||
|
|
||||||
|
The reasoning in the previous change ("CUPS 2.4 keeps serving
|
||||||
|
`ServerCertificate`, so also write `<host name>.crt`") was wrong. Reading the
|
||||||
|
CUPS 2.4.2 sources:
|
||||||
|
|
||||||
|
- `ServerCertificate`/`ServerKey` are no longer supported. In `cupsd.conf` they
|
||||||
|
log "Please move … to the cups-files.conf file"; in `cups-files.conf` they log
|
||||||
|
"is no longer supported" and are ignored (`scheduler/conf.c`). The certificate
|
||||||
|
directory is `ServerKeychain` (default `ssl` → `/etc/cups/ssl`).
|
||||||
|
- On a server TLS handshake, `_httpTLSStart()` (`cups/tls-openssl.c`) ignores
|
||||||
|
the requested (SNI) name. It takes the **local socket address** the client
|
||||||
|
connected to, reverse-resolves it, and loads `<name>.crt` from the keychain
|
||||||
|
directory. A name that is not alphanumeric, `-` or `.` is mangled to `_`
|
||||||
|
(`http_make_path()`), and a numeric IPv6 result is used verbatim because CUPS
|
||||||
|
only rejects numeric names that start with a digit.
|
||||||
|
|
||||||
|
So with host networking the client's connection to the LAN address
|
||||||
|
`192.168.100.20` makes cupsd look for `/etc/cups/ssl/host.containers.internal.crt`
|
||||||
|
(the name Podman writes into `/etc/hosts` for the host address). That file did
|
||||||
|
not exist — only `<host name>.crt` and `<Avahi FQDN>.crt` were written — so cupsd
|
||||||
|
minted its own, which does not cover `podp.local`. IPv6 connections get a
|
||||||
|
certificate named after the IPv6 literal for the same reason. The previous
|
||||||
|
change's verification only checked `localhost`, where cupsd falls back to the
|
||||||
|
default common name and happened to find the copy.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- Derive the certificate copies from what cupsd actually looks up: write
|
||||||
|
`<name>.crt`/`<name>.key` for every DNS name in the SAN list **and** for every
|
||||||
|
local address (the address literal, mangled the way `http_make_path()` does,
|
||||||
|
plus whatever name the address reverse-resolves to). This covers the Podman
|
||||||
|
`host.containers.internal` name for the LAN address and the IPv6 literals.
|
||||||
|
- Keep the SAN list and the regeneration logic unchanged: the certificate
|
||||||
|
content was already correct and reissuing it is not desirable.
|
||||||
|
- Correct the comments, the README and the spec so they no longer claim
|
||||||
|
`ServerCertificate` is what makes cupsd serve our certificate. The directive
|
||||||
|
is kept only for CUPS versions older than 2.4.
|
||||||
|
|
||||||
|
Non-goals: generating a publicly trusted certificate or a private CA, adding the
|
||||||
|
local addresses to the certificate's IP SANs (that needs `CERT_EXTRA_NAMES` and
|
||||||
|
is out of scope), and changing the plain `_ipp` service.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
<!-- None: this hardens the certificate requirement added by the previous change. -->
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `printserver/cups`: the requirement that the secure AirPrint certificate
|
||||||
|
matches the advertised name gains a scenario for serving that certificate for
|
||||||
|
the address a client connected to, not only for the name it asked for.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- `entrypoint.sh`: `install_default_cert_copy` now enumerates the local
|
||||||
|
addresses and cupsd's filename mangling; `ensure_cupsd_cert_directives`
|
||||||
|
comment corrected.
|
||||||
|
- `README.md`: certificate section explains how CUPS selects the certificate.
|
||||||
|
- `openspec/specs/printserver/cups/spec.md`: one scenario added to the
|
||||||
|
certificate requirement.
|
||||||
|
- Clients: fully validating clients (macOS/iOS AirPrint, `curl`) complete the
|
||||||
|
secure connection once they trust the certificate; plain IPP is unaffected.
|
||||||
|
- Existing deployments: an existing `/etc/cups` volume is fixed on the next
|
||||||
|
start; the certificate itself is not reissued, so clients that already trust
|
||||||
|
it keep trusting it.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Spec Delta
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Secure AirPrint certificate matches the advertised name
|
||||||
|
The container SHALL serve the secure `_ipps` (TLS) service with a certificate
|
||||||
|
whose subject alternative names cover the names clients use to reach it, so
|
||||||
|
clients that validate the name complete the secure connection instead of falling
|
||||||
|
back to a generic driver.
|
||||||
|
|
||||||
|
#### Scenario: Certificate is created on first start
|
||||||
|
- **WHEN** the container starts and no certificate is present in the certificate directory
|
||||||
|
- **THEN** a self-signed certificate and key are generated whose subject alternative names cover the container host name, its `<host name>.local` form, the name Avahi advertises, `localhost`, `localhost.localdomain`, `host.containers.internal` and `127.0.0.1`
|
||||||
|
|
||||||
|
#### Scenario: Additional names are accepted
|
||||||
|
- **WHEN** the container is started with `CERT_EXTRA_NAMES` set to a comma-separated list of DNS names and/or IP addresses
|
||||||
|
- **THEN** every listed name is included as a subject alternative name of the generated certificate
|
||||||
|
|
||||||
|
#### Scenario: Validity is configurable
|
||||||
|
- **WHEN** the container is started with `CERT_DAYS`
|
||||||
|
- **THEN** the generated certificate is valid for that number of days
|
||||||
|
|
||||||
|
#### Scenario: An existing certificate is reused
|
||||||
|
- **WHEN** the container starts and the existing certificate already covers every required name
|
||||||
|
- **THEN** it is kept instead of reissued, so clients that trusted it do not have to trust a new certificate
|
||||||
|
|
||||||
|
#### Scenario: Missing names trigger reissue
|
||||||
|
- **WHEN** the container starts and a required name — for example a newly added `CERT_EXTRA_NAMES` entry — is not covered by the existing certificate
|
||||||
|
- **THEN** a certificate covering it is generated
|
||||||
|
|
||||||
|
#### Scenario: CUPS presents the certificate
|
||||||
|
- **WHEN** a client connects over `_ipps` to the name Avahi advertises
|
||||||
|
- **THEN** the certificate CUPS presents contains that name in its subject alternative names
|
||||||
|
|
||||||
|
#### Scenario: Certificate is served for the connecting address
|
||||||
|
- **WHEN** a client connects over `_ipps` to one of the host's addresses — for example its LAN address or an IPv6 address — rather than to the advertised name
|
||||||
|
- **THEN** CUPS presents the container's certificate, so the names in its subject alternative names (including the advertised name) still apply
|
||||||
|
|
||||||
|
#### Scenario: A validating client must trust the certificate
|
||||||
|
- **WHEN** a client validates the certificate chain
|
||||||
|
- **THEN** the connection is rejected until the self-signed certificate is trusted on the client, while the plain `_ipp` service keeps working
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## 1. Serve the certificate for the connecting address
|
||||||
|
|
||||||
|
- [x] 1.1 Add a helper that lists the container's local addresses (IPv4 and IPv6, loopback excluded) using `hostname -I`, falling back to `ip -o addr`; verify the logic with a stubbed `hostname`/`getent` (IPv4 only, IPv6 only, address containing a `%zone`, and no addresses at all)
|
||||||
|
- [x] 1.2 Add a helper that mangles a name the way `http_make_path()` in CUPS does (keep `[A-Za-z0-9.-]`, replace the rest with `_`) and derive the lookup names: every DNS SAN, each address literal with `%zone` spelled `+zone`, and each address's reverse-resolved name (`getent hosts`)
|
||||||
|
- [x] 1.3 Rewrite `install_default_cert_copy()` to install `<name>.crt`/`<name>.key` for all those names, preserving the `0644`/`0640` `root:lp` permissions; verify with a stubbed environment that the copies include `host.containers.internal.crt`, `podp.local.crt` and `<ipv6-literal>.crt`
|
||||||
|
- [ ] 1.4 After rebuilding and redeploying on the podman host, verify from another host that `curl https://podp.local:631/` succeeds and `openssl s_client -connect <LAN address>:631 -servername podp.local` shows our certificate (SANs contain the advertised name), not `CN=host.containers.internal`
|
||||||
|
|
||||||
|
## 2. Correct the reasoning and documentation
|
||||||
|
|
||||||
|
- [x] 2.1 Correct the certificate comments in `entrypoint.sh`: CUPS 2.4 ignores `ServerCertificate`/`ServerKey`, selects the certificate by the connection's local address and mangles the name with `http_make_path()`
|
||||||
|
- [x] 2.2 Update the README certificate section to describe how CUPS selects the certificate and why the per-name copies exist
|
||||||
|
- [x] 2.3 Add a scenario to the `printserver/cups` spec covering the certificate served for the address a client connected to
|
||||||
|
|
||||||
|
## Workflow follow-up
|
||||||
|
|
||||||
|
- Archive this change (`/opsx-archive`) so the delta merges into `openspec/specs/printserver/cups/spec.md`.
|
||||||
|
- Commit the implementation together with the archived change using `jj`.
|
||||||
Reference in new issue
Block a user