Vertrauen durch Verifizierung: Commit-Signierung in Git

Seite 2: Öffentlichen Schlüssel für GitLab oder GitHub

Inhaltsverzeichnis

Zunächst ist sicherheitstechnisch damit noch nichts gewonnen, da das Signieren von Commits unter falschem Namen und mit beliebiger Signatur weiterhin möglich ist. Es fehlt noch die Verbindung zwischen dem Schlüssel und der zugehörigen Person. Eine Public Key Infrastructure oder ein Web of Trust erfüllen diese Aufgaben.

Für kleinere Teams ohne eine solche Infrastruktur ist ein manueller Schlüsselaustausch möglich, aber oft zu aufwendig. Am einfachsten ist es in dem Fall, dem Git-Server die Aufgabe zu überlassen. Sowohl GitHub als auch GitLab ermöglichen es, öffentliche Schlüssel im Nutzerprofil zu hinterlegen, um die Signaturen zu überprüfen. Bei GPG und S/MIME vergleichen die Server die im Schlüssel hinterlegte E-Mail mit der des Committer. Bei SSH-Signaturen nutzen die Server die E-Mail-Adresse des GitHub- beziehungsweise GitLab-Accounts zum Vergleich. Wenn ich nun einen signierten Commit unter dem Namen meines Kollegen erstelle, markiert GitHub den Urheber als "Unverified" (siehe Abbildung 2).

GitHub zeigt an, dass es den Commit-Urheber nicht verifizieren kann (Abb. 2).

(Bild: Screenshot (Janosch Deurer))

Wenn ich den Commit unter meinem eigenen Namen erstelle, zeigt GitHub dagegen den Urheber als "Verified" an (siehe Abbildung 3).

GitHub deklariert den verifizierten Urheber (Abb. 3).

(Bild: Screenshot (Janosch Deurer))

GitHub bietet zusätzlich einen sogenannten Vigilant Mode (wachsamer Modus), in dem es jeden Commit als "Unverified" kennzeichnet, der nicht mit der E-Mail des aktuellen Users signiert ist. Bei GitLab verhält sich das Interface ähnlich, aber die Plattform kennt keine Funktion, die dem Vigilant Mode entspricht.

Um sicherzustellen, dass der Server nur Commits mit valider Signatur annimmt, empfiehlt sich ein automatisierter Prozess. Bei selbstverwalteten Git-Servern können Pre-Receive-Hooks beim Push alle Commits prüfen. In GitHub lässt sich ein solcher Hook über eine Branch Protection Rule konfigurieren, die allerdings für private Repositories nur kostenpflichtig verfügbar ist. GitLab-Kunden können ebenfalls in der kostenpflichtigen Version forcieren, dass Commits immer signiert sind. Der Server überprüft dabei allerdings nicht, ob die Signaturen valide sind.

Um die Validität sicherzustellen, lassen sich alternativ die Signaturen in der Pipeline von Merge Requests überprüfen. Um zu verhindern, dass jemand diese einfach umgeht, muss die Konfiguration festlegen, dass Commits nur über Merge Requests, die erfolgreich die Pipeline durchlaufen, auf dem Hauptzweig landen können. Außerdem sind verpflichtende Code Reviews erforderlich, damit keine Einzelperson die Pipeline umgehen kann, indem sie beispielsweise den zugehörigen Schritt in der Pipeline auskommentiert. In GitLab lässt sich in einer Pipeline überprüfen, ob alle zu einem Merge Request gehörigen Commits als "Verified" markiert sind. Folgender Code erledigt die Abfrage über die GitLab-API:

Verify Commits Signature:
  stage: verify-signature
  needs: [ ]
  image: alpine:3.19.1@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b
  script:
    - |
      apk add --no-cache curl jq

      # API endpoint to get commits from the merge request
      COMMIT_API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/commits"
      
      # Making API call to get commits
      COMMITS=$(curl --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" "$COMMIT_API_URL")
      
      # Get commit SHAS
      COMMIT_SHAS=$(echo "$COMMITS" | jq -r '.[] |  .id')

      # Loop through each commit SHA to check its signature
      for SHA in $COMMIT_SHAS; do
        # API endpoint to get the commit's signature
        SIGNATURE_API_URL="${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/repository/commits/$SHA/signature"
        
        # Making API call to get the commit's signature details
        SIGNATURE=$(curl --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" "$SIGNATURE_API_URL")
        
        # Get verification status of commit
        VERIFICATION_STATUS=$(echo "$SIGNATURE" | jq '.verification_status')
        
        # Check if the signature is verified
        if [ "$VERIFICATION_STATUS" != '"verified"' ]; then
          echo "Commit $SHA is not signed or the signature is not verified."
          exit 1
        fi
      done
      
      echo "All commits are signed and verified."

Wer das Signieren für alle Commits forciert, muss dafür sorgen, dass Bots, die Commits erstellen, wie Renovate oder Dependabot ebenfalls mit validen Signaturen arbeiten. Darüber hinaus bereiten manche Funktionen in der Oberfläche von GitHub und GitLab Probleme. Unter anderem führt bei beiden Plattformen der Rebase in der Oberfläche zu unsignierten Commits. Beim Merge signiert GitHub seine Commits automatisch, aber GitLab lässt dabei ebenfalls die Signatur weg.

Teams nutzen Tags meist, wenn sie Versionen der Software veröffentlichen. Damit sind sie ein essenzieller Bestandteil des Release-Prozesses und somit auch der Software Supply Chain. Deshalb erhöht es die Sicherheit, die Tags ebenfalls zu signieren. Das funktioniert analog zum Signieren der Commits mit

git tag -s meinNeuerTag