Abgesichert: Docker auf CircleCI mit TLS ausfĂĽhren
CircleCI ist eine Cloud-Plattform für Continuous Integration und Continuous Delivery. CircleCI lässt sich detailliert anpassen und integriert verschiedene Dienste wie MongoDB, RabbitMQ und Docker. Allerdings ist Docker weder per TCP noch per TLS angebunden. Wie lässt sich das ändern?
- Golo Roden
CircleCI ist eine Cloud-Plattform für Continuous Integration und Continuous Delivery. CircleCI lässt sich detailliert anpassen und integriert verschiedene Dienste wie MongoDB, RabbitMQ und Docker. Allerdings ist Docker weder per TCP noch per TLS angebunden. Wie lässt sich das ändern?
Die Konfiguration eines Builds auf CircleCI kann auf drei Wegen erfolgen. Im Normalfall versucht die Plattform, die passenden Einstellungen eigenständig vorzunehmen. Ist eine komplexere Umgebung erforderlich, lassen sich die Details in der grafischen Benutzeroberfläche oder von Hand in der Datei circle.yml vornehmen, die gemeinsam mit dem Projekt in die Versionsverwaltung eingecheckt wird.
Um die Kontrolle ĂĽber einen Build zu wahren, empfiehlt sich die generelle Verwendung der Datei circle.yml, selbst wenn die darin vorgenommenen Einstellungen lediglich sehr simpel sind:
machine:
timezone: Europe/Berlin
node:
version: 0.12.0
dependencies:
pre:
- npm install -g grunt-cli
test:
override:
- grunt
Werden Dienste wie MongoDB, RabbitMQ oder Docker benötigt, lassen sich diese über einen Eintrag im Abschnitt machine/services anfordern:
machine:
services:
- rabbitmq-server
- docker
Während das Vorgehen beispielsweise für RabbitMQ wunderbar funktioniert, muss die Installation von Docker mit dem Notwendigsten auskommen: So ist Docker weder per TCP noch verschlüsselt ansprechbar. Beides lässt sich jedoch mit einigen wenigen Zeilen in der Datei circle.yml ändern.
Umgebungsvariablen setzen
Dazu sind zunächst die Umgebungsvariablen DOCKER_HOST, DOCKER_TLS_VERIFY und DOCKER_CERT_PATH zu setzen. Alle drei benötigt der Client, um sich auf den korrekten Server verbinden zu können. Den Host zu setzen, fällt verhältnismäßig leicht, da sich dessen IP und Port beim Start des Servers frei wählen lassen:
machine:
environment:
DOCKER_HOST: tcp://127.0.0.1:2376
Der übliche Port von Docker ist zwar 2375, für TLS-verschlüsselte Verbindungen empfiehlt die Dokumentation von Docker jedoch 2376. Diese zu erzwingen, fällt ebenfalls ausgesprochen leicht:
machine:
environment:
DOCKER_HOST: tcp://127.0.0.1:2376
DOCKER_TLS_VERIFY: yes
Ein wenig schwieriger ist die Angabe des Pfades zu den Zertifikaten, die später noch zu erstellen sind. CircleCI stellt das Arbeitsverzeichnis des Projekts nicht als Umgebungsvariable zur Verfügung, dafür aber den Namen des Projekts. Dazu dient die Umgebungsvariable $CIRCLE_PROJECT_REPONAME, die in Verbindung mit $HOME den gewünschten Pfad ergibt:
machine:
environment:
DOCKER_HOST: tcp://127.0.0.1:2376
DOCKER_TLS_VERIFY: yes
DOCKER_CERT_PATH: $HOME/$CIRCLE_PROJECT_REPONAME/certificates
Docker konfigurieren und neu starten
Nun gilt es, Docker mit einigen zusätzlichen Optionen zu konfigurieren und den Server anschließend neu zu starten. Die Konfiguration des Docker-Servers erfolgt in der Variablen DOCKER_OPTS in der Datei /etc/default/docker. Die Datei ist allerdings dem root-Benutzer vorbehalten, weshalb die Änderungen mit sudo durchgeführt werden müssen.
Naheliegend wäre, die darin bereits enthaltene Variable zu ändern, beispielsweise durch den Einsatz von sed. Viel einfacher ist es allerdings, einfach eine weitere Zeile anzufügen, die die gewünschten Werte enthält. Findet Docker mehrere Einträge in der Datei, verwendet es den letzten. Daher kann der vorige Wert gefahrlos in der Datei verbleiben.
Die tatsächliche Zeile ist ein wenig unübersichtlich, weshalb sie nachfolgend umgebrochen wird. Der Eintrag in der Datei circle.yml muss allerdings in einer einzigen Zeile erfolgen:
test:
pre:
- echo "DOCKER_OPTS=\"-s btrfs -e lxc
-H=tcp://127.0.0.1:2376
--tlsverify
--tlscacert=$DOCKER_CERT_PATH/ca.pem
--tlscert=$DOCKER_CERT_PATH/server-cert.pem
--tlskey=$DOCKER_CERT_PATH/server-key.pem\""
| sudo tee --append /etc/default/docker
Anschließend gilt es lediglich noch, Docker mit der geänderten Konfiguration neu zu starten:
test:
pre:
- echo "DOCKER_OPTS=..." | sudo tee --append /etc/default/docker
- sudo service docker restart
Danach lässt sich Docker, beispielsweise in den Tests oder dem Deployment des Projekts, wie gewohnt über TCP und TLS ansprechen.
Zertifikate erzeugen
Die einzige Stolperfalle stellen nun noch die fehlenden Schlüssel und Zertifikate dar, die sich jedoch – gewusst wie – mit wenigen Kommandos erzeugen lassen. Es empfiehlt sich, sie einmalig zu erzeugen und direkt in das Projekt einzubinden. Im Beispiel wurde dafür das Verzeichnis ./certificates gewählt.
Zunächst muss der private Schlüssel für ein CA-Zertifikat erstellt werden. Dazu dient das folgende OpenSSL-Kommando:
$ openssl genrsa -aes256 -out ca-key.pem 2048
Anschließend lässt sich auf Basis dieses Schlüssels das CA-Zertifikat erzeugen, wobei eine Laufzeit von zehn Jahren angegeben wird. Zudem sind interaktiv einige Fragen zu beantworten, deren Beantwortung jedoch beliebig ausfallen kann:
$ openssl req -new -x509 -days 3650 -key ca-key.pem -sha256 -out ca.pem
Nun wird ein privater Schlüssel für den Server benötigt. Dieser lässt sich mit dem Befehl
$ openssl genrsa -out server-key.pem 2048
erzeugen. Danach kann man mit diesem SchlĂĽssel einen CSR erzeugen, um einen Zertifikatsantrag zu erstellen:
$ openssl req -subj "/CN=127.0.0.1" -new -key server-key.pem \
-out server.csr
Es empfiehlt sich, die IP-Adresse des Docker-Servers zusätzlich auch als Subject Alternative Name (SAN) im Zertifikat zu hinterlegen. Dazu dienen die beiden folgenden Kommandos:
$ echo subjectAltName = IP:127.0.0.1 > extfile.cnf
$ openssl x509 -req -days 3650 -in server.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out server-cert.pem \
-extfile extfile.cnf
Nun fehlt noch der private Schlüssel für die Authentifizierung des Clients. Dieser lässt sich mit dem Kommando
$ openssl genrsa -out key.pem 2048
erstellen. Das dazu passende und mit den geeigneten Rechten ausgestattete Zertifikat lässt sich mit einigen weiteren Aufrufen erzeugen:
$ openssl req -subj '/CN=client' -new -key key.pem -out client.csr
$ echo extendedKeyUsage = clientAuth > extfile.cnf
$ openssl x509 -req -days 365 -in client.csr -CA ca.pem \
-CAkey ca-key.pem -CAcreateserial -out cert.pem \
-extfile extfile.cnf
Abschließend lassen sich einige nicht länger benötigte Dateien gefahrlos löschen:
$ rm -v client.csr server.csr
Mit dem Löschen der Dateien sind die Arbeiten für den Start von Docker mit TCP und TLS abgeschlossen. Die Datei circle.yml kann nun um weitere Anweisungen für den Test und das Deployment ergänzt werden, wobei sich Docker wie gewünscht verwenden lässt.
tl;dr: CircleCI unterstĂĽtzt Docker, allerdings weder mit TCP noch mit TLS. Einige Zeilen in der Datei circle.yml genĂĽgen jedoch, den Netzwerkzugriff und die VerschlĂĽsselung zu aktivieren. ()