So, I recently (on Monday, 11/20/17) started at a new job! Aside from all the new stuff that implies, and that is much more important overall, I received a Yubikey Neo key to be used for two-factor authentication on various internal services. I found this pretty cool, started using it, and thought no more of it.
Then the next day, I tried to SSH to my server for some reason and found out that I could obviously not log in since I didn’t have my key. So I thought for a minute, didn’t find any way to enter my infrastructure when I don’t have my key, and took note to do whatever I was planning that evening.
After a bit of time I put two and two together and thought I could probably try to add 2fa (TOTP, Time-based One Time Password + username) as an alternative authentication method to RSA public key. So I looked a bit and ended up setting that up. Here’s how it works.
So as you might know, authentication on Linux is handled by PAM (Pluggable
Authentication Modules). As the name implies, it’s made of modules that you can
simply plug onto the existing authentication mechanism. And thankfully, someone
developped a TOTP module for PAM, which is named libpam_google_authenticator
(which is not a very good name, but I’m not gonna complain if I can get it
working). So the plan is to install that module (pretty easy, since it’s
packaged by most linux distros), then configure ssh to use it to auth users.
That’s done by editing the /etc/pam.d/sshd
file, and setting it up like so:
# PAM configuration for the Secure Shell service ### This is the important part. It adds the module and marks it as a required ### authentication method. ## auth required pam_google_authenticator.so ## @include common-auth # Disallow non-root logins when /etc/nologin exists. account required pam_nologin.so # Uncomment and edit /etc/security/access.conf if you need to set complex # access limits that are hard to express in sshd_config. # account required pam_access.so # Standard Un*x authorization. @include common-account # SELinux needs to be the first session rule. This ensures that any # lingering context has been cleared. Without this it is possible that a # module could execute code in the wrong domain. session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close # ... nothing else interesting until end of file
Now that this is done, we need to change sshd’s config, to allow it to present
the prompt for the TOTP code and tell it to fallback to keyboard-interactive if
publickey auth doesn’t work. To do that, we add these lines to
/etc/ssh/sshd_config
:
UsePam yes ChallengeResponseAuthentication yes AuthenticationMethods publickey keyboard-interactive:pam
Be careful, this will allow users to login with TOTP+pass even if
PasswordAuthentication
is set to no
Once this is done, we need to generate a secret for the TOTP generator.
libpam_google_authenticator
has a tool for this named …
google-authenticator
. Once you run it, it’ll ask a few questions, display
a QR-code on the screen, give you a private key and a few recovery codes, and
write all this into a file named ~/.google_authenticator
. Scan the QR code or
add the private key into your TOTP client (an app on your phone, a linux CLI
client, whatever. I use OTP Auth on iOS.)
Once that’s done, you can try to use that authentication by running ssh -o
PubkeyAuthentication=no whatever.space
. It should ask you for your
“Verification Code”, which is the TOTP, and then your password, and then let you
log in. Done, you have 2fa on SSH now 😄
Now for the puppet setup:
package {'libpam-google-authenticator': ensure => present, } file {'/etc/pam.d/sshd': ensure => present, content => file('base/pam/sshd'), owner => 'root', group => 'root', } file {'/home/wxcafe/.google_authenticator': ensure => present, content => file(modules/base/google_authenticator, owner => 'wxcafe', group => 'wxcafe', mode => '0600', } file {'/etc/ssh/sshd_config': ensure => present, content => file('base/ssh/sshd_config'), }
Yeah, I know, this is pretty generic. It was a bit harder for me because I use
a git repo for my dotfiles, and as such I have a git
block and an exec
block
to chmod 600 the .google_authenticator file, and I had a bit of trouble trying
to use a file
block inside another file
block (setting the mode of the file
inside dotfiles git repo) (if you’re wondering, puppet simply ignores the
least-specific block here… I spent a while wondering why my files wouldn’t
copy…)
Either way it should work alright. You might also notice that I just deploy the .google_authenticator file on every machine, and think that’s not a very good security practice. I think it’s alright, a TOTP is basically identical security-wise to 10 TOTPs, as long as the key doesn’t leak, and the increased usability of not having to keep tens of TOTP codes on my phone is clearly worth it.