Tech Blog.

Thoughts, stories, ideas.

Yubikey Validation Server Setup

9. July 2017

Soll nicht die YubiCloud für das Validieren von Yubico One-time Passwörtern (OTP) genutzt werden, können die Validation Server auch selber betrieben werden. Dies ist grundsätzlich nicht sehr schwer, jedoch ist das redundante Setup eher dürftig dokumentiert.
OTP Validation Process

Überblick

Als Backend wird MySQL und als Frontend Apache eingesetzt. Die zwei Komponenten yubikey-val und yubikey-ksm sind simple PHP Applikationen, welche aus ein paar wenigen Dateien bestehen.
Blockschema Validation Server

Installation

Webserver

Als erstes sollte Apache, PHP und MySQL installiert werden. Aufgrund der Paket Dependency von yubikey-ksm und yubikey-val kann kein MariaDB Server genutzt werden.
Danach sollten zwei Datenbanken (ykksm und ykval) mit dazugehörigen Benutzern (ykksm_reader und ykval_verifier) erstellt werden.

yubikey_ksm

Im Yubikey Key Storage Module (KSM) werden die einzelnen Yubikeys gespeichert, sprich hier sind alle Daten (inkl. des geheimen AES Keys) aller Yubikeys erfasst. Der Service kann dazu genutzt werden, zu verifizieren, ob ein Yubikey mit dazugehörigem One-Time Passwort (OTP) gültig ist, jedoch nicht ob es eine Replay Attacke ist.

Da die Software keinen Mechanismus kennt, wie die Server synchronisiert werden können, wird dies mit einem MySQL Master-Slave Setup gelöst. Eine entsprechende Anleitung, wie dies gemacht werden kann, ist z.B. auf DigitalOcean vorhanden. Zu beachten ist, dass nur die Datenbank ykksm gesynct werden sollte (binlog_do_db = ykksm).

Auf einem Debian ist die Software yubikey_ksm simpel per apt install yubikey-ksm installiert. Danach ist der Apache und die Datenbank auch schon konfiguriert. Wer die Konfiguration nachträglich noch ändern will, findet die entsprechenden Dateien in /etc/yubico/ksm/. Eine umfassendere Installations Anleitung ist bei Yubico zu finden.

Apache ist so konfiguriert, dass es einen globalen Alias /wsapi/decrypt als /usr/share/yubikey-ksm/ykksm-decrypt.php gibt, falls auf dem Apache mehrere VirtualHosts vorhanden sind, sollten diese in der Konfiguration deaktiviert und nur den für yubikey_ksm benötigten VirtualHost aktiviert werden.

Neue Yubikeys können mit dem Tool ykksm-gen-keys erstellt werden. Dies gibt folgenden Output:

$ ykksm-gen-keys 1
1,cccccccccccb,42e31d069785,cf00b1f4c2c80e395b5e7532a5929cba,d05f7e394f0e,2016-03-22T13:12:25, 

In der Datenbank ist die Tabelle yubikeys vorhanden. Diese hat folgendes Schema:

