C-side: * New mode=recovery argv branch in pam_sm_authenticate. Reads the two-line /var/lib/authforge/recovery/<user> file, argon2_verify against PAM_AUTHTOK, on match unlinks the file (one-shot) and writes pending(re_enroll=true). Always returns PAM_IGNORE on failure paths so a missing/wrong code never blocks normal auth. * Makefile links -largon2 alongside -lpam. Daemon-side: * policy_apply::render_profile renders the recovery line first in the auth stack with [success=done default=ignore] — successful recovery short- circuits the rest, missing/wrong code falls through. * New policy_apply test asserts the recovery line precedes the default backstop. Doc: * pam/TESTING.md adds libargon2-dev to the build prereqs and a new Smoke test 4 walking through the manual recovery-code flow. C compile gate (make -C pam) requires libargon2-dev — flagged as a deferred verification step until a host with the dev package is available. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
94 lines
3.3 KiB
Markdown
94 lines
3.3 KiB
Markdown
# Testing pam_authforge_pending.so
|
|
|
|
This module gates first-login enrollment. Its full behavior is impossible to
|
|
exercise from a unit test because PAM modules run inside a real PAM stack that
|
|
needs root to install. The recipe below runs in a VM or container with
|
|
`libpam0g-dev` and `pamtester` installed.
|
|
|
|
## Build + install
|
|
|
|
```bash
|
|
sudo apt-get install -y libpam0g-dev libargon2-dev pamtester
|
|
sudo make -C pam install
|
|
sudo install -D -m 0644 pam/test/authforge.pamd /etc/pam.d/authforge
|
|
```
|
|
|
|
`libargon2-dev` is required for the recovery-mode verification path (Phase 12).
|
|
|
|
`make install` drops the `.so` into
|
|
`/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/security/`.
|
|
|
|
## Smoke test 1: pending flag present → denied
|
|
|
|
```bash
|
|
sudo mkdir -p /var/lib/authforge/pending
|
|
sudo touch /var/lib/authforge/pending/$USER
|
|
pamtester -v authforge "$USER" authenticate
|
|
# Expect: "Account setup incomplete..." printed and a non-zero exit code.
|
|
```
|
|
|
|
## Smoke test 2: pending flag cleared → falls through
|
|
|
|
```bash
|
|
sudo rm /var/lib/authforge/pending/$USER
|
|
pamtester -v authforge "$USER" authenticate
|
|
# Expect: success (the test PAM stack file falls through to pam_unix after
|
|
# our PAM_IGNORE; pamtester will prompt for the user's password).
|
|
```
|
|
|
|
## Smoke test 3: path-traversal regression
|
|
|
|
These usernames must all be rejected with a logged error and `PAM_AUTH_ERR`:
|
|
|
|
```bash
|
|
for u in "../etc" "a/b" "" "." ".." "x..y"; do
|
|
pamtester -v authforge "$u" authenticate 2>&1 | head -3
|
|
echo "---"
|
|
done
|
|
```
|
|
|
|
Logs show up via `journalctl -t authforge_pending` (or wherever your
|
|
distribution routes `pam_syslog` — typically `/var/log/auth.log`).
|
|
|
|
## Smoke test 4: recovery-code path (Phase 12)
|
|
|
|
The module's recovery branch activates when invoked with `mode=recovery` in
|
|
the PAM stack. The pam-auth-update profile rendered by Phase 4 puts this
|
|
line first in the auth stack — a successful recovery code short-circuits
|
|
the rest of the auth chain via `[success=done default=ignore]`.
|
|
|
|
To exercise it manually with `pamtester` against a custom stack:
|
|
|
|
```bash
|
|
# 1. Issue a code via the daemon and stash the plaintext.
|
|
CODE=$(sudo authforgectl recovery generate "$USER" | tr -d '\n')
|
|
echo "issued $CODE"
|
|
|
|
# 2. Build a PAM stack file that runs ONLY the recovery-mode module.
|
|
sudo tee /etc/pam.d/authforge-recovery <<'EOF'
|
|
auth [success=done default=ignore] pam_authforge_pending.so mode=recovery
|
|
auth required pam_deny.so
|
|
EOF
|
|
|
|
# 3. Verify a matching code authenticates.
|
|
echo "$CODE" | pamtester -v authforge-recovery "$USER" authenticate
|
|
# Expect: PAM_SUCCESS, /var/lib/authforge/recovery/$USER deleted, and
|
|
# /var/lib/authforge/pending/$USER now contains re_enroll: true.
|
|
|
|
# 4. Verify a non-matching code falls through to pam_deny.
|
|
echo "$CODE" | pamtester -v authforge-recovery "$USER" authenticate
|
|
# Expect: failure (pam_deny) — the recovery file was already consumed.
|
|
|
|
# 5. Cleanup.
|
|
sudo rm /etc/pam.d/authforge-recovery /var/lib/authforge/pending/$USER
|
|
```
|
|
|
|
The recovery file is at `/var/lib/authforge/recovery/<user>` mode 0600.
|
|
The two-line format is `<expires_unix>\n<argon2id-PHC>\n`.
|
|
|
|
## CI status
|
|
|
|
CI cannot run `pamtester` against a real PAM stack without root and a VM, so
|
|
this is the last manual gate before the Phase 14 PPA build. The CI pipeline
|
|
only verifies that `make -C pam` produces a valid ELF.
|