v0.4.1 — --version flag, CentOS 7 docs, Node.js mTLS example
Patch release. Closes one operator-facing UX gap (pamsignal --version now works — the man page synopsis and the package post-install smoke checks have always claimed this flag existed, but it didn't), documents the rationale for one cutoff that came up in operator conversations (CentOS / RHEL 7 unsupportability + migration paths), aligns the APT install snippet with the FHS keyring-path convention (#14), and ships an HTTPS + mTLS variant of the bundled Node.js webhook receiver to match what the threat-model docs claimed it could do (#12). No daemon behaviour changes; existing configs work unchanged. Internal: integration test suite rewritten to exercise the parse → track → format pipeline on real journal log lines, formatter coverage in test_notify.c expanded 4 → 27 cases, install-test default matrix narrowed to the build-matching Tier 1 pair. ASAN + UBSAN clean across all five test suites.
Documentation
-
docs/distros.md: CentOS / RHEL 7 unsupportability fully documented. The Tier 3 table row previously said only "Won't compile; many hardening directives ignored" — operators asking whether the cutoff could be relaxed for their CentOS 7 fleet had no specific answer to point at. Rewrote the row to cite the three independent blockers (stock kernel 3.10 lacks thememfd_createsyscall added in Linux 3.17; glibc 2.17 lacks the wrapper added in 2.27; CentOS 7 itself reached EOL on 2024-06-30 — running a security daemon on an OS that no longer receives upstream security updates is counter-productive) and added a "Why CentOS / RHEL 7 isn't supportable" subsection that spells out exactly which part of the threat model breaks (attack #3, alert-credential exposure via/proc/<pid>/cmdlinewhen memfd-backed curl config isn't available) and offers three concrete migration paths: in-place migration to AlmaLinux 9 / Rocky 9 / RHEL 9 via the officialconvert2rhel/migrate2rocky.sh/almalinux-deploy.shscripts (recommended), container deployment on the existing host with a newer-glibc image (works only ifuname -rshows a backport ≥ 3.17, not stock 3.10), or sidestepping pamsignal entirely withauditd+ SIEM correlation. Linked from the Tier 3 table so the migration path is one click away from the "is my host supported" lookup.
Features
-
pamsignal --version/-Vflag. The man page (pamsignal.8.in) and the packaging smoke check intest_install_docker.shboth assumed this flag existed, butsrc/main.c::parse_argsonly ever handled--foregroundand--config— passing--versionfell through into the daemon's normal startup, hit the non-root invariant atmain.cline 77, and exited 1 with the "should not run as root" message under any package-postinstall context (dpkg, rpm, containerRUN). Added a--version/-Vbranch that prints the meson-injectedPAMSIGNAL_VERSIONand exits 0 before any privilege checks, sopamsignal --versionworks from any context including root. The version string flows frommeson.build(-DPAMSIGNAL_VERSION="<meson.project_version()>") so it stays in sync with the man page's.THline and the.deb/.rpmpackage metadata. Synopsis line added topamsignal.8.in. -
test_install_docker.shdefault matrix narrowed to Tier 1. Without arguments the script previously tested all seven distros (Ubuntu 24.04 / 22.04, Debian 12, Fedora 40, CentOS Stream 9, AlmaLinux 9, Rocky Linux 9), which produced false-negative[FAIL]results: the bundled.debpinslibc6 (>= 2.38)to ubuntu:24.04's glibc, and the.rpmpins to fedora:40's glibc 2.39+, so older targetsapt/dnf-refuse the install correctly. That's the documented Tier 1 vs Tier 2 distinction indocs/distros.md— Tier 2 needs a per-distroseries build pipeline that doesn't ship yet. Default invocation now runs onlyubuntu:24.04(the.debbuild env) andfedora:40(the.rpmbuild env). The full matrix remains opt-in via the existing distro-name positional args (./test_install_docker.sh <deb> <rpm> ubuntu debian fedora centos almalinux rockylinux), so anyone exercising a same-distroseries-build pipeline keeps the wider check available. Usage text updated to explain the rationale.
Fixed
-
APT signing key path moved to
/etc/apt/keyrings(#14). Reported by @hongquan. Our install snippets directed users to drop the repository signing key at/usr/share/keyrings/pamsignal.gpg, which is reserved for keys shipped by official distribution packages (debian-archive-keyring,ubuntu-keyring, etc.) — administrator-installed keys belong in/etc/apt/keyrings. Thesources.list(5)man page (Debian 12+ / Ubuntu 22.04+) makes this explicit: "The recommended locations for keyrings are/usr/share/keyringsfor keyrings managed by packages, and/etc/apt/keyringsfor keyrings managed by the system operator." Apt accepts either path in thesigned-by=option — the distinction is FHS-compliance and convention, not function — so this is a documentation fix only and existing installs keep working without intervention. Updated: the install snippet inREADME.md, theaptblock indocs/deployment.md, the auto-rendered install snippet on the gh-pages landing page (in.github/workflows/release-packages.yml, takes effect on the next release-packages run), and a workflow comment in.github/workflows/bootstrap-signing-key.ymlfor consistency. Each snippet now prependssudo install -d -m 0755 /etc/apt/keyringssince the directory isn't pre-created on every base image (older containers, some minimal Ubuntu/Debian installs); the command is idempotent and safe to re-run.Migrating an existing install (optional). If you installed pamsignal before this fix and want to align with the FHS convention, three idempotent commands handle the move:
sudo install -d -m 0755 /etc/apt/keyrings sudo mv /usr/share/keyrings/pamsignal.gpg /etc/apt/keyrings/pamsignal.gpg sudo sed -i 's|/usr/share/keyrings/pamsignal.gpg|/etc/apt/keyrings/pamsignal.gpg|' \ /etc/apt/sources.list.d/pamsignal.list sudo apt updateThe final
apt updateis for confirmation only — noNO_PUBKEYerror in its output means the move worked and apt still trusts the repo. You do not need to do this. Apt resolves the absolute path insigned-by=directly, so the old/usr/share/keyrings/pamsignal.gpglayout will continue to work through future package upgrades and key rotations without issue. Migrate only if you prefer your system to follow the documented convention.
Examples
- Node.js webhook example: HTTPS + mTLS server variant (#12). The bundled
examples/nodejs-webhook/reference receiver previously listened only on plain HTTP viaapp.listen(); the README's "Mutual TLS (advanced)" section (added with #8) showed anhttps.createServer({ requestCert: true, rejectUnauthorized: true }, app)snippet but the actual code that would do that didn't ship. This change closes the docs-vs-reality gap.src/server.tsnow branches on env vars: whenTLS_KEY_PATHandTLS_CERT_PATHare set, it constructs an HTTPS server (with optionalTLS_CLIENT_CA_PATHfor trust-store override andTLS_REQUIRE_CLIENT_CERT=truefor full mTLS); otherwise the existing plain-HTTP path is unchanged..env.exampledocuments the four new env vars under a comment block that names the correspondingwebhook_client_cert/webhook_client_keykeys on the pamsignal side.scripts/gen-test-certs.shproduces a CA + server cert + client cert under./certs/for local end-to-end testing — it builds an internal CA, signs server.crt withsubjectAltName=DNS:localhost,IP:127.0.0.1(so Node's HTTPS client accepts it), signs a client.crt with CN=pamsignal-test-client, and tightens private-key files to mode 0600 to match what pamsignal will accept. The README's "Mutual TLS (advanced)" subsection is rewritten as a runnable end-to-end demo (cert script + env vars + matchingpamsignal.confblock) rather than a conceptual snippet, and explicitly distinguishes the demo cert pipeline from production cert managers (cert-manager, certbot, systemd-creds).tests/mtls.test.tsadds three Jest cases that spin up the full HTTPS server with the same construction pattern, generate certs at setup-time via openssl spawn, and verify: a request with a valid client cert + Bearer succeeds (200), a request without a client cert is rejected at the TLS layer before reaching Express, and the Bearer middleware still 401s on a successful handshake when the token doesn't match. Total Jest cases 7 → 10..gitignoreaddscerts/,package-lock.json,yarn.lockso locally-generated test certs and accidentally-installed npm/yarn lockfiles don't pollute the pnpm-managed repo. No change to pamsignal itself — pamsignal v0.4.0 already speaks mTLS correctly via #8.