User-selectable authentication methods using pam_authtok
Research Computing is in the process of migrating and expanding our authentication system to support additional authentication methods. Historically we’ve supported VASCO IDENTIKEY time-based one-time-password and pin to provide two-factor authentication.
$ ssh user1234@login.rc.colorado.edu user1234@login.rc.colorado.edu's password: <pin><otp> [user1234@login04 ~]$
But the VASCO tokens are expensive, get lost or left at home, have a battery that runs out, and have an internal clock that sometimes falls out-of-sync with the rest of the authentication system. For these and other reasons we’re provisioning most new account with Duo, which provides iOS and Android apps but also supports SMS and voice calls.
Unlike VASCO, Duo is only a single authentication factor; so we’ve also added support for upstream CU-Boulder campus password authentication to be used in tandem.
This means that we have to support both authentication mechanisms–VASCO and password+Duo–simultaneously. A naïve implementation might just stack these methods together.
auth sufficient pam_radius_auth.so try_first_pass # VASCO authenticates over RADIUS auth requisite pam_krb5.so try_first_pass # CU-Boulder campus password auth required pam_duo.so
This generally works: VASCO authentication is attempted first over RADIUS. If that fails, authentication is attempted against the campus password and, if that succeeds, against Duo.
Unfortunately, this generates spurious authentication failures in VASCO when using Duo to authenticate: the VASCO method fails, then Duo authentication is attempted. Users who have both VASCO and Duo accounts (e.g., all administrators) may generate enough failures to trigger the break-in mitigation security system, and the VASCO account may be disabled. This same issue exists if we reverse the authentication order to try Duo first, then VASCO: VASCO users might then cause their campus passwords to become disabled.
In stead, we need to enable users to explicitly specify which authentication method they’re using.
Separate sssd domains
Our first attempt to provide explicit access to different authentication methods was to provide multiple redundant sssd domains.
[domain/rc] description = Research Computing proxy_pam_target = curc-twofactor-vasco [domain/duo] description = Research Computing (identikey+duo authentication) enumerate = false proxy_pam_target = curc-twofactor-duo
This allows users to log in normally using VASCO, while password+Duo
authentication can be requested explicitly by logging in as
${user}@duo
.
$ ssh -l user1234@duo login.rc.colorado.edu
This works well enough for the common case of shell access over SSH:
login is permitted and, since both the default rc
domain and the
duo
alias domain are both backed by the same LDAP directory, NSS
sees no important difference once a user is logged in using either
method.
This works because POSIX systems store the uid number returned by
PAM and
NSS, and
generally resolve the uid number to the username on-demand. Not all
systems work this way, however. For example, when we attempted to use
this authentication mechanism to authenticate to our prototype
JupyterHub (web) service, jobs
dispatched to Slurm retained the
${user}@duo
username format. Slurm also uses usernames internally,
and the ${user}@duo
username is not populated within Slurm: only the
base ${user}
username.
Expecting that we would continue to find more unexpected side-effects of this implementation, we started to look for an alternative mechanism that doesn’t modify the specified username.
pam_authtok
In general, a user provides two pieces of information during authentication: a username (which we’ve already determined we shouldn’t modify) and an authentication token or password. We should be able to detect, for example, a prefix to that authentication token to determine what authentication method to use.
$ ssh user1234@login.rc.colorado.edu user1234@login.rc.colorado.edu's password: duo:<password> [user1234@login04 ~]$
But we found no such pam module that would allow us to manipulate the authentication token… so we wrote one.
auth [success=1 default=ignore] pam_authtok.so prefix=duo: strip prompt=password: auth [success=done new_authtok_reqd=done default=die] pam_radius_auth.so try_first_pass auth requisite pam_krb5.so try_first_pass auth [success=done new_authtok_reqd=done default=die] pam_duo.so
Now our PAM stack authenticates against VASCO by default; but, if the
user provides a password with a duo:
prefix, authentication skips
VASCO and authenticates the supplied password, followed by Duo push. Our
actual production PAM stack is a bit more complicated, supporting a
redundant vasco:
prefix as well, for forward-compatibility should we
change the default authentication mechanism in the future. We can also
extend this mechanism to add arbitrary additional authentication
mechanisms in the future.