Technischer Artikel

Git-Zugangsdaten in Cloud-Mac-CI sicher isolieren

Git-Zugangsdaten in Cloud-Mac-CI sicher isolieren

Wenn ein dauerhaft laufender Cloud-Mac nacheinander Builds für mehrere Repositorys ausführt, besteht die größte Gefahr nicht immer darin, dass ein Token direkt in einem Skript steht. Oft hinterlässt vielmehr ein vorheriger Job unbemerkt Zugangsdaten für den nächsten. Typische Rückstände finden sich in der globalen Git-Konfiguration, in Remote-URLs mit eingebetteten Anmeldedaten, im Anmeldeschlüsselbund oder in nicht gelöschten temporären Skripten. Ziel ist daher nicht, „nach dem Build eine Variable zu löschen“, sondern Zugangsdaten von Anfang an auf die Grenzen eines einzelnen Jobs zu beschränken.

Zuerst prüfen, wo Zugangsdaten gespeichert werden

Ändern Sie nicht sofort die Pipeline. Prüfen Sie zunächst, welche Konfiguration Git tatsächlich einliest, ohne dabei geheime Werte auszugeben. --show-origin zeigt zusätzlich die Herkunft jeder Einstellung an und macht damit systemweite, benutzerspezifische und repositorybezogene Konfigurationen unterscheidbar.

set +x

git config --show-origin --get-all credential.helper || true
git config --show-origin --get-regexp '^(credential|http)\.' || true
git config --show-origin --get-regexp '^url\..*\.insteadof$' || true

Achten Sie besonders auf drei Arten von Signalen:

  • Verweist credential.helper auf einen persistenten Schlüsselbund?
  • Wurde http.extraHeader in die globale oder repositorybezogene Konfiguration geschrieben?
  • Schreibt url.*.insteadOf eine normale Adresse in eine URL mit eingebetteten Anmeldedaten um?

Auch die Remote-URL muss geprüft werden, darf aber nicht unverändert im Protokoll erscheinen. Es genügt festzustellen, ob sie dem Muster Protokoll://Benutzerinfo@Host entspricht. Wird ein solcher Aufbau erkannt, sollte der Job sofort fehlschlagen, statt die vollständige Adresse auszugeben.

remote_url="$(git remote get-url origin)"
case "$remote_url" in
  *://*@*)
    printf '%s
' "Remote URL contains embedded credentials" >&2
    exit 1
    ;;
esac

Der wichtigste Grundsatz für Audit-Skripte lautet: Melden Sie nur, dass Zugangsdaten vorhanden sein könnten. Geben Sie die Zugangsdaten nicht im Build-Protokoll aus, nur um das Problem zu belegen.

Für jeden Job ein eigenes HOME erstellen

Git leitet den Pfad zur benutzerspezifischen Konfiguration aus HOME ab. Wenn alle Jobs das HOME des Runner-Benutzers gemeinsam verwenden, teilen sie damit auch .gitconfig, die Konfiguration der Credential-Helper und zahlreiche Zustände anderer Werkzeuge. Robuster ist es, für jeden Job ein temporäres HOME mit den Berechtigungen 700 anzulegen und die globale Git-Konfigurationsdatei explizit festzulegen.

set -eu

ORIGINAL_HOME="$HOME"
JOB_HOME="$(mktemp -d "${TMPDIR%/}/git-job.XXXXXX")"
chmod 700 "$JOB_HOME"

export HOME="$JOB_HOME"
export XDG_CONFIG_HOME="$JOB_HOME/.config"
export GIT_CONFIG_GLOBAL="$JOB_HOME/.gitconfig"
export GIT_TERMINAL_PROMPT=0

mkdir -p "$XDG_CONFIG_HOME"

Die systemweite Git-Konfiguration wird dadurch nicht isoliert. Deshalb muss die Helper-Kette weiterhin aktiv zurückgesetzt werden. Git unterstützt mehrere Werte für credential.helper. Ein zuerst gesetzter leerer Wert verwirft Helper, die aus Konfigurationsebenen mit niedrigerer Priorität übernommen wurden. Anschließend kann die jobeigene Implementierung ergänzt werden.

Repository und temporäres HOME getrennt halten

HOME bildet die Grenze für Zugangsdaten und Werkzeugzustände, muss aber nicht zugleich als Arbeitsbereich dienen. Der Quellcode sollte weiterhin im Workspace des Runners liegen, damit Speicherplatz kontrolliert und Build-Artefakte eingesammelt werden können. Sind beide Bereiche getrennt, löscht die Bereinigung von HOME nicht versehentlich Build-Ergebnisse, während die Bereinigung des Workspace keine benutzerspezifische Authentifizierungskonfiguration übersieht.

Bei paralleler Ausführung muss jeder Parallelisierungsslot mktemp separat aufrufen. Ein anhand des Repositorynamens festgelegtes Verzeichnis darf nicht wiederverwendet werden. Nach einem unerwarteten Abbruch könnte der nächste Job ein solches Verzeichnis übernehmen. Außerdem könnten zwei Jobs gleichzeitig dieselbe .gitconfig verändern.

Token nur auf Anfrage von Git bereitstellen

Das Token sollte über die Secret-Variablen des CI-Systems in die Umgebung injiziert werden; das Skript verweist ausschließlich auf die Variablennamen. Betten Sie das Token weder in die Clone-URL ein, noch speichern Sie den Authentifizierungs-Header dauerhaft mit git config --global http.extraHeader.

Der folgende Helper antwortet ausschließlich auf get-Anfragen von Git. In der Konfigurationsdatei steht die Logik zum Referenzieren der Variablen, nicht der Tokenwert:

: "${CI_GIT_USER:?CI_GIT_USER is required}"
: "${CI_GIT_TOKEN:?CI_GIT_TOKEN is required}"

git config --global credential.helper ""
git config --global --add credential.helper \
'!f() {
  if [ "$1" = get ]; then
    printf "username=%s
password=%s
" \
      "$CI_GIT_USER" "$CI_GIT_TOKEN"
  fi
}; f'

Befehls-Echo und interaktiven Fallback deaktivieren

Vor dem Zugriff auf geheime Variablen muss set +x ausgeführt werden. Andernfalls schreibt die Shell Befehle mit bereits expandierten Variablen in das Protokoll. GIT_TERMINAL_PROMPT=0 ist ebenso wichtig: Fehlt das Token oder ist es ungültig, muss der Job eindeutig fehlschlagen, statt an einer nicht sichtbaren interaktiven Eingabeaufforderung zu warten.

Prüfen Sie außerdem, ob der Build-Wrapper automatisch Umgebungsvariablen ausgibt. Diagnoseinformationen dürfen lediglich anzeigen, ob eine Variable gesetzt ist, beispielsweise indem geprüft wird, ob ihre Zeichenlänge größer als null ist. Der Variableninhalt, Authentifizierungs-Header und vollständige Remote-URLs dürfen nicht ausgegeben werden.

Automatisierte Aufgaben mit Schreibzugriff sollten unterschiedliche Zugangsdaten für das Lesen des Quellcodes und das Pushen von Artefakten verwenden. Ein Job, der lediglich einen Checkout ausführt, benötigt keine Schreibrechte. Auch ein Veröffentlichungsjob darf keine Berechtigungen erhalten, die über das Ziel-Repository und die erforderlichen Aktionen hinausgehen.

Mit trap alle Exit-Pfade abdecken

Ein rm -rf ausschließlich am Ende des Skripts deckt vorzeitige Fehler, durch Zeitüberschreitung verursachte Abbrüche oder manuelle Abbrüche nicht ab. Registrieren Sie deshalb unmittelbar nach dem Erstellen des temporären Verzeichnisses einen trap. Die Bereinigungsfunktion sollte zuerst die Variablen löschen und anschließend das HOME des Jobs entfernen.

cleanup_git_credentials() {
  set +e
  unset CI_GIT_TOKEN CI_GIT_USER
  if [ -n "${JOB_HOME:-}" ] && [ -d "$JOB_HOME" ]; then
    rm -rf "$JOB_HOME"
  fi
}

trap cleanup_git_credentials EXIT HUP INT TERM

Die Bereinigungsfunktion muss wiederholt ausgeführt werden können, ohne einen Fehler auszulösen, wenn das Verzeichnis bereits nicht mehr existiert. Das Löschziel muss außerdem zwei Bedingungen erfüllen: Die Variable darf nicht leer sein und muss tatsächlich auf ein Verzeichnis verweisen. Verwenden Sie keine weitreichenden Befehle zum Löschen von Schlüsselbundinhalten und leeren Sie nicht den gesamten Anmeldeschlüsselbund. Er könnte weitere erforderliche Einträge für denselben Runner-Benutzer enthalten.

Falls ältere Pipelines persistente Helper verwendet haben, müssen diese zunächst präzise nach Host und Konto erfasst werden. Planen Sie anschließend eine einmalige Migration. Solange alte und neue Lösung parallel bestehen, muss jeder Job die Herkunft des aktuell verwendeten Helpers prüfen. So lässt sich verhindern, dass das temporäre HOME zwar aktiv ist, ein systemweiter Wrapper aber die alte Konfiguration erneut hineinkopiert.

Rückstandsprüfungen als Build-Gate einsetzen

Eine fehlerfrei beendete Bereinigung allein beweist nicht, dass sie erfolgreich war. Empfehlenswert ist eine nachgelagerte Prüfung außerhalb des Runners. Alternativ kann der Executor beim Zurücksetzen des Jobs folgende Punkte verifizieren:

  • Das temporäre HOME wurde gelöscht.
  • .git/config im Workspace enthält weder Authentifizierungs-Header noch Remote-URLs mit Benutzerinformationen.
  • Das Jobprotokoll enthält keinen bekannten Fingerabdruck der geheimen Variablen.
  • Die Herkunft der Git-Konfiguration umfasst nur die erwartete Systemkonfiguration und die temporäre Konfiguration des aktuellen Jobs.
  • Ein nachfolgender leerer Job kann nicht auf die Repository-Zugangsdaten des vorherigen Jobs zugreifen.

Für ein Test-Token kann ein fester Markerwert ohne Berechtigungen verwendet werden. Führen Sie damit in einer isolierten Umgebung Fehlerszenarien aus und durchsuchen Sie anschließend Protokolle und Dateisystem. Dabei wird geprüft, ob der Marker offengelegt wurde, nicht ob ein echtes Token gültig ist. Die Fehlertests sollten mindestens einen fehlgeschlagenen Clone, den Abbruch eines Build-Befehls, den Empfang eines Beendigungssignals und die wiederholte Ausführung der Bereinigungsfunktion abdecken.

Wenn mehrere Teams denselben physischen Knoten nutzen, sollte der ausführende Benutzer als zweite Sicherheitsgrenze dienen. Das temporäre HOME verhindert Rückstände auf Jobebene. Separate Benutzer grenzen Prozesse, Dateiberechtigungen und Schlüsselbunde zwischen unterschiedlichen Vertrauensbereichen voneinander ab. Nur wenn beide Ebenen vorhanden sind, bleibt die Skriptbereinigung nicht die einzige Verteidigungslinie.

Die endgültigen Abnahmekriterien sind einfach: Vor dem Job stehen keine vererbbaren Zugangsdaten bereit, während der Ausführung werden sie nur bei Bedarf bereitgestellt, jeder Exit-Pfad löst die Bereinigung aus und der nächste Job kann nicht nachweisen, dass das vorherige Token jemals vorhanden war. Erst dann gehören die Git-Zugangsdaten tatsächlich zum Job und nicht zum dauerhaft laufenden Cloud-Mac.

Häufig gestellte Fragen

Warum reicht es nicht, die Token-Variable am Jobende zu löschen?

Der Zugang kann bereits in einem Helper, einer Remote-URL, der globalen Konfiguration oder im Schlüsselbund gespeichert sein. Jeder Speicherort muss separat geprüft werden.

Dürfen dauerhaft laufende Mac-CI-Worker den Login-Schlüsselbund teilen?

Nicht über unterschiedliche Repositories oder Vertrauensgrenzen hinweg. Nutzen Sie kurzlebige Tokens mit Minimalrechten und bei Bedarf getrennte Benutzer oder Schlüsselbünde.

Dedizierter Cloud-Mac

Wählen Sie für Entwicklung, Builds und Remote-Desktop einen dedizierten physischen Rechner.

Vergleichen Sie drei Apple-Silicon-Konfigurationen und wählen Sie beim Bestellen Knoten, Mietdauer und zusätzliche Speicheroptionen aus.

Mietoption auswählen