2022-04-10
A Linux PAM setup and the problem of stopping authentication
Suppose, not hypothetically, that you have a Linux system where SSH logins are authenticated with passwords and then a MFA challenge. This is implemented the only sensible way, with a PAM module to do MFA, such as the one from Duo; you add this PAM module to your setup along side the existing PAM stuff used for SSH logins. This is all easy to set up and there's plenty of documentation on it, but now you'd like to go the extra distance so that the login fails immediately if people get the password wrong, rather than going on to also do a (pointless) MFA challenge.
(There are good reasons to do passwords first and then stop if that fails.)
In theory this is easy in PAM authentication, using the PAM concept of
controls for what the results
of PAM modules have. If you set both pam_unix (for passwords) and
pam_mfa (for your MFA challenge) to be 'requisite
' PAM modules,
one after the other, then everything will work right. In practice, the
problem is that your Unix is may be doing password checks through a PAM
substack. Your /etc/pam.d/sshd may have, for example:
auth required pam_sepermit.so auth substack password-auth auth include postlogin
This is from CentOS 7, and all of the password authentication I covered yesterday is in the 'password-auth' substack (in /etc/pam.d/password-auth). Ubuntu has a different setup which we'll cover later.
To simplify the description in pam.conf, a substack
essentially becomes a PAM module that is set as 'required
'. All
of the controls in the substack's configuration (such as 'requisite
'
or 'sufficient
') only affect the evaluation of the substack, and at
the end PAM returns control to the surrounding PAM file.
On the one hand, this is definitely what we want for a module set up
as 'sufficient
' in the substack, because when we add pam_mfa to
our /etc/pam.d/sshd after the substack, we want it to still challenge
people who got the password right. On the other hand, this means that if
the substack fails (either by default or through a 'requisite
' module
failing), we will still go on to do additional things, like our MFA
challenge.
In a standard PAM environment, there is no really good way out of
this. If we had a hypothetical pam_echostatus PAM module that
simply used the current success or failure status of the PAM stack
as its own status, we could add it just after the substack as a
'requisite
' module; if the substack had returned failure, this
would make the failure stop PAM.
A more drastic option is to revise the password-auth stack to also
check MFA, but only if we're authenticating a SSH session:
auth [success=1 default=ignore] pam_succeed_if service != sshd auth requisite pam_mfa
But this requires additional changes, because we can no longer have
pam_unix (or any other module) be 'sufficient
', since it isn't.
We need a password-auth structure that is more like the Ubuntu one.
Ubuntu 20.04 handles this differently, in a way that explains some of its oddities I mentioned yesterday. The start of /etc/pam.d/sshd on Ubuntu 20.04 is:
@include common-auth
The @include directive is not documented in pam.conf, but we
can guess what it does by analogy to the 'include
' control that
is documented. Since this is an inclusion instead of a substack, a
'requisite
' failure in it will abort the entire process and avoid
a MFA challenge, even if the MFA challenge is in /etc/pam.d/sshd
instead of common-auth. On the other hand, if you accidentally put
a 'sufficient
' in your /etc/pam.d/common-auth, you've just shot
yourself in the foot for any further 'auth' processing in specific
PAM service control files like /etc/pam.d/sshd.
(Ie, a 'sufficient
' in the Ubuntu common-auth would bypass our
additional MFA challenge for SSH logins. Our normal Ubuntu common-auths
don't have any 'sufficient
' entries, but you might innocently add
one for local changes, especially if you're mostly used to the CentOS
approach and don't notice or understand the critical difference between
a 'substack
' and a '@include
'.)