packaging: restore the client payload, add manual pages, cover packaging with an install test ai:claude-opus-5 #50
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
mod-nag
mod-nag
mod-nag
mod-nag/ignore
mod-nag/ignore
mod-nag/ignore
nagonag
nagonag/ignore
question
security
wontfix
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
heiko/cert-proxy!50
Loading…
Reference in a new issue
No description provided.
Delete branch "cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Eleven commits, starting from the report in #45 and ending in the things found while verifying the fix. Every packaging claim below was checked against a package built with
gogogo packand installed in a container, not inferred from the config.The reported bug (#45)
cert-proxy-client_1.20.0stopped creating/etc/cert-proxy/,/etc/cert-proxy/hookand/var/lib/cert-proxy/certs. Confirmed: the move to gogogo packaging (eace6f5) dropped the olddebian/tree without carrying over its.install,.dirsandpostinst. Eleven tags are affected,v1.19.1-0.20260524130539throughv1.20.1-0.20260628182040, includingv1.20.0.Fixed in four parts, because it turned out to be four problems:
/etc/cert-proxy/hookis shipped again, now as a conffile so local edits survive upgrades, and executable so pointing-hookat it works without a chmod. The old packaging did neither./var/lib/cert-proxy/certs(0750 root:ssl-cert) and thessl-certgroup come fromtmpfiles.d/sysusers.dsnippets, replacing the hand-writtendpkg-statoverridedance.|| true), so on a host without the helpers the package installed cleanly and did nothing. The client package now depends onsystemd | systemd-standalone-sysusers | systemd-sysusersand the tmpfiles equivalent, so dpkg refuses instead.cert-proxy-client.timer: the generated postinst only restarts on upgrade. A fresh install therefore never renewed anything. Anafter-installfragment presets it, first install only, and only onceclient-ssl.pemexists.Also fixed, found on the way
/usr/lib/systemd/system/cert-proxy-*.default, because thesystemd/cert-proxy-*.*glob also matched.default. Replaced with the dedicatedsystemd-units:key.-connectonly worked with an explicit scheme.url.Parse("host:4433")yields schemehost, so the client requestedhost:4433/v1/.... Shorthands now work.req.URLverbatim under-verbose, and a PKCS12 bundle may carry?pass=, so passwords reached the journal. Redacted.cert-proxy-client manwas intercepted before-help/-version, so-help manprinted a manual page.-certbasesurfaced as amkdirfailure mid-download; it is now checked at startup. The client deliberately still does not create the store: it creates per-domain directories only, and ownership of the store stays with whoever set it up.Manual pages
There were none: the
.podsources were never wired into gogogo packaging, so every.debtripped lintianno-manual-page. Replaced with Markdown + a pinnedgo-md2man, generated throughgo generate ./...into trackedgzip -9pages, and embedded in both binaries behind<binary> man [<section>] [<page>].Four pages after a section audit:
cert-proxy-client(8),cert-proxy-server(8),cert-proxy-clients(5)(theclients/<cn>grammar) andcert-proxy(7)(endpoints, auth model, layout). The old POD had drifted — it documented a-certsflag that never existed and a-servernamedefault that was wrong — so everything was rewritten from the source.Test coverage
test/packaging/(build tagpackaging, sogo test ./...still needs only Go) builds real packages and installs them in a podman container: 14 cases covering the #45 payload, the dependency and its refusal on a systemd-less host, conffile registration, the timer logic, manual pages, clean purge, and the CA payload.make test-packaging;make test-packaging-imagebuilds the systemd-equipped image, which those two cases skip without.It earned its keep immediately by catching a manpage path collision that made the two packages refuse to co-install.
Notes for review
de38366in particular mixes the server password fix with client and generator changes. Happy to rebase into per-concern commits before merge.ssl-certgroup grants traversal but cannot read keys) is not fixed here, only documented accurately.cert-proxy-server.serviceusesWantedBy=network.target, and the CA helper programs live under/etcas conffiles.(authored by ai:claude-opus-5)
Packaging moved to gogogo, so the Debian toolchain no longer drives the Makefiles and nothing but humans does: the CI workflows only run nagonag, demo/demo calls go build directly, and .gogogo.conf builds via its own build.commands. What was left had rotted: * cmd/.makefile.common injected the version with -X go.schlittermann.de/heiko/cert-proxy.git/program.Version, which is the wrong module path (.git suffix) and the wrong package (internal/program), and Version is a func-initialised var reading debug.ReadBuildInfo() -- which -X cannot set at all. The flag was inert; both make- and gogogo-built binaries already report the same build-info version. * distclean ran "rm -rf ${GOPATH}", which wipes the user's whole GOPATH when it is set. * the CA install globbed lib/*, so a local (gitignored) vars.sh was installed along with the tracked files, and everything landed 0755. Replaced by a single top-level Makefile: build into build/ via go build ./cmd/..., install into a prefix, install-ca from an explicit file list with the same modes the .deb uses (0755 scripts, 0644 conf), plus test/update/man/clean. The documented GOOS=windows make install path and every target name in README.md are preserved. The pod2man rules survive as "make man" so the manpage tooling is not lost with the per-command Makefiles.Both binaries shipped no manual page at all -- the .pod sources were never wired into the gogogo packaging path, so every .deb tripped lintian no-manual-page. Replace them with the go-md2man workflow. Section audit (1-8): sections 1, 2, 3, 4 and 6 do not apply. Both commands are administrative, so they are section 8, not 1. Everything under internal/ is unimportable, so there is no section 3 material. Added beyond the two command pages: * cert-proxy-clients(5) -- the line grammar shared by the per-client authorization files and the client's -cnfile: one domain per line, "#" to end of line is a comment, surrounding blanks and CR stripped, empty lines skipped, literal comparison with no wildcards. Also the accepted file name, which is what keeps a common name from escaping the directory. * cert-proxy(7) -- endpoints, which of them are public and why, query parameters, the x-version header, the 401/400/304 behaviour and the on-disk layout of both sides. Canonical sources are man/*.md; man/gen.go renders them through the pinned go-md2man tool and installs deterministic gzip -9 pages, written to a temporary file and renamed so a failed run cannot truncate a tracked page. "go generate ./..." is the entry point. The pages are embedded in man/man.go and exposed as "<binary> man [<section>] [<page>]", resolving arguments the way man(1) does: no argument gives the binary's own page, a digit gives a section's default page, a bare name is searched across sections with the lowest section winning, two arguments are explicit. Piped output is raw roff; on a terminal the page goes to "man -l -", falling back to raw roff with a warning when man(1) is missing. Viewer, PATH lookup and terminal detection are injected, so the tests need no pty and start no pager. Documentation facts were taken from the source, not from the old .pod, which had drifted: it documented -certs (never existed, it is -certbase), -servername defaulting to "cert-proxy" (it defaults to empty, meaning the FQDN connected to), and omitted -shared-hook, -pkcs12-compat and -help. AGENTS.md also described /v1/list as listing all available domains; it returns only the requesting client's own authorized domains. Verified: generation is byte-identical across runs and leaves no diff, gzip -t passes, groff -man -ww is silent, every page was rendered and read in full, go test ./... passes, and a real .deb puts each page at /usr/share/man/man<N>/ with mode 0644 and renders from inside the extracted package. gzip -9 is required: the default level made lintian fail with poor-compression-in-manual-page.A missing -certbase used to surface as mkdir /var/lib/cert-proxy/certs/example.com: no such file or directory from inside the download path: accurate, but it names the per-domain directory rather than the store, and only after the run has begun and some domains may already have failed. Check it once in parseFlags instead, before any work starts: -certbase "/var/lib/cert-proxy/certs" does not exist; the certificate store is created by the package or by the administrator, not by cert-proxy-client The message says whose job it is, because it is deliberately not ours: the client creates the per-domain directories and nothing above them. A path that exists but is not a directory is reported separately. os.Stat, not Lstat, so a symlinked store keeps working. The check sits at the end of parseFlags, so man, -help and -version still work on a host that has no store at all. cert-proxy-client(8) gains a DIAGNOSTICS entry for the message.* -connect accepted only forms carrying a scheme. url.Parse reads "host:4433" as scheme "host" with opaque "4433", so the client then requested "host:4433/v1/..." instead of talking to that host. A bare "host" happened to work, "host:4433" did not. withScheme() supplies the scheme before parsing, so both work, and "//host:4433" too. * The man subcommand was intercepted before -help and -version, so "cert-proxy-client -help man" printed a manual page instead of the usage. It now runs after both, and still before CN validation. manCommand, duplicated in each command, became man.Command. * checkCertbase reported a dangling symlink as "does not exist", which is confusing when the operator can see the symlink. Lstat now distinguishes the two. * The server logged req.URL verbatim under -verbose, and a PKCS12 bundle may be requested with ?pass=..., so passwords reached the journal. redactedURL() replaces the value; the parameter list is a map so more can be added. * man/gen.go globbed "*.md" without anchoring, so running it from the repository root produced AGENTS.gz, GEMINI.gz and README.gz. It now only accepts <topic>.<section>.md and fails loudly otherwise. * TestRoffReportsDecompressionFailure never reached the decompression path: its file was not in the embed set, so it tested ReadFile. The gzip path is now covered by injecting damaged data through a new pagesFS seam, rather than shipping a corrupt page in the binaries. * The comment claiming lowest-section-wins is "the behaviour of man(1)" was wrong; man-db uses its configured SECTION order. The rule stays, the justification is corrected.A review checked the pages introduced on this branch against the source rather than against the retired POD they replaced, and found several claims that were simply untrue. cert-proxy-client(8): * The hook environment was described as "variables of the same names as the positional parameters". The variables are DOMAIN, KEYFILE, CERTFILE, CHAINFILE, FULLCHAINFILE and BUNDLEFILE -- note FULLCHAINFILE, not FULLCHAIN -- and TIMESTAMP is not among them: the timestamp is positional only. Someone reading "$TIMESTAMP" in a hook would have got an empty string. * -passout file: takes the whole file with trailing whitespace removed, not its first line. A two-line file yields a two-line password. * -passout now warns that the password travels as a query parameter. * The certs directory entry no longer implies ssl-cert membership grants access to the keys. It grants traversal; the per-domain directories are 0700 and the keys 0600 under the client's own umask. cert-proxy(7): * 401 was described as one undistinguishable case with the body "unauthorized". There are three distinct bodies, and the one for an unlisted domain echoes the client's common name and the domain back. * x-version is set in the content handler, so responses rejected by authentication or authorization never carry it. * Only an over-long path yields 400; an unknown endpoint yields 404 from the mux. * PKCS12 is stored client-side as bundle.pfx, not under the PEM names. The same false ssl-cert rationale is removed from the tmpfiles snippet, which now records that the client refuses to create the store itself.