Wenn ein Cloud Mac gleichzeitig Code abruft, kompiliert, testet und veröffentlicht, wird häufig nicht die Verzeichnisstruktur übersehen, sondern die Prozessumgebung. Sobald eine Pipeline Token in Umgebungsvariablen ablegt, werden diese standardmäßig von xcodebuild, Skripten, Test-Hosts und Hintergrundprozessen geerbt. Selbst wenn der Hauptauftrag bereits abgeschlossen ist, kann ein noch laufender Hilfsprozess weiterhin sämtliche Variablen enthalten. Die richtige Lösung besteht nicht darin, Umgebungsvariablen vollständig zu verbieten, sondern die zeitliche und prozessbezogene Sichtbarkeit von Secrets auf ein Minimum zu begrenzen.
Vererbungswege von Variablen erfassen
Jeder vom Runner gestartete Prozess sollte einer von vier Kategorien zugeordnet werden: Build, Test, Veröffentlichung oder Hilfswerkzeug. Build- und Testprozesse benötigen normalerweise keine Veröffentlichungstoken; Hilfswerkzeuge sollten sie ebenfalls nicht erben. Dokumentiert werden zunächst nur Variablenname, Zeitpunkt der Einspeisung, verwendeter Befehl und vorgesehener Bereinigungszeitpunkt – niemals der tatsächliche Wert.
| Prüfobjekt | Typisches Risiko | Abnahmemethode |
|---|---|---|
| Übergeordneter Runner-Prozess | Erhält beim Start sämtliche Secrets | Nur prüfen, ob der Variablenname vorhanden ist |
| Build-Kindprozess | Erbt die übergeordnete Umgebung uneingeschränkt | Vererbungstest mit einer zufälligen Markierung ausführen |
| launchd-Domäne | Bleibt nach setenv über mehrere Aufträge hinweg erhalten |
Vor und nach dem Auftrag prüfen und bereinigen |
| Temporäre Datei | Zu weitreichende Berechtigungen oder Rückstände nach einem unerwarteten Abbruch | Berechtigungen auf 600 setzen und Datei per trap löschen |
| Protokolle | Shell-Tracing gibt Befehlsargumente aus | Während der Secret-Verarbeitung set -x deaktivieren |
Ein Audit-Skript muss lediglich beantworten, ob eine bestimmte Markierung sichtbar ist. Umgebungs-Snapshots sollten nicht als Build-Artefakte gespeichert werden. Vollständige Ausgaben von
env,exportoderps ewwkönnen selbst zu einer neuen Quelle für Datenlecks werden.
Vererbung mit einer zufälligen Markierung prüfen
Für solche Tests dürfen keine echten Token verwendet werden. Stattdessen wird eine einmalige Markierung erzeugt, ein kurzlebiger Kindprozess gestartet und anschließend ohne Ausgabe des Werts geprüft, ob dieser Prozess die Markierung sehen kann. Die folgende Prüfung schreibt den Inhalt der Markierung nicht in das Protokoll; anhand des Exit-Codes kann die Pipeline dennoch erkennen, ob eine Vererbung stattgefunden hat.
set -euo pipefail
marker="$(/usr/bin/uuidgen)"
export CI_SECRET_PROBE="$marker"
/bin/sleep 20 &
child_pid=$!
if /bin/ps eww -p "$child_pid" -o command= | /usr/bin/grep -q "$marker"; then
result=1
else
result=0
fi
kill "$child_pid" 2>/dev/null || true
wait "$child_pid" 2>/dev/null || true
unset CI_SECRET_PROBE
test "$result" -eq 1
Dieser Test weist nach, dass die standardmäßige Vererbung tatsächlich stattfindet. Er ist keine Empfehlung, in Produktionsprotokollen wiederholt sämtliche Prozesse zu durchsuchen. Für die reguläre Abnahme kann ein fester Variablenname mit einem zufälligen Wert kombiniert werden; protokolliert wird lediglich „bestanden“ oder „fehlgeschlagen“.
Rückstände in launchd prüfen
Auftragsbezogene Secrets sollten nicht mit launchctl setenv eingespeist werden, da ihre Lebensdauer dadurch eine einzelne Shell-Sitzung überschreiten kann. Falls ältere Skripte dieses Verfahren verwendet haben, wird zunächst der Status geprüft, ohne den Wert auszugeben. Anschließend werden folgende Befehle ausgeführt:
launchctl unsetenv CI_RELEASE_TOKEN
test -z "$(launchctl getenv CI_RELEASE_TOKEN)"
Diese Prüfung sollte jeweils einmal zu Beginn des Auftrags und während der Bereinigung erfolgen. Läuft der Runner unter einem eigenen Benutzerkonto, muss sie innerhalb der launchd-Domäne dieses Benutzers ausgeführt werden. Es genügt nicht, ausschließlich die aktuelle Remote-Sitzung zu prüfen.
Minimale Umgebung für den Build einrichten
Mit env -i lässt sich ein Befehl in einer leeren Umgebung starten. Die Umgebung darf jedoch nicht unüberlegt vollständig geleert werden. Die Xcode-Toolchain benötigt üblicherweise HOME, PATH, TMPDIR, Locale-Einstellungen und ein ausdrücklich festgelegtes Entwicklerverzeichnis. Zuerst wird die Konfiguration über eine Versionsabfrage geprüft, danach kann der vollständige Build ausgeführt werden.
set -euo pipefail
job_tmp="$(/usr/bin/mktemp -d "${TMPDIR:-/tmp}/mac-ci.XXXXXX")"
trap '/bin/rm -rf "$job_tmp"' EXIT HUP INT TERM
/usr/bin/env -i \
HOME="$HOME" \
PATH="/usr/bin:/bin:/usr/sbin:/sbin" \
TMPDIR="$job_tmp/" \
LANG="en_US.UTF-8" \
DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer" \
/usr/bin/xcrun xcodebuild -version
Benötigt ein reales Projekt Ruby, Node.js oder einen Paketmanager, sollten deren Verzeichnisse mit ausführbaren Dateien einzeln zu PATH hinzugefügt werden. Die vollständige Umgebung einer Login-Shell darf nicht wiederhergestellt werden. Für jede zusätzliche Variable muss dokumentiert werden, welcher Befehl sie benötigt.
Build und Veröffentlichung in getrennte Berechtigungsbereiche aufteilen
Die Build-Phase erzeugt ausschließlich Archive und Prüfsummen und erhält keinen Zugriff auf Veröffentlichungszugangsdaten. Die Veröffentlichungsphase verarbeitet bereits abgenommene Artefakte und übergibt nur die tatsächlich benötigten Variablen an den Upload-Befehl. CI_RELEASE_TOKEN sollte nicht zu Beginn des Auftrags per export gesetzt werden, da es sonst von allen nachfolgenden Skripten geerbt wird.
set +x
CI_RELEASE_TOKEN="$release_token" \
/usr/bin/env \
RELEASE_ARTIFACT="$artifact_path" \
./ci/publish.sh
unset release_token
set -x
Noch robuster ist es, wenn publish.sh nur einen einzigen Upload-Prozess startet, dessen Beendigung abwartet und danach sofort selbst beendet wird. Verlangt das Werkzeug eine Datei mit Zugangsdaten, sollte dafür ein auftragsspezifisches temporäres Verzeichnis verwendet werden. Vor dem Erstellen der Datei ist umask 077 zu setzen; nach dem Schreiben müssen die Berechtigungen auf 600 geprüft werden. Die Datei darf weder im Repository noch auf dem Benutzerdesktop oder in einem dauerhaft verwendeten Cache-Verzeichnis abgelegt werden.
Fehlerbehandlung begrenzen
Fehler-Hooks sammeln häufig Informationen über Umgebung, Prozesse und Arbeitsbereich. Sie dürfen weder ein ungefiltertes env ausführen noch vollständige Befehlszeilen archivieren. Zulässig sind Exit-Code, Werkzeugversionen, Artefaktpfade, verfügbarer Speicherplatz sowie anhand einer Positivliste gefilterte Variablennamen. Wenn Secrets geprüft werden müssen, darf nur verglichen werden, ob ein Hash oder eine zufällige Prüfmarkierung vorhanden ist; der ursprüngliche Wert wird nicht gespeichert.
Erfolgreiche Bereinigung als Auftrags-Gate festlegen
Ein erfolgreicher Auftrag bedeutet nicht automatisch, dass auch die Bereinigung erfolgreich war. In der Abschlussphase muss mindestens geprüft werden, ob die Veröffentlichungsvariablen per unset entfernt wurden, ob die entsprechenden Namen nicht mehr in launchd vorhanden sind, ob das temporäre Verzeichnis nicht mehr existiert, ob Test- und Upload-Kindprozesse beendet wurden und ob in den Protokollen kein Befehls-Tracing mehr aktiv ist. Schlägt eine dieser Prüfungen fehl, muss der aktuelle Runner zur Überprüfung markiert werden und darf vorerst keine weiteren Veröffentlichungsaufträge mit Secrets annehmen.
Es empfiehlt sich, für jeden Auftrag ein eigenes TMPDIR zu erzeugen und bei der Bereinigung die gesamte Prozessgruppe zu berücksichtigen. Wird nur das Hauptskript beendet, können Hilfsprozesse von Simulatoren, Upload-Programme oder benutzerdefinierte Daemons zurückbleiben. Nach der Bereinigung sollte erneut eine zufällige Prüfmarkierung ohne echten Secret-Wert verwendet werden, um sicherzustellen, dass neue Kindprozesse keine Markierung aus der vorherigen Phase mehr erkennen können.
Das Ziel besteht nicht darin, Umgebungsvariablen vollständig aus der Pipeline zu verbannen, sondern klare Grenzen zu schaffen: Während der Build-Phase sind keine Veröffentlichungs-Secrets vorhanden, während der Veröffentlichungsphase kann nur ein einziger erforderlicher Prozess das Secret sehen, und nach Abschluss des Auftrags verbleibt es weder in Prozessen noch in launchd oder auf dem Datenträger. Werden diese Prüfungen in die Initialisierungs- und Abschlussskripte des Runners aufgenommen, müssen sich nachfolgende Projekte nicht auf die Aufmerksamkeit einzelner Bediener verlassen.
Häufig gestellte Fragen
Sind CI-Secrets sicher, wenn sie nur als Umgebungsvariablen vorliegen?
Nein. Variablen werden an Kindprozesse vererbt und können in Diagnosen, Debug-Ausgaben oder dauerhaften Diensten auftauchen. Übergeben Sie ein Secret nur an den tatsächlich benötigten Einzelbefehl.
Wie prüfe ich nach einem Auftrag auf verbliebene Secret-Variablen?
Untersuchen Sie den Runner-Elternprozess, die launchd-Domäne und überlebende Kindprozesse auf Variablennamen. Prüfen Sie außerdem die Löschung des temporären Verzeichnisses, ohne Werte auszugeben.
Kann env -i einen xcodebuild-Aufruf beschädigen?
Ja, weil die Standardumgebung entfernt wird. Setzen Sie HOME, PATH, TMPDIR, LANG und DEVELOPER_DIR ausdrücklich und testen Sie die Freigabeliste zuerst mit einer Versionsabfrage und einem kleinen Build.
Den nächsten Build auf einen Mac mini in der Cloud verlagern
Wählen Sie Konfiguration, Abrechnungszeitraum und Knotenregion und führen Sie Entwicklungs-, Build- und Automatisierungsaufgaben auf einem dedizierten physischen Rechner aus.