Securing your server with 2FA
I have a bunch of servers running within the company network. These server running multiple
virtual machines such that these servers can be seen as a hypervisor. I connect to them
using ssh
and for additional security I use a second factor. 2FA or two-factor authentification
can be used to improve the security by forcing the user not only provide the correct ssh key but
also another secret shared by the machine and the user. This corrects the case that a
ssh key of a users gets compromised. On the backend I use the PAM module Google Authenticator
which generates tokens via the TOTP protocoll. This is also called OATH-TOTP which stands for
Open Authentication - Time-Based One-Time Password. Initially the server and the user
exchanges a shared salt which should be unknown to all others. Afterwards, the user can generate
a 6 digit token based on that salt and the current time. The servers has the same informations
and generates the same token for validation.
Installation
First we install an app which supports TOTP on another device (maybe your smartphone). I use FreeOTP which I installed from the F-Droid store. The interface is fairly simple and should be self-explaining. We use QR codes for the initial exchange but one can enter all informations by hand.
On the server-side we install the PAM module with:
1apt install libpam-google-authenticator
2# Run the setup
3google-authenticator
4# Question time
5Do you want authentication tokens to be time-based (y/n) y
6# The initial qr code is generated, scan it with your new app and save the informations in a safe place
7#
8# Write the secret informations into your user directory or nothing works
9Do you want me to update your "/<user>/.google_authenticator" file? (y/n) y
10# Prevent man-in-the-middle attacks
11Do you want to disallow multiple uses of the same authentication
12token? This restricts you to one login about every 30s, but it increases
13your chances to notice or even prevent man-in-the-middle attacks (y/n) y
14# We only use three codes (the last, the current and the next one)
15Do you want to do so? (y/n) n
16# Enable rate-limiting for authentication
17Do you want to enable rate-limiting (y/n) y
Configuration
After the question time the PAM module is initialised and preconfigured but not active.
Now, we enable 2FA for ssh
by changing the file mg /etc/pam.d/sshd
and adding following the line on the top.
1auth required pam_google_authenticator.so nullok [echo_verification_code] [authtok_prompt=2F-Token: ]
2# Comment out the normal login mechanics
3#@include common-auth
auth required pam_google_authenticator.so
triggers that these line must be matched to grant access.nullok
means that logins without 2FA are accepted. This can be changed if all users have 2FA enabled.[authtok_prompt=2F-Token: ]
is the prompt used to question the user for 2FA.
Now we make ssh
aware of our 2FA setup by changing the following lines in the configuration (mg /etc/ssh/sshd_config
):
1# Enable challange response keys
2ChallengeResponseAuthentication yes
3# Enable pam aware login
4PAM yes
5AuthenticationMethods publickey,keyboard-interactive:pam
Reload your ssh
daemon with systemctl reload sshd
. Now you need to use 2FA for your next login.
If you want that new users can setup 2FA on the first login you can use a script which generates
the ~/.google_authenticator
file.
1# Open the profile file
2mg /etc/profile
3# Add the following lines at the bottom of the file
4test "$(id -u)" -eq 0 || test -e ~/.google_authenticator || {
5 read -p "Do you want to setup 2FA now? [Y/n]" SETUP
6 test "$SETUP" = "n" || {
7 google-authenticator -t --rate-limit=3 --rate-time=30 --window-size=17 --force --disallow-reuse
8 }
9}
For automatic creation of 2FA for new users you need the following line in the pam.d/sshd
at the end of the file.
auth required pam_permit.so
This piece of code and the nullok
allow users to bypass the 2FA forever since you can login without the setup.
So, encourage your users to use 2FA and change booth lines later to only allow 2FA. Otherwise, you can
use authorized users or groups lists within PAM to get a more secure setup.
Be aware, that you should backup your 2FA secrets in case you lost your phone or it gets damaged!
Backup TOPT tokens
To backup my tokens I use emacs and followed this guide. It uses a big table to represent all the fields needed to generate the tokens and QR codes.
The table:
1#+TBLNAME: otp-tokens
2| Issuer | Label | Secret | URI |
3|--------+------------------------+------------------+------------------------------------------------------------------------------------|
4| Google | fake.address@gmail.com | fakesecret123456 | otpauth://totp/Google:fake.address@gmail.com?secret=fakesecret123456&issuer=Google |
5#+TBLFM: $4='(concat "otpauth://totp/" (if (string-blank-p $1) "" (concat $1 ":")) $2 "?" (url-build-query-string `(("secret" $3) ("issuer" $1))))
The explaination:
- Issuer: Name of the service or machine
- Label: The account identifier
- Secret: The base32 encoded shared secret
- URI: The configuration content of the QR code. This is created by the
TBLFM
You can either fill the informations into the table from the .google_authenticator
file and
your own knowledge or use a script which fetches the informations from FreeOTP.
Afterwards, put your curser somewhere on the table and run the table function with C-c C-c
.
To generate a nice QR code use python and the tool qrencode
:
1#+name: qrcodes
2#+begin_src python :var tokens=otp-tokens :results output
3import subprocess
4for issuer, label, secret, image, uri in tokens:
5 print("%s (%s)\n" % (label, issuer if len(issuer) > 0 else "n/a"))
6 code = subprocess.check_output(["qrencode", "-t", "UTF8", uri])
7 print(code.decode("utf-8"))
8 print("\n"*30)
9#+end_src
Running this with C-c C-c
generates a result block with the QR code in it. Now, you can simply
scan the code again. If babel complains about python, make sure that python
is within the org-babel-load-languages
list.
The original author provided the following piece of python to fetch the secrets directly from the FreeOTP app.
The secret and the additional informations shall be copied into the table.
I didn’t tried it but would mention it. You need developer mode active on your phone and maybe the cmd
path needs
to be adjusted.
1#+name: free_otp
2#+begin_src python :results output
3from xml.etree import ElementTree as ET
4import base64, json, subprocess, sys
5
6cmd = ["adb", "shell", 'su -c "cat /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml"']
7ret = subprocess.run(cmd, stdout=subprocess.PIPE, check=True)
8root = ET.fromstring(ret.stdout)
9for child in root:
10 name = child.attrib['name']
11 if child.attrib['name'] != 'tokenOrder':
12 data = json.loads(child.text)['secret']
13 data = ''.join('%02x' % (x % 256) for x in data)
14 secret = base64.b32encode(bytes.fromhex(data)).decode('utf-8').rstrip('=').lower()
15 print(name, ' ' * (40 - len(name)), secret)
16#+end_src
Thanks for your attention and leave me a comment.