Understanding the effects of PAM module results ('controls' in PAM jargon)
To simplify, PAM is the standard system on Linux (and often other Unixes) for configuring how applications such as SSH handle various aspects of authenticating and logging people in. PAM splits this work up into a collection of PAM modules (sometimes 'PAMs', since PAM is theoretically short for 'Pluggable Authentication Modules'), which are then configured in per application (or 'service') stacks. Each individual PAM module can succeed, fail, or signal other conditions, and the PAM stack for a given application tells you not only what order PAM modules should be checked in, but what the effects of their results are. In the jargon of pam.conf, this is the control of a PAM module.
There are two forms for these controls; the 'historical' and still
very common single-word form that uses things like '
sufficient', and the newer, more detailed and complicated syntax.
There are two descriptions of the meanings of the historical
single-word forms in pam.conf; the somewhat informal main
description, and then the potentially more explicit version that
restates them in terms of the newer and more detailed syntax. All
of this makes for a system that can be hard to understand and follow.
(In the following I'm going to assume that you've read through the pam.conf manual page to somewhat understand the various controls, or that you know PAM well enough to not need to.)
Taken from CentOS 7, here is one example of the standard 'auth' stack:
auth required pam_env.so auth sufficient pam_unix.so nullok try_first_pass auth requisite pam_succeed_if.so uid >= 1000 quiet_success auth required pam_deny.so
Let's walk through this and try to understand its aggregate effects.
sets environment variables; since it's
required, if it fails for some reason the authentication process will appear to continue, but at the end it will be considered to fail.
Pam_env normally succeeds; as far as I know it fails only in exceptional circumstances.
verifies Unix passwords. It's '
sufficient', so if it succeeds the authentication is done. However, if it fails, further modules in the auth stack continue to be checked, and I believe that later modules could accept the authentication.
will succeed if the UID is 1,000 or higher and fail otherwise.
Since it's set as '
requisite', if it fails (the UID is under 1,000), authentication immediately fails, but if the UID is 1,000 or higher, the stack continues to check further modules.
does what you'd think; it always says no. Since it's '
required', this failure is not immediately fatal, but since it's the last module listed, its failure will cause the entire stack to fail and authentication to not be successful.
Under normal circumstances, if you enter a correct password the stack will stop at pam_unix and consider you authenticated. If you enter an incorrect password, you will eventually fail in the rest of the stack, but possibly with different error messages depending on your UID. If your UID is under 1,000, the failure will be caused by pam_succeed_if; if your UID is 1,000 or over, it will be caused by pam_deny. This two-sided failure is somewhat confusing; it's not clear what CentOS 7 is up to.
The Ubuntu 20.04 stack is as confusing in its own way, although it has comments. Here it is:
auth [success=1 default=ignore] pam_unix.so nullok_secure auth requisite pam_deny.so auth required pam_permit.so auth optional pam_cap.so
- Pam_unix with the '
success=1' syntax will skip the next module on success and otherwise do nothing.
- Pam_deny with '
requisite' will immediately fail the authentication if it's processed, but it will only be processed if pam_unix did not succeed, ie if you got the password wrong.
required' apparently is present simply to, to quote the comments in the file:
prime the stack with a positive return value if there isn't one already; this avoids us returning an error just because nothing sets a success code since the modules above will each just jump around
If it fails for some inexplicable reason, as a '
required' module it will make the authentication fail. But we only got to it if we skipped pam_deny because you entered the right Unix password.
"sets the current process' inheritable capabilities", to quote its manual
page. Since it's '
optional', its result appears not to matter, but the stack will have been set to succeed by pam_permit if we got here.
The first two PAM modules here and their setup seem to have the effect
of failing the authentication if you enter a bad Unix password and
otherwise letting the rest of the stack go on, but I'm not sure why
Ubuntu needs two modules where making pam_unix a '
module seems like it should work.
(The one difference that jumps out to me is that this way, if the
pam_unix module returns '
new_authtok_reqd', meaning 'the
user needs to change their password', the Ubuntu stack will fail
and a '
requisite' would make things succeed. See eg here. In the same situation, I believe
the CentOS 7 authentication stack will succeed.)
There's an entire additional level of complexity in real world PAM usage (and in understanding the effects of changes to your system's PAM stuff), but that's a topic for another entry.