CREATE TABLE `yubikeys` (
  `serialnr` int(11) NOT NULL,
  `publicname` varchar(16) NOT NULL,
  `created` varchar(24) NOT NULL,
  `internalname` varchar(12) NOT NULL,
  `aeskey` varchar(32) NOT NULL,
  `lockcode` varchar(12) NOT NULL,
  `creator` varchar(8) NOT NULL,
  `active` tinyint(1) DEFAULT '1',
  `hardware` tinyint(1) DEFAULT '1',
  PRIMARY KEY (`publicname`),
  UNIQUE KEY `publicname` (`publicname`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Sollen neue Yubikeys erstellt und gerade in die Datenbank hinterlegt werden, kann dies mit folgendem Script erledigt werden:

#!/bin/bash

MYSQL='mysql'

NEXTID=$(echo "SELECT t1.serialnr + 1 FROM ykksm.yubikeys t1 WHERE NOT EXISTS (SELECT serialnr FROM ykksm.yubikeys t2 WHERE t2.serialnr = t1.serialnr + 1) LIMIT 1;" | $MYSQL | tail -n 1)
if [ -z "${NEXTID}" ]; then
    NEXTID='1'
fi

KEY="$(ykksm-gen-keys ${NEXTID} | grep -v ^#)"

IFS=',' read -r -a ARR <<< "$KEY"

SQL="INSERT INTO ykksm.yubikeys VALUES (${ARR[0]}, '${ARR[1]}', '${ARR[5]}', '${ARR[2]}', '${ARR[3]}', '${ARR[4]}', 'bash', 1, 1);"
echo $SQL | $MYSQL

echo "Set Yubikey:"
echo "ykpersonalize -1 -y -a${ARR[3]} -o fixed=${ARR[1]} -o uid=${ARR[2]}"

yubikey_val

Der Yubikey Validation Service macht die eigentliche Validierung der One-Time Passwörtern (OTP). Es werden folgende Punkte verifiziert und verarbeitet:

  • Client: Der anfragende Client muss sich verifizieren, jeder Client hat eine ID und ein dazugehöriges Passwort. Dies verhindert, dass nicht jeder Client eine Anfrage stellen kann. Als Client wird in diesem Fall z.B. ein ssh Daemon oder eine Webseite verstanden, nicht der User davor.
  • Yubikey: Das OTP wird an den Service yubikey_ksm weitergeleitet. Ist das OTP gültig, wird durch den Service yubikey_val getestet, ob es sich um eine Replay Attacke handelt.
  • Sync: Die anderen Valdiation Server werden über den aktuellen Counter des Yubikeys benachrichtigt, damit keine Replay Attacke an einem anderen Validation Server getätigt werden kann.

Auf einem Debian System ist die Installation mit apt install yubikey-val gemacht. Die entsprechenden Konfigurationsdateien befinden sich im Verzeichnis /etc/yubico/val/.
Danach ist Apache auch schon wieder global konfiguriert. Bei mehreren VirtualHosts sollte die Konfiguration wieder spezifisch für nur einen VirtualHost eingerichtet werden. Folgende Aliase sollten dabei konfiguriert sein:

  • /wsapi/2.0/verify als /usr/share/yubikey-val/ykval-verify.php
  • /wsapi/verify als /usr/share/yubikey-val/ykval-verify.php
  • /wsapi/2.0/sync als /usr/share/yubikey-val/ykval-sync.php
  • /wsapi/2.0/resync als /usr/share/yubikey-val/ykval-resync.php
  • /wsapi/revoke als /usr/share/yubikey-val/ykval-revoke.php

Datenbank

Die Datenbank beinhaltet drei Tabellen:

  • clients
    Hier werden die Clients (z.B. ein PAM oder Mediawiki) erfasst, welche den Validation Server anfragen dürfen.
CREATE TABLE `clients` (
  `id` int(11) NOT NULL,
  `active` tinyint(1) DEFAULT '1',
  `created` int(11) NOT NULL,
  `secret` varchar(60) NOT NULL DEFAULT '',
  `email` varchar(255) DEFAULT NULL,
  `notes` varchar(100) DEFAULT '',
  `otp` varchar(100) DEFAULT '',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  • queue
    In der queue sind die Einträge, welche noch mit anderen Validation Servern abgeglichen werden müssen. Die Einträge werden vom System Service ykval-queue bearbeitet.
CREATE TABLE `queue` (
  `queued` int(11) DEFAULT NULL,
  `modified` int(11) DEFAULT NULL,
  `server_nonce` varchar(32) NOT NULL,
  `otp` varchar(100) NOT NULL,
  `server` varchar(100) NOT NULL,
  `info` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
  • yubikeys
    In dieser Tabelle sind die Yubikeys mit ihren entsprechenden Countern eingetragen. Diese Tabelle wird durch yubikey_val selber mit den anderen Validation Server synchronisiert.
CREATE TABLE `yubikeys` (
  `active` tinyint(1) DEFAULT '1',
  `created` int(11) NOT NULL,
  `modified` int(11) NOT NULL,
  `yk_publicname` varchar(16) NOT NULL,
  `yk_counter` int(11) NOT NULL,
  `yk_use` int(11) NOT NULL,
  `yk_low` int(11) NOT NULL,
  `yk_high` int(11) NOT NULL,
  `nonce` varchar(40) DEFAULT '',
  `notes` varchar(100) DEFAULT '',
  PRIMARY KEY (`yk_publicname`),
  UNIQUE KEY `yk_publicname` (`yk_publicname`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Synchronisation

Der Service kennt einen eigenen Mechanismus zur Synchronisation. Da jeweils der höchste Counter (der OTP) jedes Yubikeys auf allen Servern bekannt sein sollte, wird dies nicht über eine MySQL Replication gelöst. Dies hätte zur Folge, dass auch tiefere Counter synchronisiert werden.

Damit die Server synchronisieren dürfen, muss zuerst noch die Konfiguration angepasst werden. Als Beispiel werden die beiden Hosts srv-tfvalid-01 (IP 128.66.1.1) und srv-tfvalid-02 (IP 128.66.1.2) genutzt. Folgende Konfigurationsparameter müssen angepasst werden:

  • __YKVAL_SYNC_POOL__
    Hier werden die Server eingetragen, mit welchen synchronisiert werden soll.
$baseParams['__YKVAL_SYNC_POOL__'] = array(
  "https://srv-tfvalid-01/wsapi/2.0/sync",
  "https://srv-tfvalid-02/wsapi/2.0/sync"
);
  • __YKVAL_ALLOWED_SYNC_POOL__
    Hier werden alle IP Adressen aller Validation Server eingetragen, damit diese synchronisieren dürfen.
$baseParams['__YKVAL_ALLOWED_SYNC_POOL__'] = array(
  "127.0.0.1",
  "128.66.1.1",
  "128.66.1.2"
);
  • __YKVAL_RESYNC_IPS__
    Dieser Wert wird auf den Wert von __YKVAL_ALLOWED_SYNC_POOL__ gesetzt.
$baseParams['__YKRESYNC_IPS__'] = $baseParams['__YKVAL_ALLOWED_SYNC_POOL__'];
  • __YKVAL_SYNC_DEFAULT_LEVEL__
    Dieser Wert definiert den mindest Level in Prozent, wie viele Validation Server erfolgreich synchronisiert werden müssen, bevor das OTP als gültig deklariert wird. Sind Server nicht erreichbar, kann dies zur Folge haben, dass das OTP, aufgrund zu wenigen Antworten bei der Synchronisation, als ungültig deklariert wird.
    Werte zwischen 0 und 100 sind möglich. Sind im gesamten Sync Pool zwei Server, und wird Server 1 angefragt, hat dieser folgende Sync Level:

    • Server 2 online: 100
    • Server 2 offline: 0

Sind im gesamten Sync Pool drei Server und wird Server 1 angefragt, hat er folgende Sync Level:

  • Beide Server online: 100
  • Ein Server online, anderer offline: 50
  • Beide Server offline: 0

Damit bei einem Setup mit zwei Servern einer offline sein darf, muss der Wert also folgendermassen auf 0 gesetzt werden:

$baseParams['__YKVAL_SYNC_DEFAULT_LEVEL__'] = 0;
Services für die Synchronisation

Der System Service ykval-queue kann bei systemd mit folgendem Service File gestartet werden:

[unit]
Description=Yubikey Validation Server Sync Queue
After=network.target

[Service]
ExecStart=/usr/sbin/ykval-queue
Restart=on-failure

[Install]
WantedBy=multi-user.target

Zusätzlich muss noch der Cronjob, welcher die Synchronisation auslöst, erstellt werden. Der Cronjob muss pro Kombination Validation Server – Validation Server erstellt werden und sieht in etwa wie folgt aus:

* * * * * /usr/sbin/ykval-synchronize validation-server-2 all

Neue Validation Clients erstellen

Neue Clients können mit dem Tool ykval-gen-clients erstellt werden. Mit folgendem Bash Script können Clients automatisch erstellt und in die Datenbank gespeichert werden:

#!/bin/bash

MYSQL='mysql'
ERRORMSG='Failed to insert new client with query '

CLIENT="$(ykval-gen-clients 1 2>&1)"

echo "USE ykval; ${CLIENT#$ERRORMSG}" | $MYSQL

echo "Client configuration:"
echo ${CLIENT#$ERRORMSG}

Validation Server nutzen

Die Validation Server sollten vor der Nutzung noch getestet werden. Zum Beispiel sollte getestet werden, ob eine Replay-Attacke an den zweiten Server nicht möglich ist, nachdem am ersten Server die Validierung schon gemacht wurde. Ein weiterer Test ist, ob die MySQL Datenbanken sich synchronisieren und die Einträge in der Tabelle ykval.queue abarbeiten.

SSHd

Soll ein SSH Server mit TwoFactor abgesichert werden, wird dies am einfachsten im PAM (Pluggable Authentication Modules for Linux) gemacht. Es gibt ein extra PAM Modul (pam_yubico), welches die Yubikey Validation Server anfragt.

PAM ist in diesem Beispiel ein Client vom Validation Server, also muss für PAM eine ID und ein Key mit ykval-gen-clients generiert werden. Es sollen nur User in der POSIX Gruppe Users eine TwoFactor Validierung machen müssen, bei den anderen reicht ein Login ohne. Die ID der Yubikeys wird im LDAP mit dem User verknüpft, sprich die User, welche eine TwoFactor Validierung machen müssen, haben im LDAP ein Attribut (im Beispiel: yubikey), welches dem publicname ihres Yubikeys entspricht (Beispiel: cccccccccccb).

Die folgende Konfiguration wird zusätzlich in /etc/pam.d/sshd eingefügt. Am besten nach der Zeile, welche die Passwort Authentifizierung macht, also beispielsweise nach pam_unix.so oder pam_ldap.so in der Chain auth. Diese können auch mit einem @include aus z.B. der common-auth eingebunden werden, daher kann hier nicht eine fertige Konfiguration angegeben werden.

auth [success=2 default=ignore] pam_succeed_if.so user notingroup Users
auth [success=1 default=ignore] pam_yubico.so id=1 \
                                              
key=bjMN3jRHquwHr5NqNKN+LEFZUjY= \
                                              
urllist=https://srv-tfvalid-01/wsapi/2.0/verify;https://srv-tfvalid-02/wsapi/2.0/verify \
                                             
ldap_uri=ldap://ldap1.example.com:389/;ldap://ldap2.example.com:389/ \
                                             
ldapdn=cn=users,dc=example,dc=com \
                                              
user_attr=uid \
                                              
yubi_attr=yubikey \
                                              
verbose_otp
auth requisite                  
pam_deny.so

Danach sollte das Login auf den Server getestet werden (Achtung: Dabei immer ein root Login offen halten, da wenn etwas nicht funktioniert, gegebenenfalls kein Login mehr möglich ist).