Skip to content

v0.4.1 — --version flag, CentOS 7 docs, Node.js mTLS example

Choose a tag to compare

@anhtuank7c anhtuank7c released this 12 May 04:47
· 41 commits to main since this release

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 the memfd_create syscall 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>/cmdline when 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 official convert2rhel / migrate2rocky.sh / almalinux-deploy.sh scripts (recommended), container deployment on the existing host with a newer-glibc image (works only if uname -r shows a backport ≥ 3.17, not stock 3.10), or sidestepping pamsignal entirely with auditd + 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 / -V flag. The man page (pamsignal.8.in) and the packaging smoke check in test_install_docker.sh both assumed this flag existed, but src/main.c::parse_args only ever handled --foreground and --config — passing --version fell through into the daemon's normal startup, hit the non-root invariant at main.c line 77, and exited 1 with the "should not run as root" message under any package-postinstall context (dpkg, rpm, container RUN). Added a --version/-V branch that prints the meson-injected PAMSIGNAL_VERSION and exits 0 before any privilege checks, so pamsignal --version works from any context including root. The version string flows from meson.build (-DPAMSIGNAL_VERSION="<meson.project_version()>") so it stays in sync with the man page's .TH line and the .deb / .rpm package metadata. Synopsis line added to pamsignal.8.in.

  • test_install_docker.sh default 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 .deb pins libc6 (>= 2.38) to ubuntu:24.04's glibc, and the .rpm pins to fedora:40's glibc 2.39+, so older targets apt/dnf-refuse the install correctly. That's the documented Tier 1 vs Tier 2 distinction in docs/distros.md — Tier 2 needs a per-distroseries build pipeline that doesn't ship yet. Default invocation now runs only ubuntu:24.04 (the .deb build env) and fedora:40 (the .rpm build 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. The sources.list(5) man page (Debian 12+ / Ubuntu 22.04+) makes this explicit: "The recommended locations for keyrings are /usr/share/keyrings for keyrings managed by packages, and /etc/apt/keyrings for keyrings managed by the system operator." Apt accepts either path in the signed-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 in README.md, the apt block in docs/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.yml for consistency. Each snippet now prepends sudo install -d -m 0755 /etc/apt/keyrings since 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 update

    The final apt update is for confirmation only — no NO_PUBKEY error 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 in signed-by= directly, so the old /usr/share/keyrings/pamsignal.gpg layout 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 via app.listen(); the README's "Mutual TLS (advanced)" section (added with #8) showed an https.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.ts now branches on env vars: when TLS_KEY_PATH and TLS_CERT_PATH are set, it constructs an HTTPS server (with optional TLS_CLIENT_CA_PATH for trust-store override and TLS_REQUIRE_CLIENT_CERT=true for full mTLS); otherwise the existing plain-HTTP path is unchanged. .env.example documents the four new env vars under a comment block that names the corresponding webhook_client_cert / webhook_client_key keys on the pamsignal side. scripts/gen-test-certs.sh produces a CA + server cert + client cert under ./certs/ for local end-to-end testing — it builds an internal CA, signs server.crt with subjectAltName=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 + matching pamsignal.conf block) rather than a conceptual snippet, and explicitly distinguishes the demo cert pipeline from production cert managers (cert-manager, certbot, systemd-creds). tests/mtls.test.ts adds 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. .gitignore adds certs/, package-lock.json, yarn.lock so 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.