In diesem Artikel soll gezeigt werden, wie man ausgehend von einem Server mit frisch installiertem Debian Stretch einen Docker Host aufsetzt. Es besteht kein Anspruch auf Vollständigkeit in Bezug auf Sicherheit und Wartbarkeit. Ich lasse mich aber gerne eines Besseren belehren und werde den Text bei neuen Erkenntnissen anpassen.
Den Server aufsetzen
Der Ausgangspunkt
Den Anfang macht ein frisch installiertes Debian Stretch. Wahlweise kann auch Ubuntu Server zum Einsatz kommen. Die nötigen Schritte sollten weitestgehend gleich sein.
Es wurde ein Passwort für den root
Benutzer vergeben und SSH ist über den Port 22 erreichbar.
Es besteht eine SSH Verbindung zum Server als root
.
Locale
Auf einem frischen Debian Stretch ist die locale nicht gesetzt. Dies führt zu Warnungen beim Installieren von Paketen.
$ dpkg-reconfigure locales
de-DE.UTF-8
sollte im folgenden Dialog schon vorausgewählt sein. Nach der Bestätigung mit Enter
ist de-DE.UTF-8
erneut als Standard zu wählen.
Eine weitere Bestätigung mit Enter
startet die Generierung der locale. Anschließend muss das System neu gestartet werden.
$ reboot
Editor
Zu Anfang ist auf dem System kein Editor installiert. Folgende Befehle installieren vim. Es kann aber auch ein beliebiger anderer Editor verwendet werden.
$ apt-get update
$ apt-get install vim
Informationen zur Benutzung von vim sind unter https://www.selflinux.org/selflinux/html/vim.html zu finden.
SSH Zugang
Über das Internet erreichbare SSH Dienste mit Standard Konfiguration werden massiv mit automatisierten Brute Force Angriffen bedacht.
Dabei wird üblicherweise versucht, eine Anmeldung mit dem root
Benutzer durchzuführen.
Eine Konfiguration abseits des Standards ist eine einfache Methode dieses Problem zu lösen.
- Die Anmeldung als
root
soll deaktiviert werden. - Der SSH Dienst soll nicht über Port 22 erreichbar sein.
Zunächst muss ein neuer Benutzer angelegt werden. Über diesen sollen zukünftige Zugriffe via SSH erfolgen.
$ adduser maxmustermann
maxmustermann
ist dabei durch den Namen des neuen Benutzers zu ersetzen.
Es wird ein Dialog geführt, der alle benötigten Daten für den neuen Benutzer erfasst. Dabei ist ein Passwort zu vergeben. Dieses sollte dem üblichen Muster für sichere Passwörter entsprechen (lang und großer Zeichenraum). Ein Passwort-Manager (z.B. KeePass oder 1Password) hilft beim Erstellen und Verwalten.
Sollte das Passwort für root
nicht dem beschriebenen Muster entsprechen, kann es folgendermaßen geändert werden:
$ passwd
Nachdem der Benutzer angelegt wurde, kann der SSH Dienst in der zugehörigen Konfigurationsdatei /etc/ssh/sshd_config
konfiguriert werden.
$ vim /etc/ssh/sshd\_config
Dies öffnet die Datei im vim Editor. Nun müssen zwei Zeilen angepasst werden.
Port 22
Der Port sollte auf einen Wert im Bereich oberhalb von 1024 gesetzt werden.
PermitRootLogin yes
Der Wert muss auf no
gesetzt werden.
Abschließend startet man den SSH Dienst neu, um die geänderte Konfiguration zu laden.
$ /etc/init.d/ssh restart
Der SSH Dienst wurde gegen automatisierte Angriffe abgesichert.
Neue Anmeldungen erfolgen auf dem neuen Port und dem neuen Benutzer.
Nach erfolgter Anmeldung kann mittels su
auf den root
Benutzer gewechselt werden.
Firewall
Es soll ein einfaches Firewall Script mittels iptables erstellt werden. Dieses soll beim Systemstart geladen werden und erlaubt eingehende Verbindungen ausschließlich für den SSH Dienst.
$ mkdir /opt/firewall
$ cd /opt/firewall
$ touch iptables.sh
$ chmod -x iptables.sh
Dies legt eine leere Datei unter /opt/firewall/iptables.sh
an. Folgenden Inhalt soll die Datei bekommen:
#!/bin/bash
IPT="/sbin/iptables"
PUB\_IF="eth0"
$IPT -F
$IPT -X
$IPT -t nat -F
$IPT -t nat -X
$IPT -t mangle -F
$IPT -t mangle -X
modprobe ip\_conntrack
#unlimited
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
# DROP all incomming traffic
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP
# Block sync
$IPT -A INPUT -i ${PUB\_IF} -p tcp ! --syn -m state --state NEW -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Drop Sync"
$IPT -A INPUT -i ${PUB\_IF} -p tcp ! --syn -m state --state NEW -j DROP
# Block Fragments
$IPT -A INPUT -i ${PUB\_IF} -f -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Fragments Packets"
$IPT -A INPUT -i ${PUB\_IF} -f -j DROP
# Block bad stuff
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags ALL ALL -j DROP
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags ALL NONE -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "NULL Packets"
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags ALL NONE -j DROP # NULL packets
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags SYN,FIN SYN,FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "XMAS Packets"
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP #XMAS
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags FIN,ACK FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Fin Packets Scan"
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags FIN,ACK FIN -j DROP # FIN packet scans
$IPT -A INPUT -i ${PUB\_IF} -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP
# Allow full outgoing connection but no incomming stuff
$IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
# Allow ssh
$IPT -A INPUT -p tcp --destination-port 22 -j ACCEPT
# Allow ICMP
$IPT -A INPUT -p icmp --icmp-type 8 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -p icmp --icmp-type 0 -m state --state ESTABLISHED,RELATED -j ACCEPT
# Do not log smb/windows sharing packets - too much logging
$IPT -A INPUT -p tcp -i ${PUB\_IF} --dport 137:139 -j REJECT
$IPT -A INPUT -p udp -i ${PUB\_IF} --dport 137:139 -j REJECT
# log everything else and drop
$IPT -A INPUT -j LOG
$IPT -A FORWARD -j LOG
$IPT -A INPUT -j DROP
exit 0
Sollte das primäre Netzwerk-Interface nicht eth0
sein, muss die Zeile PUB_IF="eth0"
entsprechend angepasst werden.
Im Bereich #Allow ssh
ist der Port 22 entsprechend des vorherigen Kapitels anzupassen.
Um das Script beim Systemstart automatisch auszuführen, muss die Datei /etc/rc.local
angelegt und ausführbar gemacht werden.
$ touch /etc/rc.local
$ chmod +x /etc/rc.local
Der Inhalt soll folgendermaßen aussehen:
#!/bin/sh -e
sh /opt/firewall/iptables.sh
exit 0
Ab dem nächsten Neustart des Servers werden ausschließlich SSH Verbindungen zugelassen. Für den Betrieb von Docker sind keine weiteren Anpassungen notwendig. Der Docker Dienst erstellt automatisch iptables Regeln beim Start eines Containers. Wird der Container gelöscht, werden die zugehörigen iptables Regeln ebenfalls entfernt.
Automatische Updates
Um den Server gegen mögliche Angriffe abzusichern, ist es wichtig, die installierte Software auf aktuellem Stand zu halten. Es ist daher ratsam, in regelmäßigen Abständen Aktualisierungen durchzuführen.
Um die Administration des Servers zu vereinfachen, kann die Installation von Updates automatisiert werden. Folgender Befehl installiert die notwendigen Pakete:
$ apt-get install unattended-upgrades apt-listchanges
Anschließend sollte die Datei /etc/apt/apt.conf.d/50unattended-upgrades
bearbeitet werden.
Bei zwei Zeilen müssen die Kommentar-Zeichen //
entfernt werden.
// Unattended-Upgrade::Mail "root";
// Unattended-Upgrade::Automatic-Reboot-Time "02:00";
Die erste bewirkt, dass Informationen zu erfolgten Installationen an das interne Postfach von root
gesendet werden.
Die zweite legt den Zeitpunkt für ggf. nötige Neustarts fest.
Es wird nun täglich geprüft, ob Sicherheitsupdates verfügbar sind und diese ggf. automatisch installiert. Weitere Details zur Konfiguration sind unter https://wiki.debian.org/UnattendedUpgrades zu finden.
Docker
Docker kann mittels des Debian Paket Managers apt installiert werden. Das dafür benötigte Repository ist von Haus aus nicht in apt registriert und muss daher zunächst bekanntgegeben werden.
$ apt-get update
$ apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
$ add-apt-repository "deb \[arch=amd64\] https://download.docker.com/linux/debian $(lsb\_release -cs) stable"
Das Repository ist nun in apt registriert und Docker kann installiert werden.
$ apt-get update
$ apt-get install docker-ce
Um den Zugriff auf die Docker Shell Befehle auf root
zu beschränken, müssen noch die Ausführungs-Rechte für sonstige Benutzer entfernt werden.
$ chmod o-x /usr/bin/docker\*
Abschließend muss noch das automatische Starten von Docker beim Systemstart eingerichtet werden.
$ systemctl enable docker
Docker wurde installiert und startet automatisch beim Serverstart. Weitere Informationen zur Docker Installation sind unter https://docs.docker.com/install/linux/docker-ce/debian/ zu finden.
git (optional)
Eine Installation von git ist für den Betrieb des Docker Host nicht notwendig. Da aber mit steigender Anzahl genutzter Container auch die Menge von Scripts zur Wartung steigt, ist es durchaus sinnvoll diese in einem git Repository zu speichern. Neben der Versionierung bietet sich der Vorteil die Scripts auf einem Desktop System mit “schöneren” Editoren bearbeiten zu können.
$ apt-get install git-core
Anschließend ist der git Benutzer zu konfigurieren. Name und Email Adresse müssen natürlich entsprechend angepasst werden.
$ git config --global user.name "Max Mustermann"
$ git config --global user.email max@mustermann.com
Zum Schluss kann das git Repository mit den Scripts geklont werden.
$ cd /opt
$ git clone https://url-to-repo/scripts-repo.git docker-scripts
Dies lädt das Repository in das Verzeichnis /opt/docker-scripts
.
Die URL ist dabei entsprechend anzupassen.
$ cd /opt/docker-scripts
$ git pull
Hierdurch werden die Scripts auf den aktuellen Stand gebracht.
Betrieb und Wartung
Backups der Container Volumes
Angenommen es existiert ein Container mit dem Namen my-app
und es ist für ihn ein Volume my-app-config
angelegt worden.
In dem Volume sind Daten abgelegt, die regelmäßig gesichert und ggf. zurückgesetzt werden sollen.
Des weiteren existiert das Volume backup-data
, das als Ziel für Backups dient.
Alternativ kann anstatt von backup-data
ein Verzeichnis auf dem Host eingebunden werden.
Der Parameter im Befehl würde dann dem Muster -v /data/backups:/target
folgen.
Folgendes Script packt alle Daten im Volume my-app-config
in ein .tar.gz Archiv und verschiebt dieses in das eingebundene /target
Verzeichnis:
#!/usr/bin/env bash
docker stop my-app
docker run --rm --name my-app-backupdata -v my-app-config:/source -v backup-data:/target debian:latest /bin/sh -c "cd /source && tar -zcvf my-app-config.backup.tar.gz ./\* && mv ./my-app-config.backup.tar.gz /target"
docker start my-app
Das Wiederherstellen der Daten mit vorherigem Löschen des Inhalts von my-app-config
wird folgendermaßen durchgeführt:
#!/usr/bin/env bash
docker stop my-app
docker run --rm --name my-app-backupdata -v my-app-config:/target -v backup-data:/source debian:latest /bin/sh -c "rm -r -f /target/\* && cp /source/my-app-config.backup.tar.gz /target && cd /target && tar -zxvf ./my-app-config.backup.tar.gz && rm ./my-app-config.backup.tar.gz"
docker start my-app
Das Backup Script kann mittels cron regelmäßig ausgeführt werden.
Container aktualisieren
Zusätzlich zu den Annahmen des vorherigen Abschnitts soll gelten, dass ein Docker Image my-app
existiert und das Script createcontainer.sh
im selben Verzeichnis liegt.
Dieses Script beinhaltet die nötigen Befehle, um den Container aus dem Image mit dem Tag latest
zu erstellen.
Das folgende Script bringt den Container auf den neusten Stand.
Dabei wird das alte Image unter dem Tag rollback
gesichert.
Ebenfalls wird der Inhalt des Volume my-app-config
gesichert.
#!/usr/bin/env bash
docker rmi my-app:rollback
docker tag my-app:latest my-app:rollback
docker pull my-app:latest
docker stop my-app
docker rm my-app
docker run --rm --name my-app-backupdata -v my-app-config:/source -v backup-data:/target debian:latest /bin/sh -c "cd /source && tar -zcvf my-app-config.rollback.tar.gz ./\* && mv ./my-app-config.rollback.tar.gz /target"
sh ./createcontainer.sh
docker start my-app
Sollte die neue Version des Image Probleme bereiten, kann mit folgendem Script ein rollback zum vorherigen Zustand durchgeführt werden.
Dabei wird die zuvor gesicherte Version des Image wiederhergestellt und der Inhalt von my-app-config
zurückgespielt.
#!/usr/bin/env bash
docker stop my-app
docker rm my-app
docker rmi my-app:latest
docker tag my-app:rollback my-app:latest
docker rmi my-app:rollback
docker run --rm --name my-app-backupdata -v my-app-config:/target -v backup-data:/source debian:latest /bin/sh -c "rm -r -f /target/\* && cp /source/my-app-config.rollback.tar.gz /target && cd /target && tar -zxvf ./my-app-config.rollback.tar.gz && rm ./my-app-config.rollback.tar.gz"
sh ./createcontainer.sh
docker start my-app
Zusammenfassung
Es wurde gezeigt, wie ausgehend von einem frisch installierten Debian Stretch ein Docker Host aufgesetzt werden kann. Dieser wurde gegen gängige automatisierte Angriffe abgesichert. Außerdem wurden Scripts zur Sicherung von Daten und zum Updaten von Containern erstellt.