Eigene Registry für Docker-Images - Ablage D
Wer selber Docker-Images herstellt, braucht auch eine eigene Registry. Diese gibt es ebenfalls als Docker-Image, aber nur mit eingeschränkter Funktionalität. Mit einem Auth-Server wird daraus ein brauchbares Repository für Images.
Ohne jemanden nahezutreten, lässt sich wohl behaupten, dass die Veröffentlichung der Docker-Software ein Schnellschuss war. Erst später wurden viele Features eingeführt, die bitter nötig waren. Ein Beispiel dafür ist die Registry, der Speicherplatz für Docker-Images, die nicht einmal einen Mechanismus zur Authentifizierung besaß. Später wurde das Image-Format geändert und die Registry 2.0 (auch: "Docker Distribution") veröffentlicht, die mit vielen Beschränkungen aufgeräumt hat. Sie besitzt immerhin einen Basismechanismus zur Authentifizierung, der sich aber auf die von Apache bekannten "htpasswd"-Dateien beschränkt.
Zugangsbeschränkung und Rechtekontrolle
Der Mechanismus beschränkt sich aber auf die Authentifizierung und bietet keine fein abgestufte Autorisierung der authentifizierten Benutzer für einzelne Ressourcen, also Lese/Schreibzugriff auf Images. Für viele Anwender, die Docker nur mal ausprobieren wollen, und Teams, in denen jeder alles darf, ist das aber ja vollkommen ausreichend. Fügen Sie also der Htpasswd-Datei einen User hinzu und starten Sie den Registry-Container, in dem Sie das Verzeichnis mit der Passwortdatei mounten. Die Authentifizierungseinstellungen übergeben Sie als Umgebungsvariablen:
$ sudo mkdir /etc/docker-registry
$ htpasswd -Bbn oliver T0Ps3crEt | sudo tee /etc/docker-registry/htpasswd
oliver:$2y$05$lAmkjHRcR0.TK52/rHR/Pe86AGZqpRleXenHVT/eabFe8He5UZiPu
$ docker run -p 5000:5000 --name registry -v /etc/docker-registry/:/auth -e "REGISTRY_AUTH=htpasswd" -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" registry:2
Nun können Sie versuchen, die Registry zum Speichern von Images zu verwenden. Dazu laden Sie zunächst ein Image aus dem offiziellen Docker Hub herunter, zum Beispiel mit docker pull alpine:latest ein Image der platzsparenden Alpine-Distribution. Um das Image in die eigene Registry laden zu können, müssen Sie es mit dessen Hostnamen taggen:
$ docker tag alpine:latest remote.repository.com:5000/alpine-latest
Wenn Sie nun versuchen, das Image hochzuladen, erhalten Sie eine Fehlermeldung "unauthorized: authentication required". Sie müssen sich erst mit den oben vergebenen Credentials per docker login ... anmelden; anschließend klappt es auch mit dem Upload (Listing 1). In dem Listing sehen Sie auch, dass die Login-Daten in "$HOME/.docker/config.json" gespeichert werden. Um sie von dort wieder zu entfernen, verwenden Sie docker logout remote.repository.com:5000.
Listing 1: Login und Upload in die Registry
$ docker login remote.repository.com Authenticating with existing credentials... Login did not succeed, error: Error response from daemon: login attempt to https://remote.repository.com:5000/v2/ failed with status: 401 Unauthorized Username (ofrommel): oliver Password: WARNING! Your password will be stored unencrypted in /home/oliver/.docker/config.json. Configure a credential helper to remove this warning. See https://docs.docker.com/engine/reference/commandline/login/#credentials-store $ Login Succeeded docker push remote.repository.com:5000/alpine:latest The push refers to repository [remote.repository.com:5000/alpine] 73046094a9b8: Pushed latest: digest: sha256:0873c923e00e0fd2ba78041bfb64a105e1ecb7678916d1f7776311e45bf5634b size: 528
Um sich einmal mit Docker vertraut zu machen, mag diese Lösung auch genügen. Meistens will man aber noch etwas darüber hinausgehende Fähigkeiten wie etwa die oben angesprochene Autorisierung hinsichtlich einzelner Ressourcen. Konkret kann das etwa bedeuten, dass ein Benutzer uneingeschränkte Lese- und Schreibrechte für seinen Namespace ("remote.repository.com/User") erhält, aber nur Leserechte für andere Images.
Dieses lässt sich über die Identity-Management-API der Docker Registry realisieren, die tokenbasiert funktioniert. Will ein Anwender auf die Registry zugreifen, leitet der Server den Client auf einen Authentifizierungsserver um, der Login/Passwort überprüft und ein Token ausstellt (Bild 1). Darüber hinaus ist in dem Scope-Feld des Tokens festgelegt, welche Berechtigungen der Benutzer im Einzelnen besitzt.
docker_auth als Auth-Server
Implementiert wird der beschriebene Ablauf beispielsweise von der freien Software "docker_auth", die von der Firma Cesanta entwickelt wurde und auf GitHub [1] wie auch als Image im Docker Hub zu finden ist. docker_auth bietet zur Authentifizierung die folgenden Methoden: eine statische Benutzerliste, ein Login mittels Google oder GitHub, eine LDAP-Anbindung, MongoDB oder ein externes Programm. Zur Autorisierung können statische ACLs, MongoDB oder ein externes Programm verwendet werden.
Zur Konfiguration dient eine YAML-Datei, die Sie dem Container wie gewohnt per Bind-Mount als Volume unterschieben. Dies gilt auch für die TLS-Zertifikate, die docker_auth verwendet. Beispiele für die Konfiguration finden Sie im GitHub-Repository im Verzeichnis "examples", wo auch die Datei "reference.yml" liegt, die sämtliche verfügbaren Optionen mit Kommentaren enthält. Ein Beispiel für die Konfiguration, die auf Backends wie MongoDB verzichtet und sowohl die User-Accounts wie auch die ACLs bereits enthält, ist in Listing 2 zu sehen.
Listing 2: Auth-Server-Konfiguration
server: addr: ":5001" certificate: "/le/live/remote.repository.com/cert.pem" key: "/le/live/remote.repository.com/privkey.pem" token: issuer: "MyRepository auth server" # muss mit Registry-Config übereinstimmen! expiration: 900 users: # Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate. "oliver": password: "$2y$05$4dIrCZLpgSYDClrS6pN2BOxVm.rkPy/4IgnurlHbukOxOJldlhJM." acl: - match: {account: "admin"} actions: ["*"] comment: "Admin has full access to everything." - match: {account: "user"} actions: ["pull"] comment: "User \"user\" can pull stuff."

