Use pam_shells to Allow Only Listed Login Shells
You will add a PAM check that permits access only when a user's login shell appears in /etc/shells. You will also verify the file and PAM stack before changing a login service, then keep a rollback copy. Allow about 15 minutes, plus a maintenance window if the service is used by other people.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide uses pam_shells from Ubuntu's libpam-modules package, version 1.5.3-5ubuntu5.7 on the reference machine. The local manual is dated 5 July 2023. The module has no options and supplies both auth and account module types.
1. Check the current shell policy
Read the existing list and the shell recorded for the account you care about. These are ordinary, read-only commands:
$ sed -n '1,120p' /etc/shells
$ getent passwd LOGIN_NAME
LOGIN_NAME:x:1001:1001::/home/LOGIN_NAME:/bin/bash
Replace LOGIN_NAME with a real account name. The final field from getent passwd is the login shell. Check it against /etc/shells, including the exact path. A shell that is executable is not automatically valid for this module: it must be listed.
Checkpoint: this check should produce one shell path from the account record and the same path as a line in /etc/shells. A comment or blank line does not count as a shell entry.
2. Check the file the module will trust
The manual says that needed files such as /etc/shells must be plain files and not world-writable. Verify that before touching PAM:
$ stat -c 'type=%F mode=%a owner=%U group=%G path=%n' /etc/shells
type=regular file mode=644 owner=root group=root path=/etc/shells
$ test ! -L /etc/shells && test ! -w /etc/shells && echo 'safe file checks passed'
safe file checks passed
The numeric mode can differ from 644 and still be acceptable, provided the file is regular and is not writable by everyone. If the checks fail, stop and repair the file ownership or permissions through your normal system administration process. Do not make a security decision from ls output alone.
3. Find the PAM service you intend to protect
PAM rules are attached to a service file, normally under /etc/pam.d. A rule in sshd affects SSH, while a rule in login affects the local login program. A rule in common-auth or common-account can affect several services that include it.
$ ls -l /etc/pam.d/SERVICE_NAME
$ sed -n '1,160p' /etc/pam.d/SERVICE_NAME
Replace SERVICE_NAME with the actual PAM service, not the name of a shell. Confirm that the application really uses PAM and that its stack includes the type you plan to change. This is a read-only checkpoint. Do not put a new rule in a shared file simply because it is easy to find.
Keep an existing root session or console available while testing. A bad authentication or account rule can deny every user of that service, including administrators. Do not test a change by closing your only working session.
4. Back up the service configuration
This step requires elevated privileges. Choose a backup name tied to the service and record its checksum:
$ sudo cp --preserve=mode,ownership,timestamps /etc/pam.d/SERVICE_NAME /etc/pam.d/SERVICE_NAME.before-pam-shells
$ sha256sum /etc/pam.d/SERVICE_NAME /etc/pam.d/SERVICE_NAME.before-pam-shells
Do not overwrite an older backup without checking it first. The copy gives you a direct recovery path if the service stops accepting logins.
5. Add one required pam_shells rule
Before editing, read the installed module path and package ownership:
$ dpkg-query -S /lib/x86_64-linux-gnu/security/pam_shells.so
libpam-modules:amd64: /lib/x86_64-linux-gnu/security/pam_shells.so
Edit the selected service file with an administrative editor:
$ sudoedit /etc/pam.d/SERVICE_NAME
Add this line to the appropriate stack:
auth required pam_shells.so
This is the form shown by the installed pam_shells(8) manual. It has no module arguments. The required control means a failure makes the overall stack fail, although later modules in that stack may still run. The module returns success when the user's shell is listed, authentication error when access is denied, and service error when it cannot obtain the user name.
The module also supports the account type. Use that only when the service's account stack is where you have decided the policy belongs, and follow the existing stack design. Do not add both lines by reflex: two checks increase the chance of an unintended service-wide lockout without adding a useful test.
6. Verify the edit without opening a new risk
Check the exact line and inspect the surrounding stack:
$ sudo grep -n -C 3 -E '^[[:space:]]*(auth|account)[[:space:]]+required[[:space:]]+pam_shells\.so([[:space:]]|$)' /etc/pam.d/SERVICE_NAME
$ sudo test -r /etc/pam.d/SERVICE_NAME && echo 'PAM file is readable'
PAM file is readable
There is no standalone pam_shells command to run. It is a module loaded by a PAM-aware application, so a text check can confirm the edit but cannot prove that a particular service will authenticate correctly. Test first with a permitted account whose shell is already listed, then test the intended denied case only if you have a separate administrative path.
Do not deliberately change a real user's shell merely to create a negative test. That changes account state and can disrupt scheduled jobs, service wrappers and future logins. If you must test that path, use a disposable account in an isolated maintenance setup and restore its original shell afterwards.
7. Recover if access fails
Restore the backup from a still-working root session or console. This is an elevated, service-affecting action:
$ sudo cp --preserve=mode,ownership,timestamps /etc/pam.d/SERVICE_NAME.before-pam-shells /etc/pam.d/SERVICE_NAME
$ sudo sha256sum /etc/pam.d/SERVICE_NAME /etc/pam.d/SERVICE_NAME.before-pam-shells
If the hashes match, the service file is back to the saved state. PAM applications may keep their own sessions, so avoid restarting a service unless its documentation requires it; restarting can disconnect users. If no administrative path remains, use the host's console or recovery procedure rather than repeatedly guessing at PAM rules.
Done means
- The target account's shell exactly matches an entry in
/etc/shells. /etc/shellsis a regular, non-world-writable file.- You changed the intended PAM service, not an unnecessarily broad shared stack.
- The rule is
auth required pam_shells.soor a deliberate account-stack equivalent, with no invented options. - A backup exists and you have a working recovery path before testing.
- You understand that the module checks the PAM user shell; it does not change shells or edit
/etc/shells.