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 by NERSC (see the link mentioned above for MFA instructions for NERSC), the line user=$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 the NERSC 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 for NERSC in the line user=$USER, i.e., replace $USER with the NERSC user name.