
Linux PAM for Compliance
Linux PAM
Linux uses PAM or the
Pluggable Authentication Modules
mechanism for security.
This is because:
Authentication is crucial for security.
If you can't trust user authentication,
much of your information security is an unsolvable problem.
There is no single correct answer for
"How should users be authenticated?"
Different organizations have different concerns,
and we should be able to fine-tune our user authentication
to meet our needs.
Secure software is difficult to create.
We need a diverse library of security modules written
by trusted programmers, and examined by a community
eager to find errors made by others.
This is why we have PAM.
Here is the architecture of PAM.
At the top are the PAM-aware services.
Some are continually-running programs listening for
network connections (the SSH daemon, the Samba daemon),
others are continually-running programs waiting for
user interaction at the console
(login
and gdm
),
but others are programs which users may run
whenever they choose
(su
and sudo
).
There are many more than the examples shown here.
In the center is the main PAM library,
libpam.so
.

The bottom half of the diagram shows the PAM modules,
named pam_*.so
.
Those on the left have to do with mechanisms by which
users can authenticate themselves —
classic UNIX-style passwords, fingerprint scanners,
Kerberos tickets, and a smart card or YubiKey device.
Some PAM modules shown at right impose restrictions — password quality and reuse, or locking an account after a specified number of authentication failures within a specified time. Other PAM modules are used for their side effects, such as reporting to the user when they last logged in, or when there was a recent login failure for their account. Other PAM modules trigger useful log records, or establish the user's environment for a new session.
Again, there are many more PAM modules than the examples shown here.
Programs are linked to the main PAM library
in order to use the PAM architecture.
Let's look at the login
program.
That's what you interact with
while authenticating onto a text console.
It uses libpam.so.0
and a related PAM library,
and some other libraries obviously related to security.
$ which login /usr/bin/login $ ldd /usr/bin/login linux-vdso.so.1 (0x00007ffd1434d000) libpam.so.0 => /lib64/libpam.so.0 (0x00007fe0688a9000) libpam_misc.so.0 => /lib64/libpam_misc.so.0 (0x00007fe0686a5000) libaudit.so.1 => /lib64/libaudit.so.1 (0x00007fe06847b000) libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fe068251000) libc.so.6 => /lib64/libc.so.6 (0x00007fe067e8d000) libdl.so.2 => /lib64/libdl.so.2 (0x00007fe067c89000) libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007fe067a83000) libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007fe0677ff000) /lib64/ld-linux-x86-64.so.2 (0x00007fe068cc2000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fe0675df000)
The main library collection is typically somewhere like
/usr/lib64/
(as on RHEL and Oracle Linux) or
/lib/x86_64-linux-gnu/
(as on Debian, Ubuntu,
and Mint Linux).
The main library file is there, probably with a symbolic
link named libpam.so.0
pointing to the actual
file, with the same name with a version number appended.
shared library files
The PAM module files are in a subdirectory named
security
.
On Oracle Linux 8 I see this:
$ ls -l /lib64/libpam.so* lrwxrwxrwx 1 root root 16 Aug 1 2020 /lib64/libpam.so.0 -> libpam.so.0.84.2 -rwxr-xr-x 1 root root 70368 Aug 1 2020 /lib64/libpam.so.0.84.2 $ ls -F /lib64/security pam_access.so* pam_limits.so* pam_shells.so* pam_cap.so* pam_listfile.so* pam_stress.so* pam_chroot.so* pam_localuser.so* pam_succeed_if.so* pam_console.so* pam_loginuid.so* pam_systemd.so* pam_cracklib.so* pam_mail.so* pam_time.so* pam_debug.so* pam_mkhomedir.so* pam_timestamp.so* pam_deny.so* pam_motd.so* pam_tty_audit.so* pam_echo.so* pam_namespace.so* pam_umask.so* pam_env.so* pam_nologin.so* pam_unix.so* pam_exec.so* pam_permit.so* pam_unix_acct.so@ pam_faildelay.so* pam_postgresok.so* pam_unix_auth.so@ pam_faillock.so* pam_pwhistory.so* pam_unix_passwd.so@ pam_filter/ pam_pwquality.so* pam_unix_session.so@ pam_filter.so* pam_rhosts.so* pam_userdb.so* pam_ftp.so* pam_rootok.so* pam_usertype.so* pam_group.so* pam_securetty.so* pam_warn.so* pam_issue.so* pam_selinux.so* pam_wheel.so* pam_keyinit.so* pam_selinux_permit.so@ pam_xauth.so* pam_lastlog.so* pam_sepermit.so*
The PAM configuration files are in the
/etc/pam.d/
directory.
Every PAM-aware program looks there
for a configuration file with the same name.
If such a file exists, it uses the contained
authentication logic.
If it doesn't, then the program does what it natively
does by default as guided by its configuration files.
You will find other files in the PAM directory,
depending on your distribution and which packages
you have installed.
For example, system-auth
and
password-auth
on RHEL and Oracle Linux,
or common-auth
and common-password
on Debian, Ubuntu, and Mint Linux.
As we will see below, PAM lets you configure a complex password quality and maintenance policy. Because of required password change schedules, a user might be forced to change their password at any authentication event, regardless of which service they are authenticating to.
You want the password quality policy to always be the same. You don't want one set of rules applied if the text login screen told the user it was time for a new password, a different set of rules if they were logging in to the graphical console, and a third set of rules if they were connecting in via SSH.
You could strive to maintain identical policy
definitions in several files in /etc/pam.d/
,
but the details can very easily become inconsistent.
A include
mechanism was created to allow you
to configure a general policy in one file, and pull that
into multiple service-specific files.
It's quite common for a PAM file to contain nothing but
include
statements, as that service just
follows the general policies and adds no service-specific
requirements.
A substack
mechanism was added later.
It's quite similar to include
and when you
are first looking at PAM, you probably should consider
them to be equivalent.
See the pam.conf(5)
manual page for the details.
Now let's look at a specific complex set of requirements that can be achieved with PAM.
DISA STIGs
DISA STIGArchive
DISA, the Defense Information System Agency, is part of the U.S. Department of Defense. DISA maintains the Security Technical Implementation Guide (or STIG) documents.
The STIG documents describe cybersecurity requirements for a wide range of computer operating systems, routers, and other computing systems. STIG details are based on concepts in NIST Special Publication 800-53, which specifies security controls for all U.S. federal information systems other than national security systems. SP 800-53 describes concepts like "Passwords should be adequately complex", while the STIGs specify how long, how many character classes, and so on.
There is noSTIG for Debian
DISA will only create a STIG for an operating system from a trusted provider. For example, Red Hat (and now IBM) for RHEL, Oracle for Oracle Linux, and Canonical for Ubuntu Linux. There is no STIG for Debian, nor will there be one, because there is no company controlling Debian releases and maintaining quality and patch control.
Example STIGContent
Another page of mine shows you example STIG details. Let's see a practical solution: how to configure PAM on RHEL or Oracle Linux for STIG compliance.
Decide Now: Ease of Maintenance, or Complete Control
Linux distributions include programs to automatically modify
the PAM files as you turn features on and off.
RHEL and derivatives up through RHEL 7 include
authconfig
and authconfig-gtk
.
At version 8 those are replaced by
authselect
and authselect-gtk
.
Debian and Ubuntu provide pam-auth-update
,
which modifies the shared common-auth
PAM file.
These overwrite your PAM files, making any attempts to manually configure PAM confusing and frustrating.
If you need to be compliant to the US DISA STIG,
then you are going to have to modify your PAM files by
hand and be very careful to never use the
automated tools.
Unfortunately, authconfig
cannot be removed
due to package dependencies, so your PAM configuration relies
on all system administrators understand that it must never
be used.
On a test system, however, these tools can be quite useful.
You can use them to see what all is involved in enabling
or disabling authentication to sssd
, for example.
Red Hat and Oracle Linux system-auth
Usually system-auth-ac
is the actual file,
and system-auth
is a symbolic link pointing
to it.
The file system-auth
on RHEL and Oracle Linux
and derivatives is used by many services.
It's provided with the distribution,
and the main place where you extend or fine-tune
overall system authentication policy.
Here is the list of PAM configuration files,
and then we see how many use system-auth
.
$ ls /etc/pam.d authconfig login smtp@ authconfig-gtk other smtp.postfix authconfig-tui passwd sshd chfn password-auth@ sssd-shadowutils chsh password-auth-ac su config-util pluto subscription-manager crond polkit-1 subscription-manager-gui fingerprint-auth@ postlogin@ sudo fingerprint-auth-ac postlogin-ac sudo-i gdm-autologin remote su-l gdm-fingerprint rhn_register system-auth@ gdm-launch-environment runuser system-auth-ac gdm-password runuser-l system-config-authentication gdm-pin screen systemd-user gdm-smartcard smartcard-auth@ vlock liveinst smartcard-auth-ac xserver $ grep -l system-auth /etc/pam.d/* /etc/pam.d/chfn /etc/pam.d/chsh /etc/pam.d/config-util /etc/pam.d/crond /etc/pam.d/gdm-autologin /etc/pam.d/gdm-launch-environment /etc/pam.d/login /etc/pam.d/passwd /etc/pam.d/pluto /etc/pam.d/polkit-1 /etc/pam.d/screen /etc/pam.d/su /etc/pam.d/sudo /etc/pam.d/systemd-user /etc/pam.d/vlock
Let's look inside that crucial file. Below is the original content from a RHEL 7 installation.
The first field is the phase of authentication when the rule is applied.
The second is the logic.
Both required
and requisite
must succeed.
If a requisite
rule fails, the entire stack
immediately exits with failure.
If a required
rule fails, the stack is doomed
to eventually fail, but the rule execution continues.
If a sufficient
rule succeeds, the stack
immediately exits with success, unless an earlier
required
rule failed, in which case it's
immediate stack failure.
The third field is the PAM module, with any parameters continuing on that line.
$ cat /etc/pam.d/system-auth #%PAM-1.0 # This file is auto-generated. # User changes will be destroyed the next time authconfig is run. auth required pam_env.so auth required pam_faildelay.so delay=2000000 auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet account required pam_permit.so password requisite pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so
The reason for the choices of all of required
,
requisite
, and sufficient
is to support various logical combinations.
Let's say that you wanted the use to be allowed onto the
system if they could scan their recognized fingerprint
or if they could type their password.
Fingerprint is easy, try that first, but maybe the scanner
is broken or dirty.
Include this rule stack fragment.
You are allowed in with either fingerprint or
password, but denied access if you fail at both.
pam_deny.so
is a very simple module,
it always fails!
[...other lines omitted...] auth sufficient pam_fprintd.so auth sufficient pam_unix.so auth required pam_deny.so [...other lines omitted...]
On the other hand, maybe you want multi-factor authentication. The user must scan their recognized fingerprint and type their password. All or nothing! Include this:
[...other lines omitted...] auth required pam_fprintd.so auth required pam_unix.so [...other lines omitted...]
If you use required
for both, then if either
test fails the user is simply told that the authentication
failed.
They aren't told which part they failed.
If you instead use requisite
, the overall
sequence exits when they first fail a test.
Which of those approaches is best?
Whichever you choose to implement!
Unless, of course, someone like the Department of Defense
says it must be done a certain way.
WARNING
Messing with PAM is an almost guaranteed way of locking yourself out of your system.
Keep a backup copy of the original /etc/pam.d/
so you can easily restore it.
Turn off session timeout and use virtual consoles
to keep yourself logged in as root
somewhere.
Compliance, Part I: Password Quality, Account Lock-Out, and Local versus Central Account Definitions
On top of everything else, most organizations (including DISA)
seem to have a password-guessing threat model dating from 1990.
Aggressive password rotation policies make things worse, with
the result being powerful self-inflicted denial of service.
Outside the Department of Defense, password apps on smartphones
provide good security as long as you protect the device.
Inside DoD and its contractors, phones are locked away
in cabinets before you enter the facility.
What little I've observed is that simple keyboard patterns
dominate:
1qaz!QAZ2wsx@WSX
30 days later, shift one column:
2wsx@WSX3edc#EDC
The manual STIG documents specify explicitly configuring several details that would happen by default anyway. And, many automated tools apply those unneeded literal checks. The STIGs don't have to make sense, but we have to satisfy them.
Making things worse, some settings can be done with parameters on the PAM module line and with settings in module-specific configuration files.
As an example of this madness, current PAM modules require
SHA-2-512 hashes of passwords by default.
But the STIG says that you must explicitly specify
SHA512
in the
/etc/login.defs
configuration file
(rule RHEL-07-010210),
and
sha512
in the /etc/libuser.conf
configuration file
(rule RHEL-07-010220),
and
sha512
as a parameter to
the pam_unix.so
module in the PAM files
(rule RHEL-07-010200).
Below is a system-auth
PAM file with the
needed changes and additions for password-only authentication.
"Local users" are those defined in the local system's
/etc/passwd
and /etc/shadow
files.
This will be root
and quite likely no one else.
The majority, if not all, of the ordinary users will be
defined through some
LDAP / Kerberos / Winbind / etc network
protocols, relying on FreeIPA or Active Directory
or similar.
The pam_sss.so
module uses the sssd
daemon, configured through /etc/sssd/sssd.conf
,
to talk to those network information and authentication
services.
So, we need some logic like this:
if (user is defined in /etc/passwd) { password tests with pam_unix.so } else { key/password/Kerberos tests with pam_sss.so }
Unfortunately, while the PAM configuration syntax is a programming language, it's a poor one. Its logic is limited to what you can build with a weak version of GOTO, which can only GOTO forward.
Making things worse, the needed pam_faillock.so
has entirely different behavior depending on whether
you use the preauth
, authfail
,
or authsucc
parameters.
It's one module but it behaves in three totally
different ways.
It would be very foolish to simply copy a block of code off some Internet page into your PAM configuration! Make sure you fully understand what this does. It very likely will burn down your facility if used as shown here.
PAM syntax is described here:
http://linux-pam.org/Linux-PAM-html/sag-configuration-file.html
You will need that reference to understand the
square-bracketed descriptions often used in place
of the simple required
, sufficient
,
and so on.
Making things worse, distributions frequently omit
the manual pages for some PAM modules.
You may be able to find the missing ones at
linux-pam.org.
As of 2021 that site was still HTTP-only,
and the bare-basics HTML landing page says it was
last changed in 2018.
Modules such as pam_gdm.so
, included in
Red Hat Linux, aren't mentioned either within the Red Hat
distribution's manual pages or on the linux-pam.org site.
Here is system-auth
for single-factor authentication.
As best as I can tell, this satisfies the STIG requirements.
I highlighted the local password authentication with
light purple,
the remote LDAP/Kerberos/FreeIPA/AD authentication with
light orange,
and comments are greyed out.
# Modified system-auth for password-only authentication auth required pam_env.so # The logic of this next rule means: # "If defined in /etc/passwd, jump ahead 3 rules to # the faillock/pam_unix/faillock/faillock block. # Otherwise, flow into the following pam_sss.so section." auth [success=3 default=ignore] pam_localuser.so # For the remote LDAP/Kerberos/FreeIPA/AD authentication, immediately fail and exit # if it isn't an ordinary user with UID>=1000, then apply the pam_sss.so tests. auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth sufficient pam_sss.so forward_pass auth required pam_deny.so # The main authentication block begins here. Start by setting up the lock-on-failure # mode. With these numbers: lock the account after 3 failures within 20 minutes, # then automatically unlock it one hour later. Then apply the pam_unix.so # password test. If that fails, potentially lock the account. But if it # succeeds, skip ahead to the 3rd pam_faillock.so line. auth required pam_faillock.so preauth silent audit deny=3 even_deny_root fail_interval=1200 unlock_time=3600 auth [success=1 default=bad] pam_unix.so auth [default=die] pam_faillock.so authfail audit deny=3 even_deny_root fail_interval=1200 unlock_time=3600 # If we hit the above rule, we have failed and the account should now be locked if # it was the 3rd failure within 1200 seconds. But if we have jumped to this next rule, # we must have succeeded. This next rule resets the lockout counter to zero. auth required pam_faillock.so authsucc silent deny=3 fail_interval=1200 unlock_time=3600 account required pam_faillock.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet account required pam_permit.so # At least 15 characters, all 4 classes (upper, lower, digit, other) with no more than # 3 in a row of the same character, no more than 4 characters of the same class in # a row, plus the usual heuristic and ad-hoc cracklib tests. password required pam_pwquality.so try_first_pass local_users_only retry=3 difok=4 minlen=15 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 maxrepeat=3 maxclassrepeat=4 # No using any of your past 24 passwords. password required pam_pwhistory.so use_authtok remember=24 password sufficient pam_unix.so try_first_pass use_authtok sha512 shadow remember=24 password sufficient pam_sss.so use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so # Despite this next rule being optional, gdm won't run correctly without it. -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_lastlog.so showfailed session required pam_unix.so session optional pam_sss.so
For STIG compliance you must also specify SHA-2-512 hashing of passwords in two configuration files.
$ grep 512 /etc/login.defs /etc/libuser.conf /etc/login.defs:# SHA-2-512 for RHEL-07-010210 /etc/login.defs:ENCRYPT_METHOD SHA512 /etc/libuser.conf:# SHA-2-512 for RHEL-07-010220 /etc/libuser.conf:crypt_style = sha512
You must also specify the same pam_pwquality.so
parameters in a separate configuration file.
$ cat /etc/security/pwquality.conf
# Added for:
# RHEL-07-010170
# RHEL-07-010180
# RHEL-07-010190
# RHEL-07-010280
minlen=15
difok = 8
ucredit = -1
lcredit = -1
dcredit = -1
ocredit = -1
maxrepeat = 3
maxclassrepeat = 4

Compliance, Part II: Adding Multi-Factor Authentication
Warning:
The pam_pkcs11.so
module is
well on the way out.
This solution works on RHEL 7 and Oracle Linux 7.
With version 8 of both of those, or on recent Debian
and Ubuntu, pam_pkcs11.so
is gone.
Both pam_sss.so
and pam_u2f.so
offer ways forward.
We will add multi-factor authentication
using a YubiKey device and the
pam_pkcs11.so
module.
Above is a YubiKey.
They're quite small and easy to misplace. Connect a brightly colored cord! We're about to require possession of that specific device.
Our goal is multi-factor authentication. The user most both possess a specific YubiKey device and know their password. Here's our desired logic, with YubiKey work added in light blue:
if (user is defined in /etc/passwd) { if (service is text login or graphical login or a screensaver) { YubiKey tests with pam_pkcs11.so } password tests with pam_unix.so } else { key/password/Kerberos tests with pam_sss.so }
Here is a system-auth
file that seems
to accomplish this.
Remember that it would be very foolish to simply copy
a block of code off some Internet page
into your PAM configuration!
There is a required pam_pkcs11.so
rule for
the YubiKey, then a pam_unix.so
whose
customized logic is similar to required
,
except it adds in skipping the next rule if this one succeeds.
# Modified system-auth for multi-factor authentication auth required pam_env.so # The logic of this next rule means: # "If defined in /etc/passwd, jump ahead 3 rules to # the faillock/pam_unix/faillock/faillock block. # Otherwise, flow into the following pam_sss.so section." auth [success=3 default=ignore] pam_localuser.so # For the remote LDAP/Kerberos/FreeIPA/AD authentication, immediately fail and exit # if it isn't an ordinary user with UID>=1000, then apply the pam_sss.so tests. auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth sufficient pam_sss.so forward_pass auth required pam_deny.so # The main authentication block begins here. Start by setting up the lock-on-failure # mode. With these numbers: lock the account after 3 failures within 20 minutes, # then automatically unlock it one hour later. auth required pam_faillock.so preauth silent audit deny=3 even_deny_root fail_interval=1200 unlock_time=3600 # We only want to require the YubiKey for the text login, graphical display managers, # and screensaver services. So if the service is not one of those, skip pam_pkcs11.so. auth [success=1 default=ignore] pam_succeed_if.so service notin login:gdm:xdm:kdm:xscreensaver:gnome-screensaver:kscreensaver quiet use_uid auth requisite pam_pkcs11.so wait_for_card debug auth [success=1 default=bad] pam_unix.so auth [default=die] pam_faillock.so authfail audit deny=3 even_deny_root fail_interval=1200 unlock_time=3600 # If we hit the above rule, we have failed and the account should now be locked if # it was the 3rd failure within 1200 seconds. But if we have jumped to this next rule, # we must have succeeded. This next rule resets the lockout counter to zero. auth required pam_faillock.so authsucc silent deny=3 fail_interval=1200 unlock_time=3600 account required pam_faillock.so account required pam_unix.so account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 1000 quiet account required pam_permit.so password required pam_pwquality.so try_first_pass local_users_only retry=3 difok=4 minlen=15 ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 maxrepeat=3 maxclassrepeat=4 password required pam_pwhistory.so use_authtok remember=24 password sufficient pam_unix.so try_first_pass use_authtok sha512 shadow remember=24 password sufficient pam_sss.so use_authtok password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so # Despite this next rule being optional, gdm won't run correctly without it. -session optional pam_systemd.so session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_lastlog.so showfailed session required pam_unix.so session optional pam_sss.so
With the above in place, we see something like the following on the text login screen:
Red Hat Enterprise Linux Server 7 Kernel 3.10.0 on an x86_64 localhost login: cromwell Please insert your smart card. Welcome cromwell! Smart card PIN: ****** Password: _______________ Last login: Tue May 06 06:31:46 UTC 2025 on tty3 [cromwell@localhost ~]$