#yubikey #systemd #isync #udev

Letting systemd services depend on the presence of a Yubikey

Background

I love my Yubikey. Besides using it for local authentication it also contains my PGP key. By using the awesome pass password manager (or more accurately the go rewrite gopass) I do not have to write passwords into config files any more but just configure the correct call to pass and the rest is handled by the system.

In case of email this worked really well as long as I was using offlineimap. This program provides a background daemon which downloads all your mails to your drive. I was really happy with this setup, but as more and more accounts got added this was one of the biggest resource hogs on my system.

So I decided to switch to isync, which is a lightweight and really fast alternative to offlineimap. The problem is, that isync has to be called periodically since it just provides a simple sync command. That means my password is needed every X minutes. Usually this is no problem, since my Yubikey handles this for me. But sometimes I use my computer without it and this is a big problem.

Isync attempts to decrypt the gpg file containing my password and naturally asks for the key. Since it is saved on my Yubikey I get this handsome dialog:

I can manually disable the systemd timer I wrote to trigger the sync but this is cumbersome. So I searched for a way to automate this and found it:

Udev to the rescue

For some time now linux includes udev which is according to wikipedia

a device manager for the Linux kernel.

It is a complicated system and I only want to focus on the user facing part, called rules. These are simple text files which are evaluated by udev when hardware events trigger. A simple example is the addition of a usb keyboard which triggers a command to use the built in keyboard.

My very first udev rule

So I started to write my first udev rule. This is surprisingly easy, as a rule consists of a identifying part and a execution part. First I had to find something to identify my key. I decided to use the vendor and product id, which a USB device should usually have. They can easily be extracted by using the lsusb command. For my key the output was:

Bus 001 Device 030: ID 1050:0407 Yubico.com Yubikey 4 OTP+U2F+CCID

The format is: ID <vendor id>:<device id>.

With this in mind my udev rule looks like this:

SUBSYSTEM=="usb", ENV{ID_MODEL_ID}="0407", ENV{ID_VENDOR_ID}="1050", TAG+="systemd", SYMLINK="yubikey", 
SYSTEMD_READY=0
  • SUBSYSTEMS=="usb": limits to usb devices
  • ENV{ID_MODEL_ID}=="0407", ENV{ID_VENDOR_ID}=="1050",: limit to the specified usb prodcut and vendor id
  • SYMLINK="yubikey": create a device called “yubikey” in dev (/dev/yubikey)
  • TAG+="systemd": only devices with this tag are added to the systemd device system
  • SYSTEMD_READY=0: only show this device in the tree if it is actually plugged in

Saving this in a file called 85-yubikey.rules inside the /etc/udev/rules.d/ directory and calling sudo udevadm control --reload-rules activates the rule (the number in front of the rule just specifies the order; this rule gets loaded pretty late and after all rules with lower front numbers).

If you plug your device in now a new entry in /dev/ should appear, which is called yubico. If this does not happen, check the ids and maybe restart your system.

Telling systemd to look for the key

The rest is really simple. Systemd generates dev-devicename.device files for every udev device with the systemd tag. This device files can be used everywhere where a unit or target is required.

A quick look in the documentation suggests Requisite and After as the appropriate options:

[Unit]
...
Requisite=dev-yubikey.device
After=dev-yubikey.device

[Service]
...

This is basically the equivalent to the following meme 😉

And this is it. Now the service is only started when the key is inserted. This all took about half an hour and will save me a lot of time and nerves in the future.