linux · tech

OpenVPN with Google 2-factor authentication on CentOS 7

Configuring OpenVPN with 2-factor authentication is surprisingly “easier than expected”.
First thing, obviously, we need OpenVPN and easy-rsa:

yum install epel-release
yum -y --enablerepo=epel install openvpn easy-rsa

We’ll copy the easy-rsa code in /etc/openvpn/ for easier access (and no surprises during upgrades). Your specific version may vary. Please note that /usr/share/easy-rsa/3 is a symbolic link, you don’t want to copy that:

cp -a /usr/share/easy-rsa/3.0.3 /etc/openvpn/easy-rsa

Then we must create a vars file to setup some defaults for all our certificates:

# cat /etc/openvpn/easy-rsa/vars
set_var EASYRSA_REQ_CITY    "Example City"
set_var EASYRSA_REQ_ORG    ""
set_var EASYRSA_REQ_EMAIL    ""
set_var EASYRSA_REQ_OU        "Example IT"
set_var EASYRSA_KEY_SIZE    4096
set_var EASYRSA_ALGO        rsa
set_var EASYRSA_CA_EXPIRE    3650
set_var EASYRSA_CERT_EXPIRE    3650
set_var EASYRSA_CRL_DAYS    365
set_var EASYRSA_DIGEST        "sha256"

Certification Authority and crypto

Then we go on to create the Certification Authority and the other crypto bits we’ll need later:

# cd /etc/openvpn
# ./easy-rsa/easyrsa init-pki
# ./easy-rsa/easyrsa build-ca
# ./easy-rsa/easyrsa gen-dh >/dev/null 2>&1 &
# openvpn --genkey --secret pki/ta.key
# ./easy-rsa/easyrsa build-server-full nopass
# ./easy-rsa/easyrsa gen-crl

Google Authenticator pam module

Before going on to configure the OpenVPN server, we install the Google Authenticator pam module:

# LC_ALL=C yum -y groupinstall "Development Tools"
# yum -y install pam-devel
# mkdir /usr/src
# cd /usr/src
# git clone
# cd google-authenticator-libpam
# ./
# ./configure
# make && make install

… and we configure it:

# cat /etc/pam.d/openvpn
# google auth
auth        required    /usr/local/lib/security/  forward_pass
account     required
account     include     system-auth use_first_pass
password    include     system-auth
session     include     system-auth

This will be used internally by OpenVPN to test users against a local user. We’ll see later how to create the users and how to login with them.

OpenVPN server configuration

Now let’s create a client configuration directory (ccd), for future-proofing our install, in case we’ll ever need configurations for a specific client, and then examine the server configuration:

# mkdir ccd
# cat /etc/openvpn/server.conf
# 2FA
plugin /usr/lib64/openvpn/plugins/ openvpn

# tcp
port 1194
proto tcp
dev tun
keepalive 10 900
reneg-sec 10800

# log
verb 3
mute 10
log-append /var/log/openvpn.log
status openvpn-status.log

# crypto
cipher AES-256-CFB8
auth SHA512
tls-auth pki/ta.key 0

# certs
ca pki/ca.crt
cert pki/issued/
key pki/private/
dh pki/dh.pem
crl-verify pki/crl.pem

# networking
push "route"

# clients
ifconfig-pool-persist ipp.txt
client-config-dir ccd
max-clients 100

This config will create an openvpn server on TCP port 1194. The default would be to use UDP, but we’ve found TCP to work better for us in unreliable networks.

OpenVPN client configuration template

The client configuration will look like this (customized for each client):

# cat /etc/openvpn/template-client.conf
# logging on separate file if required
# log-append /var/log/openvpn-PLATFORM_NAME-CLIENT_NAME.log


# vpn concentrator
remote 1194

# certificates, with path relative to the config file
cert CLIENT_NAME.crt

# generic stuff
dev tun
proto tcp
verb 3
mute 10
reneg-sec 10800