Wieder werden die Hashes der Passwörter abgelegt, die sich mit Htpasswd erzeugen lassen. Die ACLs bestehen aus drei Elementen. "match" gibt an, für wen oder was die Regel gilt, "actions" spezifiziert, was damit gemacht werden darf, dann folgt noch ein Kommentar. Hier gibt es viele Möglichkeiten, den Zugriff zu regeln, bei denen man zum Beispiel Regular Expressions verwenden kann. Richtige Gruppen gibt es nicht, aber sie lassen sich mit den sogenannten Labeln simulieren. Auch eine Einschränkung auf IP-Adressen ist möglich. Die Dokumentation enthält eine ganze Reihe von Beispielen für ACLs, die sich auch per Copy-and-paste übernehmen lassen. Den Auth-Server starten Sie nun so:
$ docker run --name docker_auth -p 5001:5001 -v `pwd`:/config:ro \-v /var/log/docker_auth:/logs \-v /etc/letsencrypt:/le \cesanta/docker_auth:1 /config/config.yml
Auch die Docker-Registry, die wir oben mit Umgebungsvariablen gestartet haben, lässt sich mit einer YAML-Datei konfigurieren. Jede Einstellung in der Datei entspricht einer Umgebungsvariablen, die den YAML-Pfad durch Unterstriche trennt. Der erste Wert "REGISTRY" fällt dabei weg, da er nur den Container beziehungsweise die Anwendung kennzeichnet. Für "REGISTRY_AUTH_HTPASSWD_PATH" würde also folgende Struktur in der YAML-Datei entsprechen:
auth:
htpasswd:
path: "/auth/htpasswd"
Der konkrete Wert kommt allerdings im Folgenden nicht zum Einsatz, da wir ja statt der Htpasswd-Authentifizierung den Auth-Server verwenden möchten. Die entsprechende Konfiguration ist in Listing 3 zu sehen. Neben dem Speicherort der Registry-Daten ("storage/filesystem/rootdirectory"), der sich natürlich nicht im Container, sondern auf dem Host befindet, ist vor allem das Authentifizierungs-Realm interessant, hinter dem sich der Auth-Server befindet. Außdem verwenden wir Let's-Encrypt-Zertifikate, die ebenfalls auf dem Host installiert sind.
Listing 3: Registry-Konfiguration
version: 0.1 log: fields: service: registry storage: filesystem: rootdirectory: /var/lib/registry http: addr: :5000 tls: certificate: "/le/live/remote.repository.com/cert.pem" key: "/le/live/remote.repository.com/privkey.pem" auth: token: realm: "https://remote.repository.com:5001/auth" service: "Docker registry" issuer: "MyRepository auth server" rootcertbundle: "/le/live/remote.repository.com/fullchain.pem"
Ist die Konfigurationsdatei unter dem Namen "config.yml" im aktuellen Verzeichnis gespeichert, starten Sie die Registry mit dem folgenden Aufruf:
docker run -p 5000:5000 --name registry \
-v `pwd`/config.yml:/etc/docker/registry/config.yml \
-v /var/docker-registry:/var/lib/registry \
-v /etc/letsencrypt:/le registry:2
Nun können Sie die Registry wie oben gezeigt verwenden. User-Accounts und ACLs legen Sie wie beschrieben in der Konfigurationsdatei von docker_auth ab und starten den Container neu. In der Log-Ausgabe der Registry können Sie sich die einzelnen Requests ansehen, was auch bei der Fehlersuche hilft (Bild 2). Etwas dynamischer als mit der Konfigurationsdatei wird das Setup mit MongoDB als Datenbank. Hier kann die Konfiguration geändert werden, ohne den Container immer neu starten zu müssen. Komfortabler wird die Administration allerdings nicht unbedingt, denn die ACLs werden in der gleichen Syntax wie in der Konfigurationsdatei in der Datenbank gespeichert (Bild 3). Zur Authentifizierung ist, wie erwähnt, für viele Anwender sicher auch LDAP interessant, aber die ACLs müssen dabei trotzdem auf einem anderen Weg hinterlegt werden, sei es im File oder in MongoDB.

