Neuigkeiten von trion.
Immer gut informiert.

Container Images absichern mit cosign

Supply Chain Security spielt auch im Container Umfeld eine immer wichtigere Rolle. Es gilt dabei die Sicherheit im Sinne von Echtheit und Integrität der (Docker) Images abzusichern.
Typische Supply Chain Angriffsszenarien bei Kubernetes, Docker und anderen Containerumgebungen sind dabei:

  • Verwendung manipulierte Basis-Images

  • Manipulation von Images in der Registry

  • Man-in-the-Middle Angriffe beim Transport

  • Irrtümer und Social-Engineering (z.B. Typosquatting)

Um gegen derartige Angriffe zu schützen ist ein Element die kryptografische Absicherung der Container Images durch eine Signatur. Durch eine Signatur der jeweiligen Images ist nicht nur die Integrität sondern auch die Herkunft gewährleistet. Ein Werkzeug dazu ist cosign, das im folgenden im Detail betrachtet wird.

Signatur

Durch eine Signatur werden zwei Eigenschaften sichergestellt: Echtheit und Integrität. Kryptografische Signaturen basieren dabei auf mathematischen Einwegfunktionen, die es erlauben einen öffentlichen und privaten Schlüssel zu besitzen, die miteinander in Beziehung stehen, jedoch aus dem öffentlichen Teil der private Teil nicht ermittelt werden kann. Zur Signatur wird eine kryptografische Prüfsumme (Hash) der zu signierenden Daten erzeugt, die mit dem privaten Schlüssel verschlüsselt wird.

Am Beispiel eines Container Images könnte z.B. eine SHA256 Prüfsumme aus der Container Datei gebildet werden. Nehmen wir an, dass die Prüfsumme 1234 ist. Diese wird nun mit dem privaten Schlüssel "verschlüsselt", wir können hier einfach annehmen, dass das Ergebnis 4567 ist. Diese Signatur wird nun dem Container Image als zusätzliche Information beigefügt.
Dazu muss entweder das Container Format oder die Registry die Möglichkeit vorsehen, dieses zusätzliche Datum bereitzustellen.

Mit cosign sieht der Prozess dann konkret folgendermaßen aus. Zunächst muss die zur Plattform passende Version von cosign installiert werden. Hier gibt es verschiedene Wege, einer ist das passende Artefakt von https://github.com/sigstore/cosign/releases herunterzuladen.

Anschließend kann cosign entweder im Keyless-Modus, mit Hardwarekryptomodulen, oder mit eigenem Schlüsselmaterial verwendet werden. Letzteres veranschaulicht am besten den oben skizzierten Prozess.

Erzeugung eines cosign Schlüsselpaars
$ cosign generate-key-pair
Enter password for private key:
Enter again:
Private key written to cosign.key
Public key written to cosign.pub

Der öffentliche Schlüssel kann durch cosign exportiert werden. Mit diesem Schlüssel lassen sich beispielsweise Konfigurationen erzeugen, um Vorgaben umzusetzen, die Sicherstellen, dass Images von genau diesem Ursprung stammen.

Export des öffentlichen cosign Schlüssels
$ cosign public-key --key cosign.key
Enter password for private key:
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4sxuLukpMeMYk3b1AWC2jtXS7KLc
oCA2kQ/m9E57zGxRMYt591fA3mjUoyUMZiYRvLaaq7GjAj/5aslzWXYO1Q==
-----END PUBLIC KEY-----

Bei der Signatur ist zu beachten, dass Container Images stets eine eindeutige Hash-Referenz besitzen und dazu ein eher symbolischer Name gehört. Der Name wird zum Zeitpunkt der Nutzung zu einem konkreten Image (mit eindeutigem Hash-Namen) aufgelöst. Dabei kann durch die verwendetet Architektur und natürlich in Abhängigkeit des Zeitpunkts ein jeweils anderes Image aufgelöst werden.
Es ist daher empfehlenswert, ein Image bei der Signaturerstellung über den eindeutigen Image-Hash zu referenzieren.
Alternativ löst cosign zum Zeitpunkt der Signatur den Image-Hash auf, doch dies Feature soll zukünftig sogar entfernt werden.

Durch docker image ls --digests können die Hash-Referenzen lokaler Images abgefragt werden.

Abfrage der Image Digests
$ docker images --digests
REPOSITORY   TAG     DIGEST            IMAGE ID  CREATED       SIZE
trion/ng-cli 17.3.5 sha256:c348cdad... b300c372  5 months ago  742MB
trion/ng-cli latest sha256:9f81d283... 21bfa741  5 months ago  740MB

Die erzeugte Signatur wird durch cosign standardmäßig direkt in die Registry des jeweiligen Images als zusätzliches Element gepusht.

Signatur eines Images mit cosign
$ cosign sign --key cosign.key trion/ng-cli
Enter password for private key:
tlog entry created with index: 131218664
Pushing signature to: index.docker.io/trion/ng-cli

Dazu wird eine definierte URL verwendet. Dadurch lässt sich für jedes Image in einer Registry auch direkt abfragen, ob eine Signatur vorliegt.

Abfrage der Signatur-URL
$ cosign triangulate trion/ng-cli
index.docker.io/trion/ng-cli:sha256-287ea13ecb4fcaac9389a26100c474aa8e7278753f9264719be47354a142b916.sig

Je nach Registry kann eine Signatur durch cosign clean IMAGE auch wieder gelöscht werden.