# crypto
cipher AES-256-CFB8
tls-auth PLATFORM_NAME.ta.key 1
remote-cert-tls server
auth SHA512
script-security 2

As you can see, we use PLATFORM_NAME and CLIENT_NAME as placeholders that we can easily configure for each client.

Creation of a new user

Now we need to create a new user to test that everything works. We’ll call it client0001.

# set the variables we'll use later

# create the certificate and key
cd "/etc/openvpn"
/etc/openvpn/easy-rsa/easyrsa build-client-full "${NAME_CLIENT}" nopass

# create a directory to save all the files
mkdir -p "${DIR_CLIENT}"

# copy certificate, key, tls auth and CA
cp -v "/etc/openvpn/pki/ca.crt" "$DIR_CLIENT/"
cp -v "/etc/openvpn/pki/ta.key" "$DIR_CLIENT/"
cp -v "/etc/openvpn/pki/issued/${NAME_CLIENT}.crt" "$DIR_CLIENT/"
cp -v "/etc/openvpn/pki/private/${NAME_CLIENT}.key" "$DIR_CLIENT/"

# copy and customize the client configuration
cp -v "/etc/openvpn/template_client.conf" "${DIR_CLIENT}/${NAME_CLIENT}.ovpn"
sed -i "" "${DIR_CLIENT}/${NAME_CLIENT}.ovpn"

# create a new local user
PASS=$(head -n 4096 /dev/urandom | tr -dc a-zA-Z0-9 | cut -b 1-20)
useradd -m "${NAME_CLIENT}"
echo "$PASS" | passwd --stdin ${NAME_CLIENT}
echo "$PASS" > ${DIR_CLIENT}/sshpass.txt

# run the google authenticator as the local user and save the code
su ${NAME_CLIENT} -c "/usr/local/bin/google-authenticator -C -t -f -D -r 3 -Q UTF8 -R 30 -w3" > ${DIR_CLIENT}/authenticator_code.txt

Then we can send everything to our user.

Logging in

Our client configuration will ask for a user and password. The user is client0001 and the password is the one randomly generated above and sent to the user plus the authentication token he can generate adding the Google Auth code to his authenticator. So if the password is afBx and the code us 123456, the password typed at the openvpn prompt will be afBx123456.

If everything works correctly, we should see something like this:

$ openvpn --config client0001.ovpn
Thu Mar  7 16:46:47 2019 OpenVPN 2.3.10 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [EPOLL] [PKCS11] [MH] [IPv6] built on Jun 22 2017
Thu Mar  7 16:46:47 2019 library versions: OpenSSL 1.0.2g  1 Mar 2016, LZO 2.08
Enter Auth Username: ********
Enter Auth Password: **************************
Thu Mar  7 16:47:09 2019 Control Channel Authentication: using '' as a OpenVPN static key file
Thu Mar  7 16:47:09 2019 Outgoing Control Channel Authentication: Using 512 bit message hash 'SHA512' for HMAC authentication
Thu Mar  7 16:47:09 2019 Incoming Control Channel Authentication: Using 512 bit message hash 'SHA512' for HMAC authentication
Thu Mar  7 16:47:09 2019 Socket Buffers: R=[87380->87380] S=[16384->16384]
Thu Mar  7 16:47:09 2019 Attempting to establish TCP connection with [AF_INET] [nonblock]
Thu Mar  7 16:47:10 2019 TCP connection established with [AF_INET]
Thu Mar  7 16:47:10 2019 TCPv4_CLIENT link local: [undef]
Thu Mar  7 16:47:10 2019 TCPv4_CLIENT link remote: [AF_INET]
Thu Mar  7 16:47:10 2019 TLS: Initial packet from [AF_INET], sid=xxx yyy
Thu Mar  7 16:47:11 2019 VERIFY OK: depth=1,
Thu Mar  7 16:47:11 2019 Validating certificate key usage
Thu Mar  7 16:47:11 2019 ++ Certificate has key usage  00a0, expects 00a0
Thu Mar  7 16:47:11 2019 VERIFY KU OK
Thu Mar  7 16:47:11 2019 Validating certificate extended key usage
Thu Mar  7 16:47:11 2019 ++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
Thu Mar  7 16:47:11 2019 VERIFY EKU OK
Thu Mar  7 16:47:11 2019 VERIFY OK: depth=0,
Thu Mar  7 16:47:11 2019 Data Channel Encrypt: Cipher 'AES-256-CFB8' initialized with 256 bit key
Thu Mar  7 16:47:11 2019 [] Peer Connection Initiated with [AF_INET]
Thu Mar  7 16:47:13 2019 SENT CONTROL []: 'PUSH_REQUEST' (status=1)
Thu Mar  7 16:47:13 2019 PUSH: Received control message: 'PUSH_REPLY,[...]

