Today is May-20-2025. In Chinese, the pronunciation of ‘May-20’ is similar to ‘I love you’ so today is usually the day to express the love between boyfriend and girlfriend, wife and husband. Love you! My beloved Xin!
Nowadays, more and more web services are implementing the two-factor authentication (2FA, or MFA) by using the Time-Based One-Time Password (TOTP) for secure login. Usually, we input the normal password first and if the first step authentication passes, it will prompt for the input of the TOTP code. Initially when setting up the 2FA, the web service would provide us with the secret key through either a Uniform Resource Identifier (URI) or a QR code. On our mobile phone, we can then use an App like Google Authenticator or something similar to scan the QR code to sync with the web service. Once set up, the mobile app will generate a code periodically (typically, refresh every 30 seconds) and through the secret key as the connetion, the code generated by our mobile app will be syncing with the web service in the backend so that when the second-step input is asking for the 2FA code, we can put in the code generated by our mobile app so the web service can make sure it is really us who is trying to log in.
This is indeed a secure way of logging in through the two-layer of checking. However, in practice, it causes a bit pain especially when dealing with daily routines like logging in to a server through ssh, submitting running jobs, grabbing or uploading files. If we need to input the combination of password and the TOTP code every time, it is just a bit tedious. Here in this post, I am taking the login for the NERSC
supercomputer as the example, trying to set up a way of convennient yet secure way for connecting to the server. The instructions for setting up the MFA for NERSC
can be found here. There in the instruction, it was mentioned a script sshproxy.sh
which can be used to generate a ssh key pair that can be used for logging in the server using the key instead of the password so that we can save the effort of input the combination of password and the TOTP code. However, the generated key will only be valid for 24 hours, and therefore, we need to run the script from time to time when needed. When running the script, for sure, it will still ask the combination of the password and the TOTP code. This is not too bad a solution, but still it is not optimal.
To make life easier, we can use a Python module pyotp
to generate the TOTP code programmingly. Then, we can somehow generate the combination of the password and the TOTP code programmingly and feed the combination to the sshproxy.sh
script when asked. Here, I am doing it locally where I just explicitly put in my password in my Python script in which the TOTP will also be generated. Then I just print out the password-TOTP combo and I modified the sshproxy.sh
script to feed the printed combo into the script. This way, I can then set up a cronjob to run the sshproxy.sh
script once or twice a day so that every day I have an automatically generated key pair for ssh login to NERSC
. Here is my Python script,
import pyotp
passw = "....."
totp = pyotp.TOTP('*****')
code = totp.now()
print(f"{passw}{code}")
where I wiped out the password and the secret key for syncing with NERSC
. To run the script, we need to have pyotp
installed via pip install pyotp
. To obtain the secret key, we can log in the NERSC
web management platform here. Then we click on MFA
from the top menu items and then click on Add Token
. Giving it a name and then we will see the QR code together with the generated URI in the following form,
otpauth://totp/LinOTP:.....?secret=*****&issuer=LinOTP
The *****
part in the URI is just the secret key which we can grab and put into the Python script to replace the *****
there. Then we want to look for the following bit of codes in the sshproxy.sh
script,
read -r -p "Enter the password+OTP for ${user}: " -s pw
and replace the line with the following lines,
code_tmp=$(/Users/y8z/anaconda3/bin/python /Users/y8z/Packages/nersc_code_gen.py)
read -r -p "Enter the password+OTP for ${user}: " -s pw <<EOF
${code_tmp}
EOF
where we need to replace the full path to the Python executable /Users/y8z/anaconda3/bin/python
with whichever Python version that has pyotp
installed. Also, we need to replace the full path /Users/y8z/Packages/nersc_code_gen.py
to where the Python script above is saved.
Finally, we can set up cronjobs to run the sshproxy.sh
script automatically, e.g.,
0 9 * * * /Users/y8z/.ssh/sshproxy.sh
0 17 * * * /Users/y8z/.ssh/sshproxy.sh
which will run the script on 9:00 am and 5:00 pm every day. By default, the sshproxy.sh
script will generate the key pair with the name of nersc
(the private key name) under ~/.ssh
so that we can use the private key ~/.ssh/nersc
for ssh into NERSC
or tranferring files with rsync
using the key.
By default, in the top part of the provided
sshproxy.sh
script byNERSC
(see the link mentioned above forMFA
instructions forNERSC
), the lineuser=$USER
would be there. The$USER
variable refers to the local user name who is running the script. The script is assuming that the local user name is the same as theNERSC
user name, which may not be the case, especially when executing the script from the cronjob. In that case, we have to explicitly specify the user name forNERSC
in the lineuser=$USER
, i.e., replace$USER
with theNERSC
user name.