Um den Signatur-Upload zu unterdrücken, kann mit `--upload=false das Verhalten deaktiviert werden. Die Signatur kann dann in einer lokalen Datei mit --output-signature gespeichert werden. Wird zudem auch kein Timestamp-Logserver verwendet, kann eine Signatur komplett offline erzeugt werden.

Offline Signatur eines Images mit cosign
$ cosign sign --key cosign.key --upload=false --tlog-upload=false trion/ng-cli

Dabei wird die Signatur, Zertifikat bzw. Payload in einer separaten Datei gespeichert. Ein Image kann auch lokal mit Signatur Attachment versehen werden. Dieser Air-Gapped Modus kommt typischerweise beim Transport von Images zum Einsatz.

Air-Gapped offline Signatur eines Images mit cosign
$ cosign sign --key cosign.key --upload=false --tlog-upload=false --output-payload=image.json --output-signature=image.sig trion/ng-cli

Diese Daten können nun zur Validierung des Containerimage genutzt werden.

Validierung

Grundsätzlich wird bei der Validierung einer Signatur folgendermaßen vorgegangen:
Zunächst wird der Hash der zu verifizierenden Daten (erneut) berechnet. Entsprechend dem obigen Beispiel wäre die Prüfsumme wieder 1234, wenn keine Daten modifiziert wurden. Nun wird die Signatur, aus dem Beispiel wäre dies 4567, mit dem öffentlichen Schlüssel entschlüsselt und das Ergebnis mit dem lokal berechneten Hash verglichen. Wenn diese übereinstimmen, dann ist die Signatur gültig.

Bei cosign wird dies im Prinzip genauso durchgeführt, dabei gibt es jedoch noch einige Erweiterungen: Das Transparency Log und der Timestamp Server, die dazu dienen, die Integrität, Authentizität und Nachvollziehbarkeit der Signaturen zu gewährleisten.

Ein Transparency Log ist eine unveränderliche, öffentliche, Datenstruktur, an die lediglich neue Informationen angefügt werden können. Im Transparency Log werden Signaturen, Zertifikate und andere Metadaten speichert und zur öffentlichen Nachvollziehbarkeit zur Verfügung stellt. Cosign verwendet dabei den Dienst Rekor, das ebenfalls Teil des Sigstore-Projekts ist, und als kostenlose öffentliche Instanz bereitsteht. Rekor kann jedoch ebenfalls in eigener Infrastruktur betrieben werden.

Mit dem Timestamp Server wird ein kryptografischer Beweis für den Zeitpunkt, zu dem eine Signatur erstellt wurde, bereitgestellt. Der Zeitstempel wird von einem vertrauenswürdigen Server erstellt und anschließend zusammen mit der Signatur in das Transparency Log eingebettet. Damit werden spezielle Arten von Angriffen, beispielsweise ein altes, signiertes, Image mit einer Sicherheitslücke als aktuelles auszugeben, abgewehrt. Neben der öffentlichen Instanz steht mit dem Timestamp-Authority Projekt eine Implementierung für den eigenen Betrieb bereit.

Ohne diese beiden Dienst ist es möglich eine Offline/Air-Gapped Validierung durchzuführen. Passend zu dem obigen Beispiel des signierten Images wird mit cosign verify dann validiert.

Air-Gapped offline Signatur eines Images validieren (ohne Transparency-Log und Timestamp-Validierung)
$  cosign verify --insecure-ignore-tlog=true  --offline=true --key cosign.pub --payload image.json --signature image.sig trion/ng-cli@sha256:287ea13ecb4fcaac9389a26100c474aa8e7278753f9264719be47354a142b916

Im regulären Modus werden die Signatur als OCI Artifact in der Registry gespeichert und das öffentliche Rekor Transparency Log verwendet. Der Ablageort in der Registry wird vom Ort des jeweiligen Images abgeleitet und kann mit cosign triangulate auch ausgegeben werden.
Damit ist es dann möglich zu prüfen, ob für ein Image überhaupt cosign Signaturen in der Registry abgelegt sind. Als Werkzeug kann dazu crane oder ein beliebiger HTTP Client genutzt werden, mit dem die Registry API abgefragt wird.

Ermittlung und Abfrage der Signatur für das Google Distroless Java Image
$ cosign triangulate gcr.io/distroless/java21
gcr.io/distroless/java21:sha256-4ef80b38c61881bdd4d682df9989a9816f4926f8fb41eaaf55d54a6affe6a6c2.sig
$ crane manifest gcr.io/distroless/java21:sha256-4ef80b38c61881bdd4d682df9989a9816f4926f8fb41eaaf55d54a6affe6a6c2.sig | jq .

$ TOKEN=$(http https://gcr.io/v2/token\?scope\=repository%3Adistroless%2Fjava21%3Apull\&service\=gcr.io | jq -r .token)
$ http --auth-type=bearer --auth="$TOKEN" https://gcr.io/v2/distroless/java21/manifests/sha256-4ef80b38c61881bdd4d682df9989a9816f4926f8fb41eaaf55d54a6affe6a6c2.sig

Fazit

Mit cosign gibt es ein etabliertes und verbreitetes Ökosystem, um Container Images gegen Manipulation abzusichern. Dabei ist es ratsam entsprechende Automatisierungen in der CI/CD Pipeline und regelmäßige Aktualisierungen der Images als Prozess zu nutzen. Die Validierung, ob und wie ein Image signiert ist, muss entsprechend bei der Verwendung vorgesehen werden. So kann bei der Erstellung abgeleiteter Images sichergestellt werden, dass eine aktuelle und sichere Version verwendet wird. Dasselbe gilt für den Einsatz im Cluster, auch hier muss mit geeigneten Verfahren Sorge getragen werden, dass nur aktuelle, signierte, Images von vertrauenswürdigen Herausgebern verwendet werden.




Zu den Themen Kubernetes, Docker und Cloudarchitektur bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Zur Desktop Version des Artikels