That’s it, you should have a functional 2FA implementation using Google Authenticator for your OpenVPN server.


linux · tech · tips

Changing your power saving settings from cron

When I’m working, I want my laptop to stay on even if I take a break (for lunch for example), because I might have some job running in background.

Outside of work hours, though, I would like for the laptop to go on standby if I’m idle for half an hour or more.

I added to my crontab:

# set the computer to sleep after 2 hours
0 9   *   *   *    gsettings set org.mate.power-manager sleep-computer-ac 7200

# set the computer to sleep after 30 minutes
0 18  *   *   *    gsettings set org.mate.power-manager sleep-computer-ac 1800

How did I find out?

First, I started mate-power-preferences --verbose and interacted with the settings, seeing which key was getting changed:

TI:02:41:54   TH:0x557b08   FI:gpm-prefs-core.c   FN:gpm_prefs_action_time_changed_cb,192
 - Changing sleep-computer-ac to 7200

Then I searched for that key in dconf-editor and found its path, org.mate.power-manager.

linux · tech

Running Huginn in a container

It’s been a while since I wanted to run Huginn.

Today I finally unpacked my old laptop after moving and updated its OS to Fedora 29 and I thought it would be a good chance to get it up and running.

Install a tool to run containers

Since the official Docker repository wasn’t working for me, I took the chance to try out Podman as well. This is Red Hat’s tool to run containers and also encompasses the concept of “pods” popularized by Kubernetes.

$ sudo dnf -y install podman

From what I understand, podman does not have a daemon running (good) and interacts nicely with systemd and cgroups.

Run the container

I started by pulling the container image:

$ sudo podman pull huginn/huginn

Then I created a volume to store the database:

$ sudo podman volume create huginn-data

And I ran the container with a very basic setup:

$ sudo podman run -it -p 3000:3000 -v huginn-data:/var/lib/mysql huginn/huginn

But I noticed an error:

bootstrap stderr | mv: cannot create directory '/var/lib/mysql/mysql' bootstrap stderr | : Permission denied
mv: cannot create directory '/var/lib/mysql/performance_schema': Permission denied

Fix the permissions

I’m not sure if this is due to the fact that I had docker installed before podman.

Before doing anything else, I stopped and removed the container.

Then I verified if SElinux was the culprit, but there was no errors in the audit log, so it was just standard perms, so I needed two pieces of information: where the volume was stored on the host’s filesystem and which PID was mysqld running as.

To find out the first one, I ran:

$ sudo podman volume inspect huginn-data
        "name": "huginn-data",
        "labels": {},
        "mountPoint": "/var/lib/containers/storage/huginn-data/_data",
        "driver": "local",
        "options": {},
        "scope": "local"

So the path to my volume on the host system is /var/lib/containers/storage/huginn-data/_data.

To find out the PID under which mysqld was running, I restarted the container without the volume mounted:

$ sudo podman run -it -p 3000:3000 huginn/huginn

And then from another terminal I started a shell on the container:

$ sudo podman ps --all
CONTAINER ID  IMAGE                           COMMAND        CREATED            STATUS                PORTS                   NAMES
a4f0308c00f0  /scripts/init  About an hour ago  Up About an hour ago>3000/tcp  affectionate_banach

$ sudo podman exec -it a4f0308c00f0 /bin/bash
1001@a4f0308c00f0:~$ ps uaxww
1001         1  0.1  0.2  53736 17340 pts/0    Ss+  20:54   0:04 /usr/bin/python /usr/bin/supervisord -c /etc/supervisor/supervisord.conf

mysqld was launched by supervisord (ewww) and it was running with PID 1001, so on the host system I changed the owner of the volume to that:

$ sudo chown 1001 /var/lib/containers/storage/huginn-data/_data

Then I stopped the old container and started it again with the external volume attached, and it worked! :)

fotografia · linux

Updated photographic workflow on Linux

My current photographic workflow on Linux has become quite complicated, so I wanted to share some info about it.

First, I download and rename my photos from the memory card using my own Photofix, that currently writes in a temporary directory in my home.

The next step involves a piece of non-free software: since my new camera raw format isn’t supported yet, I run Adobe DNG converter with Wine, to convert my CR3 files to DNG.

Next I can finally edit my files with Darktable. I usually start by doing a broad selection: every new photo starts at 2 Stars (“saving it just in case”); the worst ones I downvote to 1 Star (“candidate for the trash bin”), those that have potential and should be developed further get 3 Stars, those that I consider “worth showing to the Internet” get 4 Stars and my best shots get 5 Stars.

After the initial assessment I go on developing the single images starting with the best ones. After the editing the initial vote can be revised both upwards and downwards, so I might promote a 3 Star photo to 4 Stars or demote a 4 Stars to 3 or 2.

After I’m done editing, I export all the photos ranked 4 or 5 Stars as high resolution JPGs from Darktable to a new directory (usually named YYYYMM), and then run another script that creates scaled down and watermarked versions for Flickr (smaller size) and YourShot (bigger size).

When I’m done editing and exporting I move the RAW files to my long term storage space, although I’m considering to refine this last step too: I should export all the 1 Star and 2 Stars photos to lower quality JPGs instead of keeping the raw files. That should free some space on my storage and make my backups lighter and faster too :)


Android adb “unknown backup flag” problem

Apparently, at some point the syntax for using adb backup changed and it’s not really well documented…

This is how I backed up my Android phone with adb today:

adb backup -f mybackup.bkp '-apk -obb -shared -all -system'

While before you would launch your backup with:

adb backup -f oldbackup.bkp -apk -obb -shared -all -system

Notice the lack of quotes in the old version.

linux · tech · tips

Schedule one-time jobs with systemd

I rarely use at, but today I shut down crond to do some maintenance and I wanted to schedule an automatic restart for later in the day in case I forget to restart it manually.

So, I ran:

# echo "/usr/bin/service crond start" | at now +6 hours
-bash: at: command not found

Turns out, on systems running systemd you can use systemd-run as a substitute to at to schedule one-time jobs, like this:

# systemd-run --on-active=30 /bin/touch /tmp/foo

The default --on-active parameter is in seconds, but you can pass modifiers to make it more readable:

# systemd-run --on-active="4h 30m" /bin/touch /tmp/foo

If you need to restart a service, there’s a handy shortcut, the --unit parameter:

# systemd-run --on-active=6h --unit crond.service

You can check the job queue (sorta what you would have done with atq) with:

# systemctl list-timers
gio 2018-06-07 16:32:01 CEST 5h 18min left mer 2018-06-06 16:32:01 CEST 18h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
gio 2018-06-07 17:12:12 CEST 7h left n/a n/a crond.timer crond.service

Another poor service (atd) has been swallowed by systemd. RIP.