Using pam_access to sometimes not use another PAM module

October 25, 2024

Suppose that you want to authenticate SSH logins to your Linux systems using some form of multi-factor authentication (MFA). The normal way to do this is to use 'password' authentication and then in the PAM stack for sshd, use both the regular PAM authentication module(s) of your system and an additional PAM module that requires your MFA (in another entry about this I used the module name pam_mfa). However, in your particular MFA environment it's been decided that you don't have to require MFA for logins from some of your other networks or systems, and you'd like to implement this.

Because your MFA happens through PAM and the details of this are opaque to OpenSSH's sshd, you can't directly implement skipping MFA through sshd configuration settings. If sshd winds up doing password based authentication at all, it will run your full PAM stack and that will challenge people for MFA. So you must implement sometimes skipping your MFA module in PAM itself. Fortunately there is a PAM module we can use for this, pam_access.

The usual way to use pam_access is to restrict or allow logins (possibly only some logins) based on things like the source address people are trying to log in from (in this, it's sort of a superset of the old tcpwrappers). How this works is configured through an access control file. We can (ab)use this basic matching in combination with the more advanced form of PAM controls to skip our PAM MFA module if pam_access matches something.

What we want looks like this:

auth  [success=1 default=ignore]  pam_access.so noaudit accessfile=/etc/security/access-nomfa.conf
auth  requisite  pam_mfa

Pam_access itself will 'succeed' as a PAM module if the result of processing our access-nomfa.conf file is positive. When this happens, we skip the next PAM module, which is our MFA module. If it 'fails', we ignore the result, and as part of ignoring the result we tell pam_access to not report failures.

Our access-nomfa.conf file will have things like:

# Everyone skips MFA for internal networks
+:ALL:192.168.0.0/16 127.0.0.1

# Insure we fail otherwise.
-:ALL:ALL

We list the networks we want to allow password logins without MFA from, and then we have to force everything else to fail. (If you leave this off, everything passes, either explicitly or implicitly.)

As covered in the access.conf manual page, you can get quite sophisticated here. For example, you could have people who always had to use MFA, even from internal machines. If they were all in a group called 'mustmfa', you might start with:

-:(mustmfa):ALL

If you get at all creative with your access-nomfa.conf, I strongly suggest writing a lot of comments to explain everything. Your future self will thank you.

Unfortunately but entirely reasonably, the information about the remote source of a login session doesn't pass through to later PAM authentication done by sudo and su commands that you do in the session. This means that you can't use pam_access to not give MFA challenges on su or sudo to people who are logged in from 'trusted' areas.

(As far as I can tell, the only information ``pam_access' gets about the 'origin' of a su is the TTY, which is generally not going to be useful. You can probably use this to not require MFA on su or sudo that are directly done from logins on the machine's physical console or serial console.)


Comments on this page:

From 193.219.181.219 at 2024-10-26 06:16:13:

Unfortunately but entirely reasonably, the information about the remote source of a login session doesn't pass through to later PAM authentication done by sudo and su commands that you do in the session. This means that you can't use pam_access to not give MFA challenges on su or sudo to people who are logged in from 'trusted' areas.

Hmm, I can imagine a new PAM module being able to solve this – it would be possible to implement a no-op pam_sm_authenticate that retrieves the remote IP from some trusted source (maybe from systemd-logind, as the previous pam_systemd invocation has helpfully stashed the remote host in logind's session data); and does pam_set_item(PAM_RHOST) to make it available to the subsequent modules.

From 193.219.181.219 at 2024-10-26 06:50:46:

I can imagine a new PAM module being able to solve this – it would be possible to implement a no-op pam_sm_authenticate that retrieves the remote IP from some trusted source

I wrote one: https://git.nullroute.lt/hacks/pam_sd_rhost.git/

From 193.219.181.219 at 2024-10-26 07:18:40:

You can probably use this to not require MFA on su or sudo that are directly done from logins on the machine's physical console or serial console

There's also an older hack that looks up logind's "is local session" flag: https://git.nullroute.lt/hacks/pam_sd_console.git/

But I'm now having second thoughts about this (and the previous one) – well, it technically works in that it populates PAM_RHOST from the systemd-logind session, but these days a lot of processes (desktop environments, even tmux panes) are being run in their own cgroups that are detached from the logind session, and therefore have no data about their "remote host".

(And of course for tmux/screen the concept of a single remote host doesn't really work anyway; even without logind-related problems it would remain possible for an untrusted SSH connection to just tmux attach a session that was previously started from a non-MFA connection.)

Written on 25 October 2024.
« Having an emergency backup DNS resolver with systemd-resolved
The importance of name-based virtual hosts (websites) »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Fri Oct 25 22:40:27 2024
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.