Skip to main content

GitHub Action zum Scannen von Docker-Images einrichten

feature docker secrets

19. Mai 2023

0 Min. Lesezeit

Heutzutage ist das Endprodukt der meisten Git-Repositories ein Docker-Image, das anschließend in einem Kubernetes-Deployment verwendet wird. Da Sicherheit aktuell ein wichtiges Thema ist – und das aus gutem Grund –, ist es entscheidend, die Docker-Images, die Sie in der CI erstellen, zu scannen.

In diesem Beitrag verwende ich GitHub Actions, um Docker-Images zu erstellen und anschließend auf Sicherheitslücken zu scannen. Das in der CI erstellte Docker-Image wird außerdem in die Docker-Registry von GitHub übertragen.

Kaffeetasse mit Latte Art unter dem Text „Docker-Image-Scan in CI mit GITHUB“

Einen GitHub-Actions-Workflow erstellen

Der CI-Workflow, den wir erstellen, hat folgende Struktur:

  • Testen

  • Image erstellen

  • Image scannen

Das im zweiten Schritt erstellte Image wird anschließend in die Docker-Registry von GitHub übertragen und im dritten Schritt wieder von dort abgerufen.

Den Test-Job erstellen

Angenommen, Ihr GitHub-Repository enthält noch keine CI-Konfiguration. Dann erstellen wir eine Datei unter `.github/workflows/ci.yaml` mit folgendem Inhalt:

    name: ci
    on:
      push:
        branches:
          - master
      pull_request:

    jobs:
      test:
        runs-on: ubuntu-latest

        strategy:
          matrix:
            node-version: [ 18.x ]

        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v3

          - name: Setup Node environment 🧱: Node.js ${{ matrix.node-version }}
            uses: actions/setup-node@v3
            with:
              node-version: ${{ matrix.node-version }}

          - name: Install and test 🪲
            run: |
              npm ci
              npm test

Das Attribut `on` legt fest, welche Ereignisse den Workflow auslösen. In diesem Fall löst jeder Push auf den Branch `master` sowie jeder Pull Request den CI-Workflow aus.

Die Job-Schritte umfassen Folgendes:

  • Checkout 🔔: Ruft den Code aus dem Repository ab

  • Node-Umgebung einrichten 🧱: Installiert die Node.js-18-Umgebung

  • Installieren und testen 🪲: Installiert die Abhängigkeiten und führt die Tests aus

Der Test-Job ist hier lediglich ein Beispiel dafür, wie Sie die Unit-Tests eines Node.js-Projekts ausführen können. Passen Sie ihn gern an die Programmiersprache und Struktur Ihres Projekts an.

Ein Test-Job ist zwar nicht zwingend erforderlich, um das Docker-Image zu erstellen, aber es ist eine bewährte Vorgehensweise – deshalb habe ich ihn beibehalten.

Den Docker-Build-Job erstellen

Um den Docker-Build-Job zu erstellen, lege ich zunächst zwei Umgebungsvariablen am Anfang der Datei unter `on` an:

  on:
      ...

    env:
      DOCKER_IMAGE_TAG: ${{ github.ref == 'refs/heads/master' && 'prod-' || 'dev-' }}${{ github.sha }}
      GITHUB_REGISTRY: ghcr.io
      GITHUB_REPOSITORY: ${{ github.repository }}

Erstellen wir nun den `build_docker job`:

    jobs:
      test:
        ...

      build_image:
        permissions:
          id-token: write
          contents: read
          packages: write
        runs-on: ubuntu-latest
        needs: [ test ]

        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v2

          - name: Log in to the Container registry 📦
            uses: docker/login-action@v2
            with:
              registry: ${{ env.GITHUB_REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}

          - name: Build and push Docker image 🐳
            uses: docker/build-push-action@v3
            with:
              push: true
              tags: |
                ${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }}

Sehen wir uns den Code an. Der Abschnitt „permissions“ ist erforderlich, da wir Docker-Images in die Registry von GitHub übertragen möchten.

Der Job wird mit der neuesten Ubuntu-Version ausgeführt und setzt voraus, dass der Test-Job abgeschlossen ist. So stellen wir sicher, dass wir Docker-Images nur für funktionsfähigen Code erstellen. Wenn Sie den Test-Job ausgelassen haben, entfernen Sie die Zeile „needs“.

Nun zu den Schritten:

  • Checkout 🔔: Ruft den Code aus dem Repository ab. Das ist erforderlich, um auf die Dockerfile und ihren Kontext zuzugreifen.

  • Bei der Container-Registry anmelden 📦

  • Docker-Image erstellen und übertragen 🐳: In diesem Schritt verwenden wir die Umgebungsvariablen, die wir am Anfang der Datei eingeführt haben, um den Tag des Docker-Images festzulegen.

Den Scan-Job erstellen

Fügen wir nun den Job zum Scannen des Docker-Images hinzu:

    jobs:
      test:
        ...

      build_image:
        ...

      scan_docker_image:
        permissions:
          id-token: read
          contents: read
          packages: read
        runs-on: ubuntu-latest
        needs: [ build_image ]
        steps:
          - name: Checkout 🛎️
            uses: actions/checkout@v2

          - name: Log in to the Container registry 📦
            uses: docker/login-action@v2
            with:
              registry: ${{ env.GITHUB_REGISTRY }}
              username: ${{ github.actor }}
              password: ${{ secrets.GITHUB_TOKEN }}

          - name: Scan Docker image 🐳
            uses: snyk/actions/docker@master
            continue-on-error: true
            with:
              image: ${{ env.GITHUB_REGISTRY }}/${{ env.GITHUB_REPOSITORY }}:${{ env.DOCKER_IMAGE_TAG }}
              args: --file=Dockerfile --severity-threshold=high --sarif-file-output=snyk.sarif
            env:
              SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

          - name: Upload Snyk report as sarif 📦
            uses: github/codeql-action/upload-sarif@v2
            with:
              sarif_file: snyk.sarif

In diesem Schritt kommt Snyk zum Einsatz, die Sicherheits-Scan-Engine hinter `docker scan`. Um sie zu nutzen, müssen Sie ein kostenloses Konto erstellen und das Token als Secret speichern:

  • Hier registrieren

  • Das Token wie hier beschrieben abrufen

  • Fügen Sie es wie im Anhang beschrieben als CI-Secret zu Ihrem GitHub-Repository hinzu und nennen Sie es `SNYK_TOKEN`.

Weiter mit den Schritten:

  • Checkout 🛎️: Der Scanner arbeitet besser, wenn er auch auf die Dockerfile zugreifen kann.

  • Bei der Container-Registry anmelden 📦: Damit die zuvor dorthin übertragenen Docker-Images abgerufen werden können.

  • Docker-Image scannen 🐳: Dieser Job scannt das Docker-Image und meldet die Sicherheitslücken in einer Datei namens `snyk.sarif`. Dieses Dateiformat wird von GitHub erkannt und kann im Pull Request angezeigt werden – deshalb folgt der nächste Schritt.

  • Snyk-Bericht als SARIF hochladen 📦: Hier laden wir die im vorherigen Schritt erstellte `sarif`-Datei zu GitHub hoch.

Die an GitHub übermittelten Sicherheitslücken werden in Ihrem Pull Request wie folgt angezeigt:

Code-Scanning-Anmerkung in einer Dockerfile mit einer Schwachstelle in curl mit hohem Schweregrad und einem fehlgeschlagenen Check.

Fazit


In diesem Beitrag haben wir einen GitHub-Actions-Workflow mit drei Jobs erstellt: zum Ausführen der Tests, Erstellen des Docker-Images, Übertragen in die GitHub-Registry, Prüfen auf Sicherheitsprobleme und Hochladen des Berichts zu Sicherheitslücken, damit GitHub ihn erkennen und in Pull Requests anzeigen kann.

Anhang: CI-Secret auf GitHub hinzufügen

In Ihrem Repository:

  1. Klicken Sie auf den Tab „Settings“.

  2. Klicken Sie im linken Menü unter „Security“ auf Secrets and variables.

  3. Klicken Sie anschließend in den neu angezeigten Menüelementen auf Actions.

  4. Klicken Sie oben rechts auf die grüne Schaltfläche New repository secret.

  5. Geben Sie im Feld Name den gewünschten Namen ein, z. B. `SNYK_TOKEN`.

  6. Fügen Sie im Feld Secret das Secret ein, z. B. das Snyk-Token, das Sie von der Snyk-Website kopiert haben.

  7. Klicken Sie anschließend auf die grüne Schaltfläche Add secret.

GitHub-Repository-Einstellungsseite mit Actions-Geheimnissen und -Variablen, darunter die Repository-Geheimnisse ACCESS_TOKEN und SNYK_TOKEN.