Probleme mit Zertifikaten umgehen
Wenn Sie eine Fehlermeldung über ein unsicheres Zertifikat erhalten, besteht die einfachste Lösung darin, den Docker-Daemon mit der Option "--insecure-registry" zu starten, gefolgt von einer Liste der Hostnamen ihrer Registries inklusive Port. Dies müssen Sie auf jedem Client tun, der die Registry verwenden möchte.
Der Grund für die Fehlermeldung liegt darin, dass beispielsweise aktuelle Linux-Distributionen das Root-Zertifikat von Let's Encrypt nicht in ihrer Keychain installiert haben, auf die Standard-Tools wie der Docker-Daemon (über die Krypto-Libraries) zurückgreifen – während etwa die Webbrowser eigene Keychains mitbringen. Listing 4 zeigt, wie Sie die Root-Zertifikate auf einem Ubuntu-System installieren und aktivieren. Dies müssen Sie nun allerdings auf allen Rechnern tun, von denen Sie oder Ihre Kollegen Docker verwenden, also möglicherweise auch auf Rechnern mit Windows oder macOS – da ist die Option mit den "--insecure-registries" sicher die einfachere.
Listing 4: Zertifikate von Let's Encrypt installieren
curl -O https://letsencrypt.org/certs/isrgrootx1.pem curl -O https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem openssl x509 -in isrgrootx1.pem -inform PEM -out isrgrootx1.crt openssl x509 -in lets-encrypt-x3-cross-signed.pem -inform PEM -out lets-encrypt-x3-cross-signed.crt sudo cp *.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates –verbose I already trust 148, your new list has 150 Certificate added: C=US, O=Let's Encrypt, CN=Let's Encrypt Authority X3 1 new root certificates were added to your trust store. ... sudo systemctl restart docker

Registry-Betrieb als Proxy-Cache
Interessant dürfte für viele auch der Betrieb einer eigenen Registry als Proxy sein, von der Docker-Dokumentation auch "pull through cache" genannt. Dies ist kein großes Problem. Es genügt, in der Konfigurationsdatei die Anweisung "proxy" hinzuzufügen und darunter die URL der Upstream-Registry einzutragen:
proxy:remoteurl: https://registry-1.docker.io
Eine weitere Option ist es, die Registry wie auch den Auth-Server hinter einem Proxy wie Apache, Nginx oder HAProxy zu betreiben. Dann fällt die Portnummer aus den Registry-URLs heraus und Sie haben auf dem Server zwei offene Ports weniger. Typischerweise erledigen Sie dann die TLS-Terminierung auch über den Proxy, dann kann die Verschlüsselung bei den Servern wegfallen. Sie müssen nur bei der Proxy-Konfiguration anhand der aufgerufenen URLs zwischen den Backends differenzieren. Eine solche Konfiguration für HAProxy können Sie in Listing 5 sehen.
Listing 5: HAProxy-Konfiguration
use_backend registry if { hdr_end(host) -i remote.repository.com } { path_beg /v2 } use_backend registry_auth if { hdr_end(host) -i remote.repository.com } { path_beg /auth } … backend registry server registry 127.0.0.1:5000 backend registry_auth server registry_auth 127.0.0.1:5001
Andere Docker-Registries
Eine Alternative zum Einsatz von docker_auth als Authentifizierungsbackend ist beispielsweise der Keycloak-Server von Red Hat [2]. Er steht ebenfalls als freie Software zur Verfügung, bietet zahlreiche weitere Features für Single Sign-on und auch ein webbasiertes Frontend – allerdings von Haus aus keine Möglichkeit zur Regelung der Autorisierung. Ein weiteres Projekt aus dem Hause Red Hat ist der Pulp-Repository-Server [3], der mittlerweile auch Docker unterstützt, aber wiederum nur rudimentär.
Wer gleich die komplette Registry inklusive Zugangskontrolle ersetzen möchte, kann sich die beiden Open-Source-Projekte Portus [4] von Suse und Harbor [5] von VMware anschauen. Beide sind aber in aktiver Entwicklung und nicht ganz einfach zu installieren und zu konfigurieren. Leichter geht es mit kommerziellen Produkten wie der Artifactory [6] von JFrog oder Sonatype Nexus, die neben Docker-Images auch zahlreiche weitere "Artefakte" hosten können, etwa Pakete für Apache Maven, RubyGems, RPMs und so weiter. Hier empfiehlt sich insbesondere ein Blick auf Sonatype Nexus [7], dessen Open-Source-Variante beinahe den kompletten Funktionsumfang der Enterprise-Ausführung bietet – abgesehen von Features wie Hochverfügbarkeit und dem Support durch den Hersteller.
Fazit
Eine eigene Registry für Docker-Images zu betreiben, ist nicht schwierig. Etwas aufwendiger wird es, wenn zur dateibasierten Authentifizierung noch eine Autorisierung für einzelne Ressourcen oder eine Anbindung an ein LDAP-Backend dazukommen soll. Beides lässt sich aber mit der freien Software "docker_auth" lösen. Als Alternativen bieten sich kommerzielle Docker-Registries an, die es zum Teil auch als leicht abgespeckte Open-Source-Varianten gibt.