News
Das Neuste aus der Welt von Adfinis SyGroup
In diesem Blogpost stellen wir eine Lösung vor mit der ein MariaDB Galera Cluster auf Kubernetes oder kompatiblen Lösungen (z.B. Red Hat OpenShift Container Plattform, CoreOS Tectonic und Canonical’s Kubernetes) betrieben werden kann. Es ermöglicht damit cloud-native Applikationen, die in Kubernetes betrieben werden, und die Datenquellen, die benötigt werden, in der gleichen Infrastruktur zu betreiben und mit den gleichen Werkzeugen zu verwalten.
Die Funktionalität baut auf dem Feature PetSets von Kubernetes auf, eine API die für Stateful Applikationen gedacht ist.
Was sind PetSets
Kubernetes hat sich im Lauf der letzten Jahre von einem neuen Tool für Container Management, zu dem Projekt entwickelt, welches in diesem Bereich den Ton und Takt vorgibt. Aufgrund der Architektur von Kubernetes war es bisher nur schwer möglich Applikationen und Services, die nicht stateless sind, darauf zu betreiben. Erste Ideen für so genannte “Nominal Services” wurden früh vorgeschlagen, aber aufgrund anderer Prioritäten nicht umgesetzt.
Mit dem Release von Kubernetes v1.3 gibt es nun eine erste Vorschau auf eine Lösung. Mit PetSets wurde in Kubernetes eine API im Alpha Status geschaffen, die spezifisch auf die Anforderungen von stateful Applikationen und Services zugeschnitten ist. Pods in einem PetSet erhalten einen eindeutigen Namen und numerischen Index (z.B. app-0, app-1, …), der über die Lebensdauer des Pods gleich bleibt. Zusätzlich bleiben auch Persistent Volumes dem Pod zugeordnet, auch wenn dieser auf einen anderen Host migriert wird. Da jedem PetSet ein Service zugeordnet wird, ist es möglich über Abfragen an den Service Informationen über die Pods des PetSets zu verarbeiten. Dies ermöglicht es z.B. das Bootstrapping eines Clusters zu automatisieren, oder die Konfiguration zur Laufzeit entsprechend anzupassen, sollte sich der Zustand des Clusters ändern (Scale Up, Scale Down, Wartung oder Ausfall eines Hosts, …).
Wie kann MariaDB Galera Cluster auf Kubernetes betrieben werden
Um ein MariaDB Galera Cluster auf Kubernetes zu betreiben ist es vor allem notwendig den Bootstrap des Clusters und die Anpassung der Konfiguration auf Basis der aktuellen Pods im PetSet zu implementieren. In unserem Fall werden diese beiden Aufgaben von zwei Init Containern übernommen. Im Docker Image galera-init sind die entsprechenden Tools verpackt um den Bootstrap durchzuführen, der zweite Container startet das Binary peer-finder
welches den SRV Record des Kubernetes Services abfragt und auf Basis der Mitglieder des PetSets die Konfiguration für das MariaDB Galera Cluster generiert.
Beim Starten des ersten Pets wird wsrep_cluster_address=gcomm://
in der Konfiguration gesetzt und der Pod führt automatisch einen Bootstrap des Cluster durch. Alle weiteren Pets die gestartet werden, befüllen wsrep_cluster_address mit den Hostnamen aus dem SRV Record des Services, und treten automatisch dem bestehenden Cluster bei. Hier noch ein Beispiel der Konfiguration nach dem Starten des zweiten Pets:
wsrep_cluster_address=gcomm://mariadb-0.galera.lf2.svc.cluster.local,mariadb-1.galera.lf2.svc.cluster.local
wsrep_cluster_name=mariadb
wsrep_node_address=mariadb-1.galera.lf2.svc.cluster.local
Die entsprechenden Quellen um MariaDB Galera Cluster auf der eigenen Kubernetes oder Red Hat OSCP Infrastruktur zu testen sind auf Github veröffentlicht: https://github.com/adfinis-sygroup/openshift-mariadb-galera
Beschränkungen von PetSets im Alpha Status
Die Beschränkungen von PetSets im Alpha Status sind in der Kubernetes Dokumentation erläutert und betreffen diverse Bereiche, vor allem Tasks die manuell bearbeitet werden müssen. So ist es zum Beispiel nur möglich die Anzahl der Replikas über den Parameter replicas zu erhöhen, aber nicht möglich sie zu senken. Auch ein Update der PetSet Definition ist nicht automatisch möglich, daher muss ein Update auf eine neue Image Version manuell über ein neues PetSet gemacht werden.
Wie sieht die Zukunft von PetSets aus
Mit dem Release von Kubernetes v1.5 verlässt PetSets den Alpha Status und wird unter dem Namen StatefulSet in den Beta Status gehoben. Dies ist eine wichtige Entwicklung, da sich die externe API in Kubernetes nach dem Beta Status nicht mehr ändert. Der Release von Kubernetes v1.5 ist für den 9. Dezember 2016 geplant.
zukünftige Arbeiten
Ein weiterer Blogpost wird veröffentlicht sobald Kubernetes v1.5 released wurde. Dieser enthält Updates der YAML Definitionen und zusätzliche Anweisungen wie man MariaDB Galera Cluster auf seiner eigenen Infrastruktur testen kann.
Links
In diesem zweiteiligen Artikel werden wir die Continuous Integration Funktionalitäten von GitLab CI genauer unter die Lupe nehmen.
Der erste Teil widmet sich der Installation und Konfiguration, während der zweite Teil detaillierter auf einzelne Anwendungsszenarien eingehen wird.
Was ist CI überhaupt?
CI steht in diesem Fall für Continous Integration und bedeutet vereinfacht gesagt, dass nach jeder Änderung am Source Code eines Projektes ein Script ausgeführt wird.
Das CI-Script wird in den meisten Fällen über eine Versionsverwaltung getriggert. Dateien welche dieses Script erstellt, können später als sogenannte Build Artifacts
weiterverwendet werden.
In vielen Fällen startet dieses Script Unit-Tests, welche überprüfen, ob die soeben gemachten Änderungen nicht unbeabsichtigt andere Teile des Projektes beeinflussen.
Unit-Tests sind jedoch nur ein Szenario, ein CI kann beispielsweise auch Pakete bauen, Dokumentationen erstellen oder sogar Anwendungen deployen.
GitLab CI
GitLab ist eine Open Source Lösung für Git-Hosting, die unter anderem auch CI-Funktionalität bietet. Seit Version 8.2 ist der CI-Teil fest in GitLab integriert und muss nicht mehr wie zuvor auf einem separaten Server installiert werden.
Das Konzept von GitLab CI basiert auf so genannten Runnern, welche die eigentlichen Build-Scripts ausführen. GitLab CI ist nur für das Orchestrieren dieser Runner und dem Zusammentragen von Resultaten und Artifacts zuständig. Runner können Docker-Container, VMs oder auch bare metal Maschinen sein, die optimalerweise nicht auf demselben Server wie der Orchestrator ausgeführt werden. Dadurch haben die CI-Scripte kein Zugriff auf GitLab selbst.
Die Runner melden sich alle paar Sekunden über HTTPS bei GitLab und fragen nach, ob es für sie einen Job gibt, GitLab CI kann die Runner von sich aus nicht kontaktieren.
Dies erleichtert die Einrichtung von neuen Runnern erheblich, einzige Voraussetzung ist, dass sich der Runner per HTTPS mit GitLab verbinden kann.
Die Runner-Komponente von GitLab nennt sich gitlab-ci-multi-runner
[1] und ist ein statisch kompiliertes Binary, welches neben Linux auch Builds auf Windows, OSX und BSD unterstützt.
Das CI-Script für GitLab muss im Hauptverzeichnis des jeweiligen Repos liegen und den Dateinamen .gitlab-ci.yml
haben.
CI für ein Projekt aktivieren
Seit Version 8.2 ist die CI-Komponente in GitLab integriert und standardmässig aktivert. Sie kann jedoch auch nur für einzelne Projekte aktiviert bzw. deaktiviert werden.
Die Einstellung nennt sich Builds und ist in den Projekteinstellungen unter Features zu finden:
GitLab löscht keine bereits ausgeführten Builds und Artifacts wenn das Feauture deaktiviert wird, sondern versteckt nur den Menüpunkt Builds.
Unter gitlab.example.com/group/repo/builds
sind diese weiterhin einsehbar. Builds zu löschen ist über das Webinterface machbar. Seit GitLab 8.9 ist es möglich im .gitlab-ci.yml
zu konfigurieren, wie lange die Resultate und Artifacts aufbewahrt werden. [2]
Runner
Setup
Nach dem Aktivieren von CI im Webinterface, muss ein Runner eingerichtet werden, welcher die effektiven Builds ausführt. In diesem Beispiel wird gitlab-ci-multi-runner
auf einer Debian Jessie VM mit dem Docker-Executor
eingerichtet. Im zweiten Teil dieses Artikels wird detaillierter auf weitere Runner-Konfigurationen eingegangen.
Als erster Schritt sollte das System aktualisiert werden:
# apt-get update
# apt-get upgrade
Da Docker-Runner verwendet werden, braucht es entsprechend Docker:
# curl -sSL https://get.docker.com/ | sh
GitLab stellt ein Script zur Verfügung, welches das Repository automatisch konfiguriert. Dies kann mit folgendem Kommando ausgeführt werden:
# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
Wer keine Scripte aus dem Internet unkontrolliert ausführen möchte, kann das Repo auch auf den traditionellen Weg aktivieren:
# echo "deb https://packages.gitlab.com/runner/gitlab-ci-multi-runner/debian/ jessie main" >> /etc/apt/sources.list
# wget -qO - https://packages.gitlab.com/gpg.key | apt-key add -
# apt-get update
Anschliessend kann gitlab-ci-multi-runner
über APT installiert werden:
# apt-get install gitlab-ci-multi-runner
Dies richtet einen systemd-Dienst ein, welcher (auf Debian Systemen) per Default aktiviert und gestartet sein sollte:
# systemctl status gitlab-runner.service
● gitlab-runner.service - GitLab Runner
Loaded: loaded (/etc/systemd/system/gitlab-runner.service; enabled)
Active: active (running) since Mon 2016-09-19 18:33:32 CEST; 1 weeks 0 days ago
Main PID: 28102 (gitlab-ci-multi)
CGroup: /system.slice/gitlab-runner.service
└─28102 /usr/bin/gitlab-ci-multi-runner run --working-directory /home/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog...
Der Runner ist nun bereit und kann in Projekten registriert und verwendet werden.
Runner registrieren
Der neu installierte Runner muss nun in GitLab registriert werden. Jedes Projekt hat hierzu ein eindeutiges Runner-Token, welches im Webinterface unter Project Settings > Runner eingesehen werden kann:
Mit diesem Token kann der zuvor eingerichtete Runner aktiviert werden. Dazu auf der Runner-VM folgendes Kommando eingeben:
# gitlab-ci-multi-runner register \
--url gitlab.example.com
--registration-token L4zpDbiAAA86sDnvYPkn \
--description "CI hello world" \
--executor "docker" \
--docker-image debian:jessie
Parameter:
--url
GitLab CI URL--registration-token
Registration Token--description
Beschreibung des Runners--executor
Executor, in diesem Fall Docker--docker-image
Docker-Image, unter dem der Runner läuft. In diesem Beispiel wird ein Jessie-Container als Runner verwendet.
Der Runner sollte nach einigen Sekunden im Webinterface erscheinen:
.gitlab-ci.yml
Nachdem der Runner eingerichtet und registriert ist, muss das .gitlab-ci.yml
im Hauptverzeichnis des Projektes erstellt werden. In unserem Beispiel wird nur eine Datei erstellt und getestet, ob diese vorhanden ist:
stages:
- test
run-test:
stage: test
script:
- touch foo
- test foo
Nach dem Commiten und Pushen sollte unter Builds bereits ein laufender Build erscheinen:
Der Build sollte bereits nach einigen Sekunden abgschlossen sein:
Ausblick
Im zweiten Teil werden wir detaillierter auf Real-Life Anwendungsszenarien eingehen und weitere Runner-Typen vorstellen.
Referenzen
Manchmal wäre es doch praktisch, wenn man nicht im Büro sitzt, die Kiste schön brav abgestellt ist zum Strom sparen und man einfach einem Kollegen sagen könnte, er solle mal den Rechner hochfahren. Man könnte danach die wunderbare Technologie namens SSH benutzen. Leider macht einem da die verschlüsselte Festplatte einen Strich durch die Rechnung: Um die Verschlüsselung aufzuheben, muss jemand die Passphrase eintippen.
Abhilfe hierfür bietet der lightweight SSH-Server Dropbear, welchen man im initramfs installieren kann. Zusätzlich sorgt man dafür, dass das Netzwerk-Interface gestartet wird und gibt die verschlüsselte Partition an.
Auf meinem Arch Linux waren hierfür folgende Schritte nötig:
Dropbear installieren und konfigurieren
Als erstes müssen aus dem AUR die Pakete mkinitcpio-utils und mkinitcpio-dropbear installiert werden:
$ yaourt -S mkinitcpio-dropbear mkinitcpio-utils
Diese installieren die nötigen Hooks für das initramfs. Im Anhang ist ein Link zu einem Beispiel, wie in Ubuntu ein solcher Hook gemacht werden kann.
Als nächstes muss der Public Key, mit dem man sich später am System anmelden will, in der Datei /etc/dropbear/root_key
hinterlegt werden. Die Datei ist aufgebaut wie die bekannten authorized_key Dateien unter ~/.ssh/
, es können also mehrere Keys angefügt werden. Bei jedem neuen Key muss aber das initfamfs neu gebaut werden!
mkinitcpio.conf vorbereiten
Als nächstes wird die /etc/mkinitcpio.conf
angepasst. Einerseits muss das Kernel-Modul für den Netzwerkchip unter MODULES=""
hinzugefügt werden. Um das Modul herauszufinden, kann lspci
benutzt werden:
$ lspci -k
Unter den jeweiligen Devices findet man das dafür geladene Modul in der Zeile Kernel modules
. Andererseits muss in den Hooks netconf
, dropbear
und encryptssh
vor filesystems
hinzugefügt werden.
Das Ganze sieht danach ungefähr so aus:
[..]
MODULES="r8169"
HOOKS="base udev autodetect modconf block netconf dropbear encryptssh filesystems keyboard fsck"
Anschliessend muss das initramfs neu gebaut werden:
$ mkinitcpio -p linux
Bootloader konfigurieren
Im Bootloader müssen dem Linux-Kernel noch Optionen zur Kernel-Zeile hinzugefügt werden. In meinem Fall (systemd-boot) ist dies unter /boot/loader/entries/arch.conf
zu finden. Bei GRUB kann dies über Variablen in der Datei /etc/default/grub
gemacht werden (siehe Arch Linux Wiki).
Die eine Option ist cryptdevice
, womit angegeben wird, welches Device (sprich: Partition) überhaupt entschlüsselt werden soll. Am besten gibt man hier die UUID (ls -l /dev/disk/by-uuid/
) an, im Format UUID=<effektive-uuid>
und nicht der Blockdevice Name unter /dev, da sich dieser ändern kann.
Die andere Option sagt dem Kernel, wie das Netzwerk konfiguriert werden soll. Diese Option heisst schlicht ip
und kann entweder per DHCP oder manuell konfiguriert werden. Um DHCP zu verwenden, kann einfach ip=:::::eth0:dhcp
angegeben werden. In meinem Falle wollte ich jedoch eine statische Konfiguration, damit ich weiss, wohin ich per SSH verbinden muss. Das Format dafür ist folgendermassen: ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>
(siehe auch die Kernel-Dokumentation), wobei dies bei mir nicht vollständig zutraf. Den letzten Doppelpunkt musste ich auslassen, damit es funktioniert. Zu beachten ist auch, dass für <device>
der Kernel-Name (ethX) und nicht der Udev-Name verwendet werden muss:
dmesg | grep eno1
16.593465] r8169 0000:03:00.0 eno1: renamed from ==eth0==
Das Resultat sieht so aus:
title Arch Linux
linux /vmlinuz-linux
initrd /initramfs-linux.img
options ip=10.9.4.70::10.9.1.1:255.255.0.0:huckfinn:eth0:none: cryptdevice=UUID=9c53e8b6-07bf-4e9d-bb3e-feaf55c02f41 root=/dev/sda2 rw
Crypttab
Ich hatte vorher in meinem Boot-Prozess im /etc/crypttab
meine verschlüsselte Partition zur Entschlüsslung angegeben. Da die Partition bereits mit dem Parameter cryptdevice
definiert wird, ist keine crypttab Konfiguration mehr nötig. Der Eintrag muss dort deshalb entfernt werden, da die Partition ja bereits geöffnet ist. In der fstab kann nun auch nicht mehr /dev/mapper/XYZ verwendet werden; wenn man die UUID benutzt, funktioniert es aber ohne Probleme.
Nun ist es an der Zeit, sich mit dem Live-Boot USB Stick zu bewaffnen und den Rechner neu zu starten. Im Idealfall sollte es etwa folgendermassen aussehen:
Hat man sich in der IP-Konfiguration vertan, ist dies weniger schlimm: Die Fehlermeldung sollte recht aufschlussreich sein, man kann einfach weiter booten und die Anpassung vornehmen. Ist hingegen der cryptdevice-Parameter falsch, kommt man in einen Zustand, in dem es nicht weiter geht. Dann heisst es, Live-System booten, Partition, auf der die Konfiguration liegt, mounten und den Parameter korrigieren.
Weiterführende Links
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.
Ü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.
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).
Damit wir im Monitoring-Tool sehen können, ob und wann ein SSL-Zertifikat abläuft, wurde ein Script zur Ermittlung des Ablaufdatums geschrieben. Im Vergleich zu anderen Checks, kann dieses Script nicht nur Zertifikate via HTTPS prüfen.
Hier findest du eine Beschreibung, wie das Ablaufdatum eines SSL Zertifikats geholt wird.
openssl s_client -servername "${HOST}" -connect "${HOST}":"${PORT}" 2>&- | openssl x509 -enddate -noout
Damit überhaupt eine Verbindung zu einem SSL Host gemacht werden kann, braucht man zuerst einen SSL client. Hier wird der s_client von OpenSSL verwendet, welcher auch TLS beherrscht. Der s_client dient allgemein zum Diagnostizieren und Debuggen von SSL/TLS Zertifikaten.
servername
Über den Parameter “servername” kann man verifizieren, dass das richtige Zertifikat benützt wird, falls ein Server über VHosts mehrere SSL Zertifikate hat.
Dies geschieht über SNI (Server Name Indication).
connect
Hier geschieht die Verbindung zum Host über den Hostname und Port.
openssl x509 -enddate -noout
Nun wird das x509 Zertifikat ausgelesen und durch die Parameter “enddate” und “noout” wird das Ablaufdatum ausgegeben respektive dass das Zertifikat nicht nach Datum XY gültig ist. Durch “noout” wird der restliche Output verhindert.
Alarm, Alarm!
Das Ziel war es selbst bestimmen zu können, wie und wann Nagios / Icinga alarmiert. Damit Nagios / Icinga alarmiert braucht es den Error Status Code 2. Error Codes:
- 1 – Warning
- 2 – Critical
0 ist natürlich OK. Es werden Parameter zum Einstellen der Warning- und Critical-Werte vergeben. Standartwerte sind:
- Warning=30 (Tage)
- Critical=5 (Tage)
Das Ablaufdatum wird mit dem aktuellem Datum verglichen, so ist erkennbar, wie viele Tage man noch hat bis das Zertifikat abläuft.
Das Monitoring Script ist auf GitHub verfügbar.
Integration ins Monitoring
Das Script wird bei uns als Icinga Plugin ausgeführt.
Commands sind im configfile “/etc/icinga/icinga.cfg” definiert bzw. es ist beschrieben wo diese definiert sind.
Untenstehend der Auszug aus der Icinga-Konfiguration:
define command {
command_name check _ssl
command_line /bin/bash $USER1$/check_ssl.sh -H $ARG1$ -p $ARG2$ -P $ARG3$ -w $ARG4$ -c $ARG5$
}
Die $ARG$ Variablen werden dann durch eine Service Definition gegeben, welche sich standartmässig in /etc/icinga/objects/services_icinga.cfg befindet.
So wird der SSL Check Service für einen Host definiert:
define service {
use template-service
host_name Hostname (e.g. adfinis.com)
service_description check_ssl
max_check_attempts 5
check_interval 360
retry_interval 1
check_command check _ssl!'adfinis.com'!'443'!'no_tls'!'30'!'5'
}
Bei der check_command Zeile werden die $ARG$ Variablen durch Ausrufezeichen getrennt und werden danach auch in dieser Reihenfolge vergeben. Das heisst:
- adfinis.com = $ARG1
- 443 = $ARG2$
- no_tls = $ARG3$
- 30 = $ARG4$
- 5 = $ARG5$