A little while ago, I "secured" root access on my GNU/Linux1 systems by stopping using the root password and migrating to sudo. I also setup Google Authenticator for it.

1 See Wikipedia's article on the topic for a summary of whether it should be "GNU/Linux" or "Linux".


My original implementation simply used pam_google_authenticator.so with default options, which meant that although sudo required a OTP (one-time passcode) the secret used to generate that (time-based) passcode was stored in a file called .google_authenticator in each user's home. With this secret anyone can start generating codes from it, which meant that if a user account with sudo access was compromised it was trivial for a bad-actor to then get root access. Obviously this was not good.

Doing it properly

Fortunately the PAM module supports some options which allow overriding of the location of the secrets file and the user to use when processing the authentication (so the files can be secured from the user being authenticated without doing this as root).

My first step was to create a user to use to do the authentication:

# useradd sudo-auth

Next I made a directory to store the credentials in and generated a secret. I need to create a file with a known name for each user:

# mkdir /var/lib/sudo-secrets
# chmod 700 /var/lib/sudo-secrets
# google-authenticator -t -d -r 3 -R 30 -w 3 -s /var/lib/sudo-secrets/my_username.google_authenticator
# chown -R sudo-auth /var/lib/sudo-secrets
The options to google-authenticator are:
  • -t: time based tokens
  • -d: do not allow passcode reuse
  • -r 3: rate limit to 3 attempts
  • -R 30: rate limit window set to 30s
  • -w 3: window size of 3 (allows 1 code either side the current one, so current time-based code +-1min)
  • -s /var/lib/sudo-secrets/my_username.google_authenticator: use this non-standard (the standard being ~/.google_authenticator) secret file location
google-authenticator creates secret files with 600 permissions, so there's no need to run chmod on that file after creation.

The final piece in this puzzle is tweaking the PAM configuration in /etc/pam.d/sudo to look in the right (secure) location for the tokens and use a user that has read access to this location:

auth sufficient pam_google_authenticator.so secret=/var/lib/sudo-secrets/${USER}.google_authenticator user=sudo-auth
# Fail if pam_google_authenticaor did not return success
auth requisite pam_deny.so