Mit Docker Version 1.13 hielt eine eigene Verwaltung für Zugangsdaten Einzug, die sogenannten Docker Secrets. Damit ist es möglich Services mit Zugangsdaten, wie Passwörter und Zertifikate zu verknüpfen. Docker speichert diese verschlüsselt innerhalb seines Swarm Mode Clusters.
Es war einmal ...
Doch bevor ich genauer darauf eingehe, möchte ich beleuchten, wie ohne Docker Secrets damit umgegangen wird.
Da mir der praktische Nutzen meiner Artikel sehr wichtig ist, zeige ich im nachfolgenden Beispiel eine docker-compose.yml
, die demzufolge Docker Compose nutzt. Die Datei enthält nur einen MySQL-Dienst. Das Beispiel ließe sich auch mit dem Docker CLI und einem docker container run
umsetzen. Ich gehe aber davon aus, dass es sich in der Realität um einen komplexeren Stack, wie LAMP oder ELK handelt, bei dem die Befehle mit docker container run
schnell unübersichtlich werden können und wir den Stack später einfach mit docker stack deploy
auf das Produktiv-System bringen können.
version: "2"
services:
mysql:
image: mariadb:10.4
environment:
MYSQL_ROOT_PASSWORD: myrootpassword
MYSQL_DATABASE: mydatabase
MYSQL_USER: myuser
MYSQL_PASSWORD: mypassword
Anmerkung der Redaktion: Die obige docker-compose.yml nutzt Version 2 des Standards.
Mit einer solchen Datei im aktuellen Verzeichnis und docker-compose up -d
starten wir unseren Container. Dabei werden die Werte aus dem Node environment
als Environment Variables bekannt gemacht. docker-compose exec mysql env
zeigt uns alle Umgebungsvariablen des laufenden Containers.
[...]
MYSQL_ROOT_PASSWORD=myrootpassword
MYSQL_PASSWORD=mypassword
MYSQL_USER=myuser
MYSQL_DATABASE=mydatabase
[...]
Laut Heroku's 12 Factor App sind Environment Variables eine ideale Möglichkeit die verschiedenen Konfigurationen in den unterschiedlichen Umgebungen bereitzustellen. Allerdings bringen Env Vars einige Probleme mit sich, die Aaron Franks in seinem lesenswerten Blog-Beitrag zusammenfasst. Bedenken sollten man bei der Entscheidung von Heroku's Autoren, dass sie einen generischen Weg gesucht haben, der mit möglichst vielen Technologien funktioniert und die Umgebungsvariablen sind in den meisten Programmiersprachen ansprechbar. Dazu passt der folgende Auszug aus dem Manifest:
The twelve-factor app stores config in environment variables [...] and unlike custom config files, or other config mechanisms such as Java System Properties, they are a language- and OS-agnostic standard.
Heroku, The Twelve-Factor App, Chapter 3: Config
Als Alternative zur Definition einer Liste innerhalb des Knotens environment
gibt es noch die Variante env_file: my-environment.txt
zu nutzen, um einen Pfad zu einer Datei nach der env
-Syntax zu nutzen. Das macht die docker-compose.yml
lesbarer bei der Verwendung vieler Variablen.
$ cat my-environment.txt
MYSQL_ROOT_PASSWORD=myrootpassword
MYSQL_PASSWORD=mypassword
MYSQL_USER=myuser
MYSQL_DATABASE=mydatabase
Der neue Standard: Docker Secrets
Die nachfolgend beschriebenen Docker Secrets lassen sich nur unter Verwendung von Services und dem Swarm Mode nutzen. Den Wunsch nach größtmöglicher Parität aller eingesetzten Umgebungen, brechen wir damit ein wenig, aber wir werden später noch einen Weg kennenlernen, wie wir diesen Unterschied minimieren.
Die Docker Secrets lassen sich selbstverständlich auch mit dem Docker CLI und dem Basis-Befehl docker secret
nutzen. Ich möchte die Docker Secrets, allerdings wieder am Beispiel einer docker-compose.yml
erläutern.
version: "3.7"
services:
mysql:
image: mariadb:10.4
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
MYSQL_DATABASE: mydatabase
MYSQL_USER: myuser
MYSQL_PASSWORD_FILE: /run/secrets/mysql_password
secrets:
- mysql_root_password
- mysql_password
secrets:
mysql_root_password:
file: secrets/mysql_root_password.txt
mysql_password:
file: secrets/mysql_password.txt
Anmerkung der Redaktion: Die obige docker-compose.yml nutzt Version 3.7 des Standards.
Im Vergleich zur vorigen Datei fällt der neue Knoten secrets
auf, der Verwendung auf zwei Ebenen findet. Zum einen auf der Hauptebene (unten), wo in diesem Beispiel auf externe Text-Dateien verwiesen wird. Möglich wäre es auch ein externes, bereits über das CLI angelegte, Secret zu verwenden. Zum anderen wird der neue Knoten innerhalb der Service-Definition benutzt, um klarzumachen, auf welche Secrets der Dienst Zugriff haben soll. Bereitgestellt werden die Geheimnisse innerhalb des Containers, im Verzeichnis /run/secrets/SECRET_NAME
als Datei. Das MariaDB-Imageberücksichtigt die Zugangsdaten in diesem Verzeichnis standardmäßig nicht, aber die offiziellen Docker-Images akzeptieren bekannte Environment Variablen, wie MYSQL_ROOT_PASSWORD
mit dem Suffix _FILE
und dem gewünschten Pfad zum Secret. Im obigen Beispiel nutze ich für MYSQL_DATABASE
und MYSQL_USER
herkömmliche Env Vars, um den Einsatz beider Wege zu veranschaulichen. Für den Paranoia Modus lassen sich diese Werte auch über Secrets verwalten. Diese Datei ließe sich mit docker stack deploy -c docker-compose.yml mystack
wunderbar in Produktion bringen. Zuvor müssen nur die zwei Dateien mit den Passwörtern angelegt werden.
Einsatz von eigenen Images
Nun haben wir ausführlich über die Verwendung des offiziellen MariaDB-Images gesprochen. Wie sieht es bei eigenen Images aus? Wie kann man die Zugangsdaten, die in die Container gereicht werden, an den Ort bringen, wo sie benötigt werden. Hierzu wechseln wir die Perspektive und schauen nicht mehr auf eine Stack-Defintion in Form einer docker-compose.yml, sondern schauen uns ein Dockerfile einer Go-Applikation an, die eine Config-Datei erwartet. Im besten Fall ist die Applikation so flexibel, dass sie die Übergabe eines eigenen Config-Pfads beim Aufruf ermöglicht, dann hängt man den Pfad einfach beim Aufruf an, wie im Folgenden zu sehen.
FROM golang:1.8-alpine
COPY src /usr/src/app
WORKDIR /usr/src/app
RUN go build && \
chmod +x app
CMD ["./app", "-c", "/run/secrets/myconfig"]
EXPOSE 80
Bedenken sollte man dabei, dass die Secrets nur im Swarm Mode, bei der Verwendung von Services, bzw. Stacks, genutzt werden können, also aller Wahrscheinlichkeit nach vor allem auf dem Produktivsystem. Es ist aber kein Problem für die lokale Entwicklung mittels Docker Compose, eine Beispiel-Config an die gleiche Stelle, also unter /run/secrets/myconfig
zu mounten oder das Kommando zum Ausführen der Applikation mittels docker-compose.yml
zu überschreiben, wie unten stehend zu sehen.
version: "3"
services:
app:
build:
context: .
volumes:
- "./my-example-config.txt:/run/secrets/my-example-config"
command: "./app -c /run/secrets/my-example-config"
Wenn man nun allerdings Applikationen verpacken möchte, bei denen sich der Pfad zur Konfigurationsdatei gar nicht oder nur sehr aufwendig anpassen lässt, müssen wir die Config-Datei vor der Laufzeit der Applikation in Position bringen. Das erreichen wir mithilfe des folgenden Shell-Skripts, das wir im Dockerfile als ENTRYPOINT
definieren.
#!/bin/sh
set -e
SOURCE="/run/secrets/my-example-config"
DESTINATION="/etc/app-config"
if [ -s "$SOURCE" ] && [ ! -f "$DESTINATION" ]; then
ln -sf "$SOURCE" "$DESTINATION"
echo "Config link created."
else
echo "Config already exists."
fi
exec "$@"
Darin wird überprüft, ob das Secret an der erwarteten Stelle gefunden wurde und wenn noch kein Link am Ziel-Pfad existiert, wird ein Sym-Link an dieser Stelle erzeugt. Man könnte die Datei auch alternativ an den Bestimmungsort kopieren. Das würde allerdings den Docker Secrets die Möglichkeit nehmen, einem Service auch wieder Secrets zu entziehen, da die Kopie ja noch im Container verweilen würde. Anschließend folgt das passende Dockerfile dazu.
FROM golang:1.8-alpine
COPY src /usr/src/app
WORKDIR /usr/src/app
RUN go build && \
chmod +x app
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["./app", "-c", "/run/secrets/myconfig"]
EXPOSE 80
Fazit
Mit dem Secret Management hat Docker einen großen Schritt im Bereich der Produktivumgebung gemacht, wo die Secrets ihre Stärken ausspielen können. So werden die Inhalte bereits von Hause aus verschlüsselt. Im Swarm Mode lassen sich Services spielerisch Passwörter oder Ähnliches zuweisen und auch wieder entziehen. Als tmpfs-Mount werden die Infos nur flüchtig im Container gehalten, um dem hohen sicherheitstechnischen Anspruch gerecht zu werden.
Bild von Andrew Measham