Setup OpenVPN + 2fa

The way it works is that openvpn besides only allowing guest with a valid certificate it will prompt users for a username and password in where username is a system user and password is the OTP generated by the google authenticator.

First you need to install google-authenticator:

pkg install pam_google_authenticator

Next create the file /etc/pam.d/openvpn with this contents:

auth            required        /usr/local/lib/pam_google_authenticator.so

The openvpn configuration should look like this:

# ------------------------------------------------------------------------------
# routed VPN
# ------------------------------------------------------------------------------
port 1194
proto udp
dev tun
topology subnet
server 172.16.13.0 255.255.255.0
tun-mtu 1400

ca keys/ca.crt
cert keys/server.crt
key keys/server.key
dh keys/dh2048.pem
tls-auth keys/ta.key 0
key-direction 0

push "dhcp-option DNS 4.2.2.2"
push "dhcp-option DNS 8.8.8.8"
push "redirect-gateway def1"

client-config-dir ccd
plugin /usr/local/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
duplicate-cn

cipher AES-256-CBC
auth SHA512

comp-lzo
max-clients 100
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
verb 4
keepalive 20 40

# certificate revoking list
# crl-verify keys/crl.pem

Notice the line:

plugin /usr/local/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn

The client side should be something like this:

client
dev tun
proto udp
remote X.X.X.X 1194
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo
key-direction 1
cipher AES-256-CBC
auth SHA512
verb 3
auth-user-pass
auth-nocache
<ca>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
-----END OpenVPN Static key V1-----
</tls-auth>

Notice the:

auth-user-pass
auth-nocache

🔗Add users (create & sign certs)

First create user certificate by doing something like (this can be created by user and the admin could only sign the cert):

openssl req -new -newkey rsa:2048 -nodes -out guest.csr -keyout guest.key -subj "/C=DE/ST=Germany/L=Berlin/O=acme/OU=IT/CN=guest"

With the server key you need to sign the guest.csr:

openssl x509 -req -in guest.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out users/guest.crt -days 7

In this case the guest.crt will be only valid for 7 days.

🔗create the 2fA codes for the user

The easy way is to create / add user to the system adduser, once the user has been created you could run this command:

su - guest -c google-authenticator

This will create a file in the guest user $HOME: /home/test/.google_authenticator:

$ cat .google_authenticator
35NIQ7S2T44WT5ZC
" RATE_LIMIT 3 30 1510594908 1510594922
" WINDOW_SIZE 17
" DISALLOW_REUSE 50353163
" TOTP_AUTH
53344656
94746893
13420893
11387169
23988009

The 35NIQ7S2T44WT5ZC is the seed / code you need to use within your app that generates the codes for example in authy it will be here:

authy enter code

This should be enough for a starting point.

To test that the pam settings are working you could use pamtester, for example:

# pamtester -v openvpn guest authenticate
pamtester: invoking pam_start(openvpn, test, ...)
pamtester: performing operation - authenticate
Verification code:
pamtester: successfully authenticated

The verification code should be the one generated by your application.