fix(ci): Fork-PR-Risiko für den docker-host-Runner dokumentieren (ADR-0003) #50
3 changed files with 87 additions and 6 deletions
|
|
@ -153,11 +153,14 @@ jobs:
|
|||
# (erreichbar über das Default-Gateway des Job-Netzes). Supabase-Ports liegen dann im dind;
|
||||
# die Supabase-CLI nimmt den Host aus DOCKER_HOST automatisch.
|
||||
# Nutzt die eingecheckte supabase/config.toml. Ohne passenden Runner Job bei Bedarf deaktivieren.
|
||||
# SICHERHEIT: siehe Kommentar am Job e2e und docs/adr/0003-ci-runner-sicherheit.md. Der `if:` ist nur Komfort
|
||||
# (keine hängenden Läufe für Fork-PRs), kein Schutz.
|
||||
supabase-tests:
|
||||
runs-on: docker-host
|
||||
concurrency:
|
||||
group: docker-host-supabase
|
||||
cancel-in-progress: false
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
env:
|
||||
DOCKER_CLI_VERSION: 27.3.1
|
||||
DIND_PORT: 2375
|
||||
|
|
@ -189,11 +192,13 @@ jobs:
|
|||
# Web-E2E (Playwright: api, web, throttle) gegen lokalen Supabase-Stack und gebaute Flutter-Web-App (Issue #38, Spec #1 D2).
|
||||
# Läuft wie supabase-tests auf "docker-host" (Docker-CLI + dind per TCP). Der Stack liegt im dind; ein
|
||||
# TCP-Relay (e2e/support/tcp-forward.mjs) macht ihn im Job unter 127.0.0.1 erreichbar.
|
||||
# SICHERHEIT: Der Job führt PR-Code mit Zugriff auf den dind-Daemon des Runners aus (Daemon = Root-Äquivalent).
|
||||
# Der `if:` unten ist KEIN Schutz vor böswilligen Forks: Bei pull_request kommt der Workflow aus dem PR-Head,
|
||||
# ein Fork kann die Bedingung einfach entfernen. Er verhindert nur versehentliche Läufe. Die echte Absicherung
|
||||
# gehört in Repo-/Runner-Einstellungen (Freigabe für Fork-PRs, Runner nur für vertrauenswürdige Repos; Folge-Issue).
|
||||
# Gleiches Risiko hat supabase-tests.
|
||||
# SICHERHEIT (ADR-0003, Issue #43): Der Job führt PR-Code mit Zugriff auf den dind-Daemon des Runners aus
|
||||
# (Daemon = Root-Äquivalent). Der `if:` unten ist KEIN Schutz vor böswilligen Forks: Bei pull_request kommt die
|
||||
# Workflow-Datei aus dem PR-Head, ein Fork kann die Bedingung entfernen. Er verhindert nur, dass ehrliche Fork-PRs
|
||||
# auf einen Lauf warten, den niemand freigibt. Die tatsächliche Absicherung liegt außerhalb dieser Datei:
|
||||
# Freigabe-Pflicht für Fork-PRs (Maintainer prüft die Workflow-Änderungen vor „Freigeben“) und ein isolierter
|
||||
# Runner/Daemon. Schritt-für-Schritt-Anleitung und Restrisiken: docs/adr/0003-ci-runner-sicherheit.md.
|
||||
# Gleiches gilt für supabase-tests.
|
||||
# needs + concurrency: Beide Jobs belegen dieselben festen Container-Namen/Ports (project_id shuffle, 54321 ...) im dind.
|
||||
# `needs` serialisiert innerhalb eines Laufs; die concurrency-Gruppe (auch an supabase-tests) soll Läufe verschiedener
|
||||
# PRs hintereinander schalten. Ob Forgejo 8.0.3 / Runner v5.0.4 Job-concurrency auswertet, ist hier nicht verifiziert;
|
||||
|
|
|
|||
76
docs/adr/0003-ci-runner-sicherheit.md
Normal file
76
docs/adr/0003-ci-runner-sicherheit.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# ADR-0003: CI-Sicherheit – Fork-PRs und der Runner `docker-host`
|
||||
|
||||
- **Status:** Vorgeschlagen (Chris entscheidet über die Maßnahmen unten), Issue #43
|
||||
- **Datum:** 2026-11-03
|
||||
|
||||
## Kontext
|
||||
Das Repo ist öffentlich. Der Runner `shuffle-mac-runner` (Label `docker-host`) läuft auf Chris' Mac mit Docker-in-Docker;
|
||||
die Jobs `supabase-tests` und `e2e` sprechen den dind-Daemon per TCP an (`DOCKER_HOST=tcp://<Gateway>:2375`, ohne TLS).
|
||||
Wer dort Code ausführt, hat Root-Äquivalent im dind (Container mit Host-Mounts, Zugriff auf alles, was der dind sieht).
|
||||
|
||||
Bei `on: pull_request` wird die Workflow-Datei aus dem PR-Head gelesen. Ein Fork-PR kann Bedingungen wie
|
||||
`head.repo.full_name == github.repository` entfernen oder ein beliebiges `runs-on:` setzen. **Ein `if:` in `ci.yml` schützt
|
||||
daher nicht** und kein Inhalt des Repos kann einen Fork-PR daran hindern, `runs-on: docker-host` anzufordern.
|
||||
|
||||
Stand der Prüfung (Forgejo 8.0.3+gitea-1.22.0, Runner v5.0.4): Die Aussagen zum Verhalten stammen aus Kenntnis der
|
||||
Gitea-1.22-/Forgejo-8-Quellen und der Doku, **nicht** aus einem Live-Test. Prüfschritte stehen unten.
|
||||
|
||||
## Was Forgejo 8.0.3 bietet
|
||||
| Mechanismus | Verfügbarkeit | Bewertung |
|
||||
|---|---|---|
|
||||
| Freigabe für Fork-PR-Workflows (`need approval`) | Eingebaut, **keine Repo-Einstellung zum Ein-/Ausschalten**. Läufe von Fork-PRs (Event `pull_request`) von Nutzern ohne Schreibrecht warten auf „Freigeben“ durch einen Maintainer. | Hauptschutz. Schwäche: Nach einer Freigabe gelten spätere Läufe desselben Nutzers im Repo als freigegeben (Quelltext-Annahme), d. h. ein neuer Push mit geändertem Workflow läuft evtl. ohne erneute Freigabe. |
|
||||
| `pull_request_target` | Läuft mit Workflow aus dem Base-Branch | Nicht verwenden, um PR-Code auf `docker-host` zu bauen (klassische Pwn-Request-Lücke). |
|
||||
| Runner-Scoping (Instanz/Organisation/Nutzer/Repo) | Eingebaut | Begrenzt, *welche Repos* den Runner nutzen, nicht *welche Events*. Hilft hier nicht gegen Fork-PRs im selben Repo. |
|
||||
| Runner-Labels | Eingebaut | Jeder Workflow darf jedes Label anfordern. Kein Schutz. |
|
||||
| Event-/Repo-Filter im Runner (`runner.yaml`) | Nicht vorhanden | -- |
|
||||
| Repo privat stellen | Eingebaut | Beseitigt das Problem (Fremde können keine PRs öffnen), kostet Öffentlichkeit. |
|
||||
| Forks instanzweit verbieten (`app.ini`, Forgejo-Doku prüfen: `[repository] DISABLE_FORKS`/ähnlich) | In 8.0.3 nicht verifiziert | Nur als Prüfpunkt. |
|
||||
|
||||
## Entscheidung (Vorschlag)
|
||||
Verteidigung in drei Schichten; nur die erste lässt sich im Repo ausdrücken, der Rest braucht Chris:
|
||||
|
||||
1. **Repo (umgesetzt):** `if:`-Guard an `supabase-tests` und `e2e` bleibt als Komfort (ehrliche Fork-PRs hängen nicht in
|
||||
„wartet auf Freigabe“); Kommentare in `ci.yml` benennen ihn ausdrücklich als keinen Schutz.
|
||||
2. **Forgejo (Chris):** Freigabe-Pflicht nutzen und Disziplin: Fork-PR-Läufe **nie** freigeben, ohne vorher die Änderungen an
|
||||
`.forgejo/`, `e2e/`, `supabase/`, `app/pubspec*` und allen Skripten gelesen zu haben. Bis zur Isolation (3.) bevorzugt:
|
||||
Fork-PRs gar nicht ausführen, sondern den Branch lokal prüfen oder in ein Branch im Haupt-Repo übernehmen.
|
||||
3. **Runner/Mac (Chris):** Blast Radius des dind begrenzen (siehe Anleitung).
|
||||
|
||||
Solange 2. und 3. nicht erledigt sind, gilt `docker-host` als **nicht für externe Beiträge geeignet**. Empfehlung: vor dem
|
||||
ersten externen Beitrag, spätestens vor Release, entweder 3. umsetzen oder das Repo privat stellen.
|
||||
|
||||
## Anleitung für Chris (nicht automatisiert; nichts davon wurde ausgeführt)
|
||||
|
||||
### A. Verhalten prüfen (10 Minuten, empfohlen als Erstes)
|
||||
1. Zweites Forgejo-Konto ohne Schreibrecht anlegen (oder Test-Nutzer), Repo forken, im Fork `ci.yml` ändern
|
||||
(`if:`-Zeilen entfernen, harmlosen Schritt `echo hallo` ergänzen), PR gegen `main` öffnen.
|
||||
2. Erwartung: Lauf steht auf „Wartet auf Freigabe“ und startet erst nach Klick auf **Freigeben** (PR-Seite bzw. Actions-Lauf).
|
||||
Falls er ohne Freigabe startet: Das Repo sofort privat stellen (Einstellungen -> Repository -> Sichtbarkeit) und
|
||||
Runner pausieren (B.3), dann Forgejo-Version/Doku prüfen.
|
||||
3. Zweiten Push im selben PR machen: Läuft er jetzt ohne erneute Freigabe? Dann ist die Annahme „einmal freigegeben, immer freigegeben“ bestätigt.
|
||||
|
||||
### B. Auf dem Mac (Runner/dind)
|
||||
1. Prüfen, ob `docker`-Jobs (nicht nur `docker-host`) den dind erreichen: In einem Testlauf mit `runs-on: docker` einen Schritt
|
||||
`curl -s -m 3 http://<Default-Gateway>:2375/version` ausführen (Gateway wie in `ci.yml` aus `/proc/net/route`). Antwortet er,
|
||||
kann jeder Fork-PR-Code auf **jedem** Label den Daemon übernehmen. Dann dind-Port nur an einen eigenen Docker-Netzwerk-Alias
|
||||
binden, der nur `docker-host`-Jobs zugewiesen ist (`container.network`/`options` in der Runner-Konfiguration), oder
|
||||
TLS (Port 2376 mit Client-Zertifikaten) einführen, wobei die Zertifikate nur in `docker-host`-Jobs gemountet werden
|
||||
(`valid_volumes` in der Runner-Konfiguration).
|
||||
2. Besser: Runner in eine Wegwerf-VM (z. B. Lima/Colima/UTM) oder auf einen separaten Rechner/Hetzner-Server verlagern, ohne
|
||||
Host-Mounts, ohne Zugriff aufs LAN, ohne Zugangsdaten und ohne persönliche Daten auf dem Mac. Ziel: Ein Ausbruch aus dem dind
|
||||
trifft nichts Wertvolles. Eigenes Registrierungs-Token verwenden; `.runner` und Token nicht ins Repo und nicht in Logs.
|
||||
3. Notbremse: Runner stoppen (`launchctl`/Prozess beenden oder in Forgejo unter Einstellungen -> Actions -> Runner deaktivieren).
|
||||
4. Runner nur für dieses Repo registrieren (Repo-Scope statt Instanz-/Nutzer-Scope), damit andere Repos ihn nicht ansprechen können.
|
||||
|
||||
### C. In Forgejo
|
||||
1. Fork-PR-Läufe nur nach Review der Workflow-/Skriptänderungen freigeben (siehe Entscheidung 2.).
|
||||
2. Optional: Branch-Schutz für `main` mit **Geschützte Dateimuster** `.forgejo/**;e2e/**;supabase/config.toml`, damit
|
||||
Änderungen daran nie ohne Review in `main` landen (wirkt auf Pushes nach `main`, nicht auf PR-Läufe).
|
||||
3. Repo privat stellen, wenn externe Beiträge nicht gewollt sind (löst das Problem vollständig).
|
||||
4. Runner-Token regelmäßig erneuern.
|
||||
|
||||
## Konsequenzen
|
||||
- Externe Beiträge brauchen manuelle Freigabe und Review mit Blick auf CI-Dateien.
|
||||
- Fork-PRs bekommen für `supabase-tests`/`e2e` keine Prüfung, solange der `if:`-Guard steht; sie laufen dann nur auf `docker`
|
||||
(Format, Analyse, Tests, Web-Build). Ein Maintainer kann den Branch bei Bedarf ins Haupt-Repo übernehmen, um die Docker-Jobs zu starten.
|
||||
- Offen: Ergebnis der Prüfung A und Umsetzung B. Bis dahin Issue #43 nicht schließen.
|
||||
|
|
@ -202,7 +202,7 @@ Der Job `e2e` in `.forgejo/workflows/ci.yml` läuft auf dem Runner `docker-host`
|
|||
4. `.forgejo/scripts/e2e-run.sh` startet den TCP-Relay `e2e/support/tcp-forward.mjs` (127.0.0.1:54321/54322/54324/9998 -> dind-Gateway) und ruft `npx playwright test --project=api --project=web --project=throttle` auf. Der Relay ist nötig, weil die Ports im dind liegen, die Tests, die Web-App (CORS/Origin) und `e2e/support/db.ts` (erlaubt nur lokale Hosts) aber 127.0.0.1 erwarten. Die statische Auslieferung der Web-App auf Port 3000 übernimmt das `webServer` der Playwright-Konfiguration (`serve -s`).
|
||||
5. Bei Fehlern werden `e2e/playwright-report` und `e2e/test-results` (Traces) als Artefakt `playwright-<Lauf>` hochgeladen. Der Schritt „Aufräumen“ (`if: always()`) entfernt den Extra-Container und stoppt den Supabase-Stack.
|
||||
|
||||
Das Projekt `spike` (SMTP-Ausfall, `SPIKE_GOTRUE_URL`) läuft in CI nicht. Der Job hat `needs: supabase-tests` (gleiche Container-Namen und Ports im dind) und läuft nicht für Fork-PRs: Wer PR-Code auf `docker-host` ausführt, hat Zugriff auf den dind-Daemon des Runners.
|
||||
Das Projekt `spike` (SMTP-Ausfall, `SPIKE_GOTRUE_URL`) läuft in CI nicht. Der Job hat `needs: supabase-tests` (gleiche Container-Namen und Ports im dind) und läuft nicht für Fork-PRs (nur Komfort-Guard, kein Schutz vor böswilligen Forks): Wer PR-Code auf `docker-host` ausführt, hat Zugriff auf den dind-Daemon des Runners. Absicherung und offene Schritte: `docs/adr/0003-ci-runner-sicherheit.md`.
|
||||
|
||||
**Achtung Artefakte:** Playwright-Reports, Traces und Screenshots (CI-Artefakte, `test-results/`, `playwright-report/`) enthalten Tokens, Passwörter und Mailinhalte der angelegten Testkonten. Die Konten existieren nur in der Wegwerf-Instanz (lokale CLI/CI), dennoch Artefakte nicht in ein öffentliches Repo oder eine öffentliche Release-Seite legen und nur kurz aufbewahren. Die Tests lehnen nicht-lokale Hosts ab (`SUPABASE_URL`, `MAILPIT_URL`, `E2E_DB_URL`; CI-Hosts nur explizit über `E2E_ALLOWED_HOSTS`).
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue