0

Similar to Configure pam_mount to only prompt at initial login, but the recommended answer does not work on RHEL 9 (and apparently didn't work for the person who originally asked the question).

I need to set up DUO two-factor authentication on Red Hat Enterprise Linux 9. Following the instructions on the vendor's website results in a setup that requires answering a 2FA challenge every time the user enters a password, e.g. for sudo. I would like to modify this setup to prompt for a 2FA challenge only on the initial login.

I added the following to /etc/authselect/password-auth after the line containing pam_unix.so:

auth    [success=1 default=ignore]    pam_succeed_if.so service = systemd-user quiet
auth    sufficient                    pam_duo.so

The pam_succeed_if condition was suggested both on the linked question and on the Arch wiki (itself linked in that question). However, the pam_succeed_if condition fails every time, and now every time the system is unlocked, DUO prompts for 2FA. How can I reliably skip DUO if the user has already logged in?

zaen
  • 204
  • 2
  • 10

2 Answers2

0

If you wish the 2FA challenge to happen only on initial login, then you must refer to pam_duo.so only with the services that handle initial logins.

Out of abundance of caution, you might instead wish to explicitly list the services that are allowed to skip the 2FA, so you won't accidentally leave a 2FA-free "hole" on a service you did not realize needs 2FA after all.

The line

auth    [success=1 default=ignore]    pam_succeed_if.so service = systemd-user quiet

means: "If the condition "service = systemd-user" matches, skip the next auth configuration line, otherwise proceed as if this line did not exist at all."

To provide a similar skip for two services (e.g. systemd-user and sudo), you could do this:

auth    [success=2 default=ignore]    pam_succeed_if.so service = sudo quiet
auth    [success=1 default=ignore]    pam_succeed_if.so service = systemd-user quiet
auth    sufficient                    pam_duo.so

If the first line matches, we already know the service requesting authentication was sudo, so we don't have to check if it was systemd-user. So we skip two lines: the systemd-user check and the pam_duo.so authentication module.

If you want to allow 2FA-less su also (just as an example), you would add a third line:

auth    [success=3 default=ignore]    pam_succeed_if.so service = su quiet
auth    [success=2 default=ignore]    pam_succeed_if.so service = sudo quiet
auth    [success=1 default=ignore]    pam_succeed_if.so service = systemd-user quiet
auth    sufficient                    pam_duo.so

And so on.

As always, when modifying PAM configuration, it is wise to not log out after you've tested your changes: if you are making the change over a SSH connection, open a second SSH connection for testing instead of logging out of the first one. If you are making the change locally, open a second terminal window and become root in there, or switch to a different virtual console and login in text mode likewise. You'll want a session that already is logged in with full root privileges ready to undo your changes in case it turns out you made a critical mistake.

On production systems, I personally would first log in a second SSH connection and switch it to root before making PAM changes, just to guard against accidental "fingers-faster-than-brain" logouts.

telcoM
  • 87,318
  • 3
  • 112
  • 232
  • Re: "you must refer to pam_duo.so only with the services that handle initial logins" does this mean the configuration needs to be done in a different PAM file than `/etc/authselect/password-auth`? Otherwise I don't see how this is substantially different from what I've already attempted. – zaen Oct 19 '22 at 21:14
0

I was able to get this to work using information from a few other answers. First, it's not possible to differentiate between login and unlocking using only PAM modules, at least on GNOME, as per this answer. The other answers to that question suggest using pam_script as a workaround, but I wasn't crazy about using somebody's github project in my PAM stack. Fortunately, the built-in pam_exec will do the same job.

First, modify the script from this answer for use with pam_exec:

#!/bin/sh

SESSION_ID=$(loginctl session-status | head -1 | cut -d' ' -f1)

if [ -z "$SESSION_ID" ]; then
    exit 1
fi
if ! loginctl show-session "$SESSION_ID" | grep '^LockedHint=yes$' > /dev/null; then
    exit 1
fi

# Current session is locked, return success
exit 0

Save it in /etc/pam.d/is-current-session-locked or wherever you want, as long as the path passed to pam_exec is modified accordingly. Be sure to make the script executable.

The use of the command stored in SESSION_ID rather than XDG_SESSION_ID is because GNOME (at least on RHEL 9) doesn't store anything to XDG_SESSION_ID. The return values are changed to exit values because pam_exec checks the exit value of a command, and the exit values themselves are dependent on what your PAM stack logic is.

Next, I added the following line before the line for pam_duo.so:

auth    sufficient    pam_exec.so quiet /etc/pam.d/is-current-session-locked/pam_script_auth.sh

Since pam_duo is also sufficient, nothing after it is processed anyway.

And of course, as mentioned in the other answer, it's a good idea to be logged in and have a root shell in another session. I locked myself out a few times while figuring this out, but because I had the root shell open I could just switch to that session and fix the PAM file to be able to unlock the graphical session.

zaen
  • 204
  • 2
  • 10