Zum Hauptinhalt springen

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 grant ist verboten.
  • compile und tests starten 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 root abzulegen.
  • Das Projekt liegt im Wurzelverzeichnis des Repositorys. check-diff bricht 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.

  1. Xcode installieren und auswählen. xcodebuild muss eine Version >= 26.0.0 und < 28.0.0 haben. 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.

  2. CodeGuard installieren:

    git clone <REPOSITORY-URL> /tmp/CodeGuard # Platzhalter: Repository-URL eintragen
    cd /tmp/CodeGuard
    Scripts/install.sh # nach /usr/local/bin, root:wheel
  3. SwiftLint als root-eigene Kopie bereitstellen, z. B.:

    sudo install -o root -g wheel -m 0755 "$(realpath "$(command -v swiftlint)")" /usr/local/bin/swiftlint
  4. Organisationsrichtlinie installieren:

    # /Library/Application Support/CodeGuard/policy.yml
    policy_version: 1
    configuration_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/bin
    execution_environment:
    declaration_path: /Library/Application Support/CodeGuard/execution-environment.yml
    require_for_untrusted_builds: true
    sudo 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_roots an, wenn Xcode nicht unter /Applications/Xcode.app liegt.

  5. 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.yml
    sudo install -o root -g wheel -m 0644 execution-environment.yml \
    "/Library/Application Support/CodeGuard/execution-environment.yml"
    gefahr

    Mit 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.

  6. Image prüfen (als der Benutzer, unter dem der Runner läuft):

    codeguard version
    codeguard --ci config validate
    codeguard --ci doctor

    config validate muss policy: /Library/Application Support/CodeGuard/policy.yml (sha256:…) zeigen, doctor ready: true, swift-format: active und swiftlint: active.

Schlüsselbund auf dem Runner

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.yml und gegebenenfalls .swift-format und .swiftlint.yml ins Wurzelverzeichnis committen. CodeGuard lädt .codeguard.yml automatisch.
  • Beachte: .github/workflows/**, .codeguard.yml, Package.swift, Package.resolved, *.xcconfig, project.pbxproj und 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/checkout klont standardmäßig nur den letzten Commit. Ohne die Basis-Revision endet check-diff mit Exit 2 (unbekannte Referenz) bzw. Exit 3 (Commit-Objekt fehlt).
  • Basis: Bei pull_request checkt actions/checkout standardmäßig den Merge-Commit des Pull Requests aus. check-diff --base <base.sha> prüft damit genau die Änderungen des Pull Requests.
  • set +e und exit $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.
Mehrere Formate

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​

ExitBedeutung für den Pull Request
0bestanden
1blockierende Funde, Bericht im Artefakt ansehen
2Konfiguration oder Basis-Referenz falsch (z. B. fetch-depth fehlt)
3Werkzeug, SDK, Simulator-Runtime oder Abhängigkeit fehlt auf dem Runner
4Werkzeugfehler, Bericht ansehen (codeguard-tool-output-unparseable)
5Zeitlimit einer Phase überschritten
6Attestierung fehlt, geschützte Datei geändert oder Pfad außerhalb des erlaubten Bereichs
9Installation unvollständig oder interner Fehler
64Bedienfehler 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.

Siehe auch​