Create a user for logging into our OpenVPN server
First, we create a user for our OpenVPN connections. Even though we give it a long-secret password, this is never used for authentication, it’s just there to prevent brute force attacks via SSH or the web interface. Later, we will change the user from ‘administrator’ to ‘operator’.
configure set system login user openvpn-user authentication plaintext-password superlongsecretpassword=5 set system login user openvpn-user level admin commit save exit
Install the libqrencode3 package (not strictly essential, but it makes things much easier later) and easy-rsa (used later to create certificates):
sudo apt-get update sudo apt-get install libqrencode3 easy-rsa
For EdgeOS < 2.0 (Jessie), Unfortunately, Google Authenticator hasn’t been packaged for Debian wheezy, but the jessie package works just fine for our purposes. (Alternatively, you can compile it from source, but I didn’t bother as the jessie package just works and doesn’t have any non-wheezy dependencies…) Grab and install the jessie package like this:
cd ~ mkdir ./downloaded-packages cd downloaded-packages curl -O http://ftp.us.debian.org/debian/pool/main/g/google-authenticator/libpam-google-authenticator_20170702-1_mipsel.deb dpkg -i libpam-google-authenticator_20170702-1_mipsel.deb
For EdgeOS >= 2.0 (Stretch), install Google Authenticator
apt-get -y install libpam-google-authenticator
Create a new PAM config for our OpenVPN server(s) to use:
cd /etc/pam.d cp common-account openvpn
Now edit the new config using nano:
nano openvpn
You now need to ask yourself if you want 2-factor or 3-factor authentication… i.e. do you want to have to enter username and a six-digit TOTP code when prompted, or do you want to have to enter username, password AND the six-digit TOTP code?
Personally, I tried both and found that entering username and a strong password as well as the TOTP quickly became very tiresome. Also, for me, the combination of PKI and TOTP is very much “secure enough”. But you might feel differently, so I’m giving both options below…
(a) To require only username and TOTP to authenticate, add the following to the end of the openvpn file:
auth required pam_google_authenticator.so
(b) Alternatively, to require username and password+TOTP (both in the password field, e.g. password123456) to authenticate, add the following to the end of the openvpn file:
auth requisite pam_google_authenticator.so forward_pass auth required pam_unix.so use_first_pass
Let’s create our OTP code for our new user. Be sure to adjust the command below so that the username (at the end of the line) matches the real user you’ve set up earlier, and the label text between ‘\”‘ and ‘\”‘ is whatever you want to appear next to the passcode in your authenticator app (remember you might eventually have lots of other Google Authenticator entries with their own secret codes – there are loads of services offering 2-factor authentication now – so give it a sensible unique name):
sudo su -c "google-authenticator --label=\"%LOCATION% OpenVPN openvpn-user\"" openvpn-user
Answer ‘y’ to the first question, and if you installed the libqrencode3 package earlier, you’ll be presented with a nice QR code as ASCII art in your terminal window – neat, eh? (I had to resize the terminal window on my Macbook so that the QR code was displayed properly).
If you did not install the extra package, then you can copy’n’paste the URL provided by the google-authenticator executable into any browser and Google’s web service will provide an image of the relevant QR code for you.
Now open Google Authenticator app (or your OTP app of choice) and press the ‘+’ button. Choose the option ‘Scan a barcode’ and then scan the QR code. The app will then start displaying a unique six digit one-time password every 30 seconds (hence the importance of your server and clients all having accurate time set on them!).
Back in our SSH session, answer the rest of the questions as follows (if you don’t have faith in your time synchronization working properly, you might want to answer ‘y’ to the third question):
Do you want me to update your "/home/openvpn-user/.google_authenticator" file (y/n) y
Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y
By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n
If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
Now change rights on the newly-created Google Authenticator config for your user, so that only the user is allowed only read permissions, like this:
sudo chmod 400 /home/openvpn-user/.google_authenticator
Repeat the above steps to run the google-authenticator executable for each user that you created earlier.
Setup OpenVPN Server Vars and Certificates
Our first job is to make a directory where all the certificates will be. It’s important to save it under /config, because this directory will survive a firmware update. All certificate commands are run with /config/openvpn as the current directory.
sudo -i mkdir /config/openvpn mkdir /config/openvpn/keys cd /config/openvpn/keys touch index.txt echo 00 > serial
Make a copy of the example easy-rsa vars file, to customize for our needs (note the space between vars and ./):
cd /config/openvpn #cp -p /usr/share/easy-rsa/vars ./ #we aren't going to copy the easy-rsa vars file. instead create a new file and paste in the contents below... nano ./vars
New ./vars file contents:
# easy-rsa parameter settings
# NOTE: If you installed from an RPM,
# don't edit this file in place in
# /usr/share/openvpn/easy-rsa --
# instead, you should copy the whole
# easy-rsa directory to another location
# (such as /etc/openvpn) so that your
# edits will not be wiped out by a future
# OpenVPN package upgrade.
# This variable should point to
# the top level of the easy-rsa
# tree.
export EASY_RSA="/usr/share/easy-rsa"
# This variable should point to
# the requested executables
export OPENSSL="openssl"
export PKCS11TOOL="pkcs11-tool"
export GREP="grep"
# This variable should point to
# the openssl.cnf file included
# with easy-rsa. whichopensslcnf is
# a script that will output the current
# openssl.cnf
export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
# Edit this variable to point to
# your soon-to-be-created key
# directory.
# WARNING: clean-all will do
# a rm -rf on this directory
# so make sure you define
# it correctly!
export KEY_DIR="/config/openvpn/keys"
# Issue rm -rf warning
echo NOTE: If you run ./clean-all, I will be doing a rm -rf on $KEY_DIR
# PKCS11 fixes
#export PKCS11_MODULE_PATH="dummy"
#export PKCS11_PIN="dummy"
# Increase this to 2048 if you
# are paranoid. This will slow
# down TLS negotiation performance
# as well as the one-time DH parms
# generation process.
export KEY_SIZE=2048
# In how many days should the root CA key expire?
export CA_EXPIRE=3650
# In how many days should certificates expire?
export KEY_EXPIRE=3650
# These are the default values for fields
# which will be placed in the certificate.
# Don't leave any of these fields blank.
export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="SanFrancisco"
export KEY_ORG="Fort-Funston"
export KEY_EMAIL="**@****st.mydomain"
export KEY_OU="MyOrganizationalUnit"
export KEY_ALTNAMES="somethingFunHere"
# X509 Subject Field
export KEY_NAME="EdgeOSMyOrg"
# PKCS11 Smart Card
# export PKCS11_MODULE_PATH="/usr/lib/changeme.so"
# export PKCS11_PIN=1234
# If you'd like to sign all keys with the same Common Name, uncomment the KEY_CN export below
# You will also need to make sure your OpenVPN server config has the duplicate-cn option set
# export KEY_CN="CommonName"
Change to the Easy-RSA folder and use the scripts to create a Certificate Authority (CA) and generate keys and certificates for each client:
cd /usr/share/easy-rsa # note there needs to be a space after the dot on this next line: . /config/openvpn/vars # cleans all in /config/openvpn/keys previously generated keys and certificates # if you are following this guide, the /keys directory is already empty. ./clean-all # takes some time, only about 5-10 minutes I think when i made a 1024 key. # takes about an hour for a 2048 key. ./build-dh # Didn't have openssl.cfn in the /usr/share/easy-rsa so needed to copy /usr/share/easy-rsa/openssl-1.0.0.cnf to /usr/share/easy-rsa/openssl.cnf cp openssl-1.0.0.cnf openssl.cnf # Edit the end of the openssl.cnf file and comment out these 2 lines. It was causing errors when revoking certificates. nano/usr/share/easy-rsa/openssl.cnf #MODULE_PATH = $ENV::PKCS11_MODULE_PATH #PIN = $ENV::PKCS11_PIN # most steps in the following will use your pre-populated values (from your # edited 'vars' file). For Common Name, I used 'OpenVPN-CA' ./build-ca # Assuming you edited the information in the vars files simply hit enter all # the way through this. Otherwise, enter your desired values. # As in the previous step, most parameters can be defaulted by pressing enter. # When the Common Name is queried, enter "server". # Don't set a challenge password. # Two other queries require positive responses, "Sign the certificate? [y/n]" # and "1 out of 1 certificate requests certified, commit? [y/n]" - answer # 'y' for both. ./build-key-server server
Creating a .p12 file (last step above) is not strictly required, but it provides extra security because it enables the private key to be stored in the Apple Keychain (which has hardware-backed crypto). To make sure your keychain is safe, need to have a strong device passcode set – not just a simple 4 digit PIN – of course.
Configure OpenVPN Server
Begin by setting some of the VPN config. Be sure to change the green text to match your router config.
set interfaces openvpn vtun0
set interfaces openvpn vtun0 description "OpenVPN server"
set interfaces openvpn vtun0 mode server
set interfaces openvpn vtun0 encryption aes256
set interfaces openvpn vtun0 hash sha256
set interfaces openvpn vtun0 server subnet 10.8.1.0/24
set interfaces openvpn vtun0 server push-route 10.1.1.0/24 #this is your primary LAN
set interfaces openvpn vtun0 server name-server 10.8.1.1
set interfaces openvpn vtun0 openvpn-option "--port 1194"
set interfaces openvpn vtun0 openvpn-option --tls-server
set interfaces openvpn vtun0 openvpn-option "--comp-lzo yes"
set interfaces openvpn vtun0 openvpn-option --persist-key
set interfaces openvpn vtun0 openvpn-option --persist-tun
set interfaces openvpn vtun0 openvpn-option "--keepalive 10 120"
set interfaces openvpn vtun0 openvpn-option "--user nobody"
set interfaces openvpn vtun0 openvpn-option "--group nogroup"
set interfaces openvpn vtun1 openvpn-option "--reneg-sec 28800"
set interfaces openvpn vtun0 openvpn-option '--plugin /usr/lib/openvpn/openvpn-auth-pam.so openvpn'
# certificates
set interfaces openvpn vtun0 tls ca-cert-file /config/openvpn/keys/ca.crt
set interfaces openvpn vtun0 tls cert-file /config/openvpn/keys/server.crt
set interfaces openvpn vtun0 tls key-file /config/openvpn/keys/server.key
set interfaces openvpn vtun0 tls dh-file /config/openvpn/keys/dh2048.pem
set interfaces openvpn vtun0 openvpn-option '--tls-auth /config/openvpn/keys/ta.key 0'
commit
Now we need to punch a hole in our WAN interface to allow incoming OpenVPN traffic.
set firewall name WAN_LOCAL rule 20 action accept set firewall name WAN_LOCAL rule 20 description 'Allow OpenVPN UDP' set firewall name WAN_LOCAL rule 20 destination port 1194 set firewall name WAN_LOCAL rule 20 protocol udp
We also need to listen to DNS queries on the OpenVPN interface
set service dns forwarding listen-on vtun0
Create the User Certs.
Now let us create the User certificates and OVPN file. Create a script and name it ‘CreateUserCertOVPN.sh’
cd /config/openvpn/ nano CreateUserCertOVPN.sh
Copy and paste this in:
#!/bin/bash # Run this script from /config/openvpn # if [ "$1" = "" ] || [ "$2" = "" ]; then printf "Please pass in the username like UserCertAndOVPN.sh username serverPath\n\n" exit 0 fi #----------------------------------------------------------------------------------------------- # Edit this setup before running. Make sure CAname and ServerName are the same between # the two scripts ClientName="$1" ServerPath="$2" KeyPath="/config/openvpn/keys" #----------------------------------------------------------------------------------------------- # Functions function makeCert () { ClientName=$1 printf "\nGeneration certificate for ${ClientName}...\n\n" cd /usr/share/easy-rsa . /config/openvpn/vars ./build-key ${ClientName} printf "\nGeneration p12 certificate for ${ClientName}...\n\n" openssl pkcs12 -export -in /config/openvpn/keys/${ClientName}.crt -inkey /config/openvpn/keys/${ClientName}.key -certfile /config/openvpn/keys/ca.crt -name OVPNclient -out /config/openvpn/keys/${ClientName}.p12 } function makeOvpnFile () { ClientName=$1 ServerPath=$2 KeyPath=$3 printf "\nGenerating .ovpn file ${ClientName}.ovpn...\n\n" cd ${KeyPath} echo client > ${ClientName}.ovpn echo dev tun >> ${ClientName}.ovpn echo proto udp >> ${ClientName}.ovpn echo remote ${ServerPath} 1194 >> ${ClientName}.ovpn echo cipher AES-256-CBC >> ${ClientName}.ovpn echo auth SHA256 >> ${ClientName}.ovpn echo resolv-retry infinite >> ${ClientName}.ovpn echo redirect-gateway def1 >> ${ClientName}.ovpn echo nobind >> ${ClientName}.ovpn echo comp-lzo yes >> ${ClientName}.ovpn echo persist-key >> ${ClientName}.ovpn echo persist-tun >> ${ClientName}.ovpn echo user nobody >> ${ClientName}.ovpn echo group nogroup >> ${ClientName}.ovpn echo verb 3 >> ${ClientName}.ovpn echo setenv ALLOW_PASSWORD_SAVE 0 >> ${ClientName}.ovpn echo auth-user-pass >> ${ClientName}.ovpn echo reneg-sec 28800 >> ${ClientName}.ovpn echo key-direction 1 >> ${ClientName}.ovpn echo '<tls-auth>' >> ${ClientName}.ovpn cat ${KeyPath}/ta.key >> ${ClientName}.ovpn echo '</tls-auth>' >> ${ClientName}.ovpn echo '<ca>' >> ${ClientName}.ovpn cat ${KeyPath}/ca.crt >> ${ClientName}.ovpn echo '</ca>' >> ${ClientName}.ovpn echo '<cert>' >> ${ClientName}.ovpn cat ${KeyPath}/${ClientName}.crt | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' >> ${ClientName}.ovpn echo '</cert>' >> ${ClientName}.ovpn echo '<key>' >> ${ClientName}.ovpn cat ${KeyPath}/${ClientName}.key >> ${ClientName}.ovpn echo '</key>' >> ${ClientName}.ovpn printf "\n ---------------- Beginning of OVPN file ---------------- \n\n" cat ${ClientName}.ovpn printf "\n ---------------- End of OVPN file ---------------- \n\n" cd ${KeyPath} ls -al ${ClientName}.* } #----------------------------------------------------------------------------------------------- # Make everything makeCert ${ClientName} makeOvpnFile ${ClientName} ${ServerPath} ${KeyPath}
The above script will generate the user certificates and create a user.ovpn
CHMOD it so we can execute it
chmod 655 ./CreateUserCertOVPN.sh
Execute the script and pass in the username and server path
./CreateUserCertOVPN.sh openvpn-user vpn.myserverpath.com
Copy that .ovpn file to your client.
Once we’re all done with adding the user, let’s set that person as an “Operator”
configure
set system login user openvpn-user level operator
commit
To revoke a user:
sudo -i cd /usr/share/easy-rsa . /config/openvpn/vars ./revoke-full openvpn-user # if you see "Error 23 : certificate revoked" run the following lines rm /config/openvpn/keys/openvpn-user.*