pam-lease grants time-limited SSH access to Linux users via a custom PAM module written in C, with automatic session termination enforced by a systemd watchdog daemon. Once a lease is granted, users SSH in normally with zero extra prompts; the lease is checked transparently on every login attempt.
- An administrator runs
pamlease grant <user> --duration 30m. This writes a JSON lease file to/run/pamlease/<user>.lease(tmpfs — wiped on reboot). - When the user SSHes in, the PAM module (
pam_lease.so) reads the lease file.- No lease → authentication denied, "Permission denied".
- Expired lease → authentication denied.
- Valid lease → authentication proceeds; the session-open hook prints the expiry time.
- The
pamlease-watchdogdaemon runs every 30 seconds. It warns the user at 5 minutes and 1 minute before expiry by writing to their TTY, then terminates the session vialoginctl terminate-userat expiry and deletes the lease file.
Install the PAM development headers for your distribution before building the C module:
| Distribution | Command |
|---|---|
| Debian / Ubuntu | sudo apt install libpam-dev |
| RHEL / Fedora | sudo dnf install pam-devel |
| Arch Linux | sudo pacman -S linux-pam |
sudo pip install .This installs the pamlease and pamlease-watchdog CLI tools and creates
/run/pamlease/ with mode 700.
cd pam_lease
make
sudo make installThe Makefile verifies that pam_appl.h is present before compiling and installs
pam_lease.so to /lib/security/.
Add the following two lines to /etc/pam.d/sshd, before the existing auth lines:
auth required pam_lease.so
session optional pam_lease.so
Example resulting file fragment:
auth required pam_lease.so
auth required pam_unix.so
auth required pam_nologin.so
...
session optional pam_lease.so
session required pam_unix.so
sudo cp systemd/pamlease-watchdog.service /etc/systemd/system/
sudo cp systemd/pamlease-watchdog.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now pamlease-watchdog.timerThe watchdog runs every 30 seconds and is restarted automatically if it crashes.
All pamlease subcommands require root.
# Grant 30 minutes
pamlease grant odai --duration 30m
# Grant 1.5 hours, overwriting any existing lease
pamlease grant alice --duration 1h30m --forceOutput:
Lease granted for odai until 10:30:00 (30m 0s).
pamlease revoke odaiOutput:
Lease revoked for odai. Active sessions terminated.
Active SSH sessions are terminated immediately via loginctl terminate-user.
pamlease listOutput:
USER GRANTED BY EXPIRES AT TIME LEFT
-----------------------------------------------------------------
odai root 2026-03-21 10:30 28m 14s
alice root 2026-03-21 11:00 58m 14s
pamlease show odaiOutput:
User: odai
Granted by: root
Issued at: 2026-03-21 10:00:00
Expires at: 2026-03-21 10:30:00
Time left: 28m 14s
Warned 5m: No
Warned 1m: No
pamlease extend odai --duration 30mOutput:
Lease for odai extended by 30m 0s. New expiry: 11:00:00.
Extension resets both the 5-minute and 1-minute warning flags so the user receives fresh warnings before the new deadline.
Your session will expire in 28 minutes (at 10:30:00).
*** pamlease: Warning: Your session expires in 5 minutes. Save your work. ***
*** pamlease: Warning: Your session expires in 1 minute. ***
*** pamlease: Your session has expired. You will be disconnected now. ***
The session is then terminated by loginctl terminate-user.
Permission denied (publickey).
The connection is dropped by PAM before any password prompt is shown.
The PAM module is loaded with required, meaning a valid lease is mandatory for
authentication. There is no fallback — if the lease file is missing, unreadable, or
expired, the user cannot log in regardless of their SSH key or password.
- tmpfs storage — lease files live in
/run/pamlease/, which is a tmpfs mount on all modern Linux distributions. All leases are automatically wiped on reboot with no manual cleanup required. - Root-only directory —
/run/pamlease/is created with mode0700owned by root. Lease files are created with mode0600owned by root. Unprivileged users cannot read, create, or modify lease files. - Atomic writes — all lease file updates are performed by writing to a
.tmpfile and then callingos.rename(), so a concurrent reader never sees a partially-written file. - No secrets — the lease file contains only timing metadata. There are no tokens, keys, or passwords stored on disk.
- CLI requires root — every
pamleasesubcommand checksos.geteuid() == 0and exits immediately if not running as root.
pip install pytest
pytest tests/All tests use pytest's tmp_path fixture and never touch /run/pamlease/.