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?

vorlesen Druckansicht
Lesezeit: 6 Min.
Von
  • Golo Roden
Inhaltsverzeichnis

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.

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

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.

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. ()