CI mit GitHub Actions
Diese Anleitung richtet CodeGuard für Pull Requests, Pushes und nächtliche Läufe in GitHub Actions ein. Sie setzt self-hosted macOS-Runner voraus, die ephemer und isoliert sind, sodass CodeGuard dort bauen und testen darf.
So funktioniert CodeGuard in der CI
- Jeder Aufruf bekommt
--ci. Das Profil ist nicht interaktiv, lokale Trust-Tickets gelten nicht,trust grantist verboten. compileundtestsstarten nur, wenn der Runner eine Attestierung der Organisation trägt (siehe Organisationsrichtlinie). Fehlt sie, laufen nur die statischen Prüfungen und der Lauf endet mit Exit 6.- Der Job-Status folgt dem Exit-Code von CodeGuard. Die Berichte werden als Artefakt abgelegt.
Voraussetzungen
- Self-hosted Runner auf macOS 15 oder neuer, der ephemer ist (jeder Job auf einer frischen Umgebung) sowie dateisystem- und netzwerkisoliert. Nur dann darf die Attestierungsdatei auf ihm liegen.
- Admin-Zugriff auf das Runner-Image, um Dateien als
rootabzulegen. - Das Projekt liegt im Wurzelverzeichnis des Repositorys.
check-diffbricht ab, wenn Projektwurzel und Git-Wurzel verschieden sind.
Schritt 1: Runner-Image vorbereiten
Diese Schritte gehören in die Erstellung des Runner-Images, nicht in den Workflow.
-
Xcode installieren und auswählen.
xcodebuildmuss eine Version>= 26.0.0und< 28.0.0haben. Installiere die Simulator-Runtimes aller Plattformen, die getestet werden sollen. Ohne Runtime endet ein Lauf für eine reine watchOS- oder tvOS-App mit Exit 3. -
CodeGuard installieren:
git clone <REPOSITORY-URL> /tmp/CodeGuard # Platzhalter: Repository-URL eintragencd /tmp/CodeGuardScripts/install.sh # nach /usr/local/bin, root:wheel -
SwiftLint als root-eigene Kopie bereitstellen, z. B.:
sudo install -o root -g wheel -m 0755 "$(realpath "$(command -v swiftlint)")" /usr/local/bin/swiftlint -
Organisationsrichtlinie installieren:
# /Library/Application Support/CodeGuard/policy.ymlpolicy_version: 1configuration_versions: [1]enforcement:tools:trusted_roots:- /usr/bin- /usr/local/bin- /Applications/Xcode.app/Contents/Developer/usr/bin- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/binexecution_environment:declaration_path: /Library/Application Support/CodeGuard/execution-environment.ymlrequire_for_untrusted_builds: truesudo mkdir -p "/Library/Application Support/CodeGuard"sudo install -o root -g wheel -m 0644 policy.yml "/Library/Application Support/CodeGuard/policy.yml"Passe die
trusted_rootsan, wenn Xcode nicht unter/Applications/Xcode.appliegt. -
Attestierung ablegen. Der Inhalt muss exakt so lauten, Zeile für Zeile:
printf 'version: 1\nrunner_class: ephemeral\nephemeral: true\nfilesystem_isolated: true\nnetwork_isolated: true\n' > execution-environment.ymlsudo install -o root -g wheel -m 0644 execution-environment.yml \"/Library/Application Support/CodeGuard/execution-environment.yml"gefahrMit dieser Datei sicherst du zu, dass der Runner ephemer und isoliert ist. CodeGuard führt daraufhin Projektcode aus Pull Requests ohne menschliche Freigabe aus. Lege sie nie auf einem dauerhaften oder vernetzten Runner ab.
-
Image prüfen (als der Benutzer, unter dem der Runner läuft):
codeguard versioncodeguard --ci config validatecodeguard --ci doctorconfig validatemusspolicy: /Library/Application Support/CodeGuard/policy.yml (sha256:…)zeigen,doctorready: true,swift-format: activeundswiftlint: active.
doctor und check-* nutzen einen Schlüssel aus dem Schlüsselbund des Runner-Benutzers. Existiert der Eintrag nicht, legt CodeGuard ihn an. Gibt es einen Eintrag eines anders signierten CodeGuard-Binarys (etwa nach einem Neubau), kann ein Lauf ohne grafische Sitzung unbegrenzt warten. Baue das Image deshalb so, dass CodeGuard nach dem letzten Neubau nicht erneut gebaut wird, oder lösche den Eintrag com.codeguard.control-state/hmac-key-v1 vor dem ersten Lauf. Ob der Schlüsselbund des Runner-Benutzers in deiner Umgebung nutzbar ist, prüft der doctor-Aufruf in Schritt 6. Ein Job-Timeout schützt zusätzlich vor einem Hänger.
Schritt 2: Repository vorbereiten
.codeguard.ymlund gegebenenfalls.swift-formatund.swiftlint.ymlins Wurzelverzeichnis committen. CodeGuard lädt.codeguard.ymlautomatisch.- Beachte:
.github/workflows/**,.codeguard.yml,Package.swift,Package.resolved,*.xcconfig,project.pbxprojund Entitlements sind geschützte Dateien. Ein Pull Request, der eine davon ändert, endet mit Exit 6 (protected_file). Das ist gewollt: Solche Änderungen brauchen eine menschliche Prüfung.
Schritt 3: Workflow anlegen
Datei .github/workflows/codeguard.yml:
name: CodeGuard
on:
pull_request:
push:
branches: [main]
schedule:
- cron: "0 2 * * *" # nächtlicher Projektlauf
jobs:
codeguard-diff:
if: github.event_name == 'pull_request'
runs-on: [self-hosted, macOS] # Platzhalter: Labels deiner ephemeren Runner
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # volle Historie, damit die Basis-Revision existiert
- name: CodeGuard check-diff
run: |
set +e
mkdir -p codeguard-reports
codeguard --ci --format json --output codeguard-reports/codeguard.json \
check-diff --base "${{ github.event.pull_request.base.sha }}"
rc=$?
echo "CodeGuard exit code: $rc"
exit $rc
- name: Berichte ablegen
if: always()
uses: actions/upload-artifact@v4
with:
name: codeguard-diff
path: codeguard-reports/
codeguard-project:
if: github.event_name != 'pull_request'
runs-on: [self-hosted, macOS] # Platzhalter: Labels deiner ephemeren Runner
timeout-minutes: 180
steps:
- uses: actions/checkout@v4
- name: CodeGuard check-project
run: |
set +e
mkdir -p codeguard-reports
codeguard --ci --format sarif --output codeguard-reports/codeguard.sarif check-project
rc=$?
echo "CodeGuard exit code: $rc"
exit $rc
- name: Berichte ablegen
if: always()
uses: actions/upload-artifact@v4
with:
name: codeguard-project
path: codeguard-reports/
Erläuterungen:
fetch-depth: 0:actions/checkoutklont standardmäßig nur den letzten Commit. Ohne die Basis-Revision endetcheck-diffmit Exit 2 (unbekannte Referenz) bzw. Exit 3 (Commit-Objekt fehlt).- Basis: Bei
pull_requestchecktactions/checkoutstandardmäßig den Merge-Commit des Pull Requests aus.check-diff --base <base.sha>prüft damit genau die Änderungen des Pull Requests. set +eundexit $rc: Der Schritt läuft bis zum Ende, gibt den Exit-Code aus und übernimmt ihn als Job-Ergebnis. Jeder Code ungleich 0 lässt den Job fehlschlagen.if: always(): Der Bericht wird auch abgelegt, wenn CodeGuard Funde meldet.timeout-minutes: Builds für mehrere Plattformen und Simulator-Tests dauern. CodeGuard hat kein Gesamtzeitlimit über den ganzen Lauf.
Jeder CodeGuard-Aufruf ist ein vollständiger Lauf mit Build und Tests. Erzeuge deshalb nur ein Format pro Lauf (JSON für eigene Auswertungen, SARIF für Werkzeuge, die SARIF lesen). Den Bericht kannst du nachträglich mit jq zusammenfassen, sofern jq im Image vorhanden ist.
Ergebnis prüfen
Bei einem Lauf mit gültiger Attestierung steht im JSON-Bericht:
"run": { "build_authorization": "operator_attested", "mode": "check", "scope": "diff", … }
Fehlt die Attestierung oder ist sie ungültig, laufen nur die statischen Prüfungen. Echtes Beispiel eines --ci-Laufs auf einem Rechner mit Richtlinie, aber ohne Attestierungsdatei:
CODEGUARD_BLOCKED
1 blocking violations found in 1 changed files.
Validation: file (partial)
Exit: 6 (security_policy)
Requested: compile, format, security, swiftlint, swiftlint_metrics, tests
Completed: format, security, swiftlint, swiftlint_metrics
Skipped: compile, tests
Konfiguration: .codeguard.yml (auto, sha256 5967f32a…)
Tool: swift-format main [tool_swift_format] completed, 14ms, truncated: false
Tool: swiftlint 0.65.1 [tool_swiftlint] completed, 70ms, truncated: false
Tool: swiftlint 0.65.1 [tool_swiftlint_metrics] completed, 61ms, truncated: false
Run: cg_6259708c-32a9-4208-8dac-f8da3309cbcb
1. ERROR
Rule: codeguard-build-execution-denied
Build and test execution was not authorized: ci-untrusted execution requires an attested isolated runner: no declaration is installed at the organisation's execution-environment path.
Grant project trust, or run inside an attested isolated environment.
Next action: Request human review for the reported findings.
Die Meldung nennt den Grund. Prüfe dann Pfad, Eigentümer, Rechte und den exakten Inhalt der Attestierung sowie die Richtlinie.
Exit-Codes im Workflow
| Exit | Bedeutung für den Pull Request |
|---|---|
| 0 | bestanden |
| 1 | blockierende Funde, Bericht im Artefakt ansehen |
| 2 | Konfiguration oder Basis-Referenz falsch (z. B. fetch-depth fehlt) |
| 3 | Werkzeug, SDK, Simulator-Runtime oder Abhängigkeit fehlt auf dem Runner |
| 4 | Werkzeugfehler, Bericht ansehen (codeguard-tool-output-unparseable) |
| 5 | Zeitlimit einer Phase überschritten |
| 6 | Attestierung fehlt, geschützte Datei geändert oder Pfad außerhalb des erlaubten Bereichs |
| 9 | Installation unvollständig oder interner Fehler |
| 64 | Bedienfehler im Aufruf (unbekannte Option) |
Simulatoren auf dem Runner
CodeGuard legt für Tests Wegwerf-Simulatoren im Standard-Device-Set des Runner-Benutzers an und löscht sie danach. Auf einem ephemeren Runner verschwinden auch die bleibenden Logs mit der Umgebung. Bricht der Job hart ab (Timeout, Abbruch), kann ein Gerät zurückbleiben. Der nächste Lauf auf demselben Runner räumt es ab. Siehe Trust und Sicherheit.