Sichere Docker images für cloud Anwendungen erstellen
Docker images können leicht mehrere hundert MB groß werden. Während die Größe bei der Übertragung nur störend ist, stellen die vielen unnötigen Komponenten ein Sicherheitsproblem dar. Jede zusätzliche Komponente erhöht die Angriffsfläche und damit die Gefahr auf einen erfolgreichen Angriff.
Ein Docker image sollte keine interaktiven tools wie z.B. bash
enthalten,
sondern nur die Komponenten, die für den Betrieb unverzichtbar sind.
Durch die Verwendung des Docker scratch
image, wird genau dieses Ziel erreicht.
Das scratch
image enthält ein minimales Linux System ohne jegliche weitere Komponenten.
Dieses Tutorial zeigt anhand einer http server Anwendung,
wie ein sicheres Docker image für den cloud Betrieb erstellt werden kann.
Erstelle eine Datei app.d
mit diesem Inhalt:
/+ dub.sdl:
name "app"
dependency "vibe-d:http" version="0.8.6"
dependency "vibe-d:tls" version="*"
subConfiguration "vibe-d:tls" "openssl-1.1"
+/
import std.process : environment;
import vibe.core.core : runApplication;
import vibe.http.server;
void main()
{
string port = environment.get("PORT", "8080");
listenHTTP("0.0.0.0:" ~ port, &handleRequest);
runApplication();
}
private void handleRequest(HTTPServerRequest req, HTTPServerResponse res)
{
if (req.path == "/")
res.writeBody("Hello, World!");
}
Die Umgebungsvariable PORT
wird ausgelesen und der http Server für diesen
Port gestartet. Falls die Umgebungsvariable nicht definiert ist, wird als
Port 8080
verwendet.
Erstelle eine Datei Dockerfile
mit diesem Inhalt:
FROM ubuntu:focal as base
RUN apt-get update && apt-get upgrade -y \
&& apt-get install --no-install-recommends -y build-essential ldc dub zlib1g-dev libssl-dev
COPY app.d /tmp
RUN dub build --single /tmp/app.d
RUN mkdir -p /dist/opt/ && cp /tmp/app /dist/opt/
WORKDIR /dist
RUN { ldd /dist/opt/app; } | tr -s '[:blank:]' '\n' | grep '^/' | \
xargs -I % sh -c 'mkdir -p $(dirname ./%); cp % ./%;'
FROM scratch as final
COPY --chown=0:0 --from=base /dist /
COPY --from=base /etc/passwd /etc/passwd
COPY --from=base /etc/group /etc/group
USER www-data
ENV LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:/lib64:/usr/lib/x86_64-linux-gnu
CMD ["/opt/app"]
Das Dockerfile besteht aus den zwei stages base
und final
. Im stage base
wird der LDC compiler und zusätzliche Abhängigkeiten installiert. Danach wird die http server Anwendung kompiliert und nach /dist/opt/
kopiert.
Am Ende von stage base
werden alle zusätzlichen Abhängigkeiten (Dynamische Bibliotheken) der
Anwendung ermittelt und in ein Unterverzeichnis von /dist/
kopiert.
Nur die Dateien, die sich im stage final
befinden, werden auch im Docker image verfügbar sein.
Der Inhalt von /dist
wird aus dem base
stage nach final
kopiert.
Um die Sicherheit weiter zu erhöhen, wird der Benutzer www-data
gesetzt. Dies erfordert,
das zuvor die Dateien /etc/passwd
und /etc/group
aus stage base
nach final
kopiert werden.
Stage final
endet mit der Angabe, unter welchen Pfaden die dynamischen Bibliotheken zu finden sind
und die http server Anwendung wird als Docker image Startanwendung gesetzt.
Öffne eine Kommandozeile und erstelle das Docker image mit docker build
. Danach starte einen
container mit docker run
:
docker build -t sample .
docker run -it --rm -p 8080:8080 sample
Öffne einen webbrowser. Der http server ist unter der Adresse http://localhost:8080 erreichbar.
Alpine als Basis
Im vorrigen Beispiel basierte der stage base
auf der Linux Distribution Ubuntu
.
Statt dessen kann aber auch eine andere Linux Distribution, wie z.B. Alpine
genommen werden.
An dem Dockerfile ändern sich nur Kleinigkeiten. Die Paketverwaltung hat einen anderen Namen
und auch die Pakete selbst heißen anders. Auch muss der Benutzer www-data
und die dazu gehörige
Gruppe www-data
angelegt werden.
FROM alpine:3.12 as base
RUN apk add --update alpine-sdk ldc dub openssl-dev zlib-dev
COPY app.d /tmp
RUN dub build --single /tmp/app.d
RUN mkdir -p /dist/opt/ && cp /tmp/app /dist/opt/
WORKDIR /dist
RUN { ldd /dist/opt/app; } | tr -s '[:blank:]' '\n' | grep '^/' | \
xargs -I % sh -c 'mkdir -p $(dirname ./%); cp % ./%;'
RUN set -x ; \
addgroup -g 82 -S www-data ; \
adduser -u 82 -D -S -G www-data www-data && exit 0 ; exit 1
FROM scratch as final
COPY --chown=0:0 --from=base /dist /
COPY --from=base /etc/passwd /etc/passwd
COPY --from=base /etc/group /etc/group
USER www-data
ENV LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:/lib64:/usr/lib/x86_64-linux-gnu
CMD ["/opt/app"]