NEURLFilter-Stack (iOS 26): Extension RebreakURLFilter -> URLFilterExtension umbenannt, url-filter-provider-Entitlement, Bloom-Prefilter-Extension, PIR-Client-Config (pirServerURL/pirAuthToken via Build-Env). PIR-Server-Ops unter ops/pir-server/ (Dockerfile, build-and-deploy, Patches, DTS-Report). backend/scripts/generate-pir-input.ts erzeugt die PIR-Datenbank. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
168 lines
8.1 KiB
Markdown
168 lines
8.1 KiB
Markdown
# PIR-Server — iOS NEURLFilter Backend
|
||
|
||
**Status: LIVE (Staging)** — `https://pir.staging.rebreak.org` · Stand 2026-05-21
|
||
|
||
Dieses Dokument beschreibt den PIR-Server vollständig, damit eine andere Session
|
||
ihn verstehen, betreiben und weiterentwickeln kann.
|
||
|
||
---
|
||
|
||
## 1. Was & Warum
|
||
|
||
Rebreaks iOS-Gambling-Blocker nutzt Apples **`NEURLFilter`** (iOS 26). NEURLFilter
|
||
filtert URLs **systemweit** (Safari + alle WebKit-Browser + `URLSession`) und
|
||
verlangt dafür **zwingend** einen vom Vendor gehosteten **PIR-Server** (Private
|
||
Information Retrieval) — `NEURLFilterManager.setConfiguration()` akzeptiert keine
|
||
Konfiguration ohne `pirServerURL`. Dieser Server ist dieser PIR-Server.
|
||
|
||
**Ablauf zur Laufzeit:**
|
||
1. Das Gerät prüft die URL gegen einen lokalen **Bloom-Filter** (Prefilter, in der
|
||
iOS-App-Extension `NEURLFilterControlProvider`).
|
||
2. Bei einem Bloom-Treffer → **PIR-Lookup** gegen diesen Server, geroutet über
|
||
Apples Oblivious-HTTP-Relay.
|
||
3. Per Homomorphic Encryption sieht der Server die geprüfte URL **nie im Klartext**.
|
||
|
||
**DSGVO/DiGA-Vorteil:** Kein Beteiligter sieht URL + Identität zusammen — Rebreak
|
||
erfährt das Surfverhalten der User nie. Datenminimierung per Krypto-Konstruktion.
|
||
|
||
---
|
||
|
||
## 2. Architektur-Bausteine
|
||
|
||
Basiert auf Apples Open-Source-Vorlagen:
|
||
- `apple/pir-service-example` → `PIRService` (Swift/Hummingbird-HTTP-Server, bedient
|
||
PIR-Queries + eingebauten PrivacyPass-Issuer) + `ConstructDatabase`.
|
||
- `apple/swift-homomorphic-encryption` → `PIRProcessDatabase` (wandelt `input.txtpb`
|
||
in PIR-optimierte Shards).
|
||
|
||
---
|
||
|
||
## 3. Deployment-Stand (Hetzner `rebreak-server`, 49.13.55.22)
|
||
|
||
| Komponente | Ort / Wert |
|
||
|---|---|
|
||
| Docker-Container | `pir-service-staging`, `restart=unless-stopped`, `127.0.0.1:8090->8090` |
|
||
| Docker-Image | `pir-service-staging:latest` (Multi-Stage Swift-Build, ~5 GB) |
|
||
| Build-Repos | `/srv/pir-build/{pir-service-example,swift-homomorphic-encryption}` + `Dockerfile` |
|
||
| Deploy-Verzeichnis | `/srv/pir-server/` (`build-and-deploy.sh`, `gen-test-input.sh`, `config/`, `data/`) |
|
||
| PIR-DB-Artifacts | `/srv/pir-server/data/` — `input.txtpb` + `url-0..3.bin` + `url-0..3.params.txtpb` (4 Shards) |
|
||
| Service-Config | `/srv/pir-server/config/service-config.json` (Token inline — NICHT im Repo) |
|
||
| nginx | `/etc/nginx/sites-{available,enabled}/pir-staging.rebreak.org`, `server_name pir.staging.rebreak.org` |
|
||
| TLS | Let's Encrypt für `pir.staging.rebreak.org`, läuft 2026-08-18 ab, Auto-Renew aktiv |
|
||
| Public-URL | `https://pir.staging.rebreak.org` |
|
||
|
||
---
|
||
|
||
## 4. Konfiguration
|
||
|
||
**`service-config.json`** (auf dem Server; Token nicht im Repo → siehe `service-config.template.json`):
|
||
- usecase-Name: **`org.rebreak.app.url.filtering`** — muss EXAKT mit der iOS-`setConfiguration` matchen, sonst HTTP 404.
|
||
- `shardCount`: **4** (215k Domains / 50k pro Shard).
|
||
- `users[].tokens`: enthält den `PIR_AUTH_TOKEN`.
|
||
|
||
**`PIR_AUTH_TOKEN`** — liegt in **Infisical**, Env `staging`, Key `PIR_AUTH_TOKEN`,
|
||
Project-ID `14b11b35-ef59-4b8a-a16b-398f0cc3ad93`. App-weit halten (NICHT pro-User —
|
||
ein per-User-Token würde die PIR-Nicht-Verkettbarkeit schwächen).
|
||
|
||
**`process-config.json`** — Parameter für `PIRProcessDatabase`:
|
||
`entryCountPerShard 50000`, `rlweParameters n_4096_logq_27_28_28_logt_5`,
|
||
`databaseType keyword`.
|
||
|
||
**`PIR_ISSUER_REQUEST_URI`** — Env-Var am Container (`docker run -e ...`), gesetzt von
|
||
`build-and-deploy.sh` auf `https://pir.staging.rebreak.org/issue`. **Zwingend absolut**
|
||
(RFC 9578 §6): das Apple-`pir-service-example` advertised von Haus aus nur ein
|
||
**relatives** `/issue` im Issuer-Directory (`PrivacyPassController.swift`, hardcodiert) —
|
||
der NEURLFilter-Client kann damit keinen Privacy-Pass-Token holen und scheitert mit
|
||
`serverSetupIncomplete`. Behoben durch `patches/0001-absolute-issuer-request-uri.patch`,
|
||
das die URL aus dieser Env-Var liest.
|
||
|
||
---
|
||
|
||
## 5. iOS-Integrationswerte (`NEURLFilterManager.setConfiguration`)
|
||
|
||
| Parameter | Wert |
|
||
|---|---|
|
||
| `pirServerURL` | `https://pir.staging.rebreak.org` |
|
||
| `pirPrivacyPassIssuerURL` | `https://pir.staging.rebreak.org` (PIRService bedient beides) |
|
||
| `pirAuthenticationToken` | Infisical `staging/PIR_AUTH_TOKEN` |
|
||
| usecase-Name | `org.rebreak.app.url.filtering` |
|
||
| `controlProviderBundleIdentifier` | `org.rebreak.app.URLFilterExtension` (geplant — beim iOS-Scaffolding final festlegen) |
|
||
|
||
---
|
||
|
||
## 6. Daten-Pipeline (Blocklist → PIR-DB)
|
||
|
||
1. `backend/scripts/generate-pir-input.ts` erzeugt `input.txtpb` aus ~215k Gambling-Domains.
|
||
- **DB-Modus** (Default): liest die `BlocklistDomain`-Tabelle — braucht `infisical run` für `DATABASE_URL`.
|
||
- **HaGeZi-Modus** (`--source hagezi`): direkt von HaGeZis `gambling.txt`, kein DB/Infisical
|
||
nötig. **Aktuell verwendet**, weil `infisical` nicht auf dem Dev-Mac liegt. Äquivalent —
|
||
die `BlocklistDomain`-Tabelle wird ohnehin aus genau dieser HaGeZi-Liste gespeist.
|
||
- Output-Format: pro Domain eine Zeile `rows: { keyword: "<domain>" value: "1" }`.
|
||
2. `input.txtpb` → per `scp` nach `/srv/pir-server/data/input.txtpb`.
|
||
3. `PIRProcessDatabase /data/process-config.json` (im Container) → `url-N.bin` + `url-N.params.txtpb`.
|
||
|
||
---
|
||
|
||
## 7. Betrieb / Runbook
|
||
|
||
**Health-Check:**
|
||
```bash
|
||
curl -s -o /dev/null -w '%{http_code}\n' \
|
||
https://pir.staging.rebreak.org/.well-known/private-token-issuer-directory # → 200
|
||
ssh rebreak-server 'docker ps | grep pir-service-staging'
|
||
```
|
||
|
||
**Blocklist aktualisieren:**
|
||
1. Neue `input.txtpb` generieren (`backend/scripts/generate-pir-input.ts`) + nach
|
||
`/srv/pir-server/data/input.txtpb` scp'en.
|
||
2. Alte Shards löschen: `ssh rebreak-server 'rm /srv/pir-server/data/url-*.bin /srv/pir-server/data/url-*.params.txtpb'`.
|
||
3. `PIRProcessDatabase` neu laufen lassen (siehe `build-and-deploy.sh` Step 6) — erzeugt neue Shards.
|
||
4. `shardCount` in `service-config.json` prüfen + Container neu starten.
|
||
|
||
**Komplett neu bauen/deployen:**
|
||
```bash
|
||
ssh rebreak-server 'bash /srv/pir-server/build-and-deploy.sh'
|
||
```
|
||
Swift-Build ~20–30 Min, RAM-intensiv (~2–3 GB → Swap nötig, off-peak bauen).
|
||
|
||
---
|
||
|
||
## 8. Bekannte Issues / TODOs
|
||
|
||
- ✅ **`build-and-deploy.sh` shardCount-Bug (BEHOBEN 2026-05-21):** Step 3 leitet den
|
||
`shardCount` jetzt aus den vorhandenen `url-*.bin`-Artifacts ab statt ihn fix auf `1`
|
||
zu setzen — kein Regress mehr von 4 Shards auf 1 bei Re-Runs.
|
||
- ✅ **Relatives `issuer-request-uri` (BEHOBEN 2026-05-21):** siehe §4 `PIR_ISSUER_REQUEST_URI`.
|
||
War die Ursache für `serverSetupIncomplete` auf der iOS-Seite. Fix via
|
||
`patches/0001-absolute-issuer-request-uri.patch`, von `build-and-deploy.sh` Step 3b
|
||
automatisch auf den `pir-service-example`-Clone angewandt.
|
||
- Deploy-Skripte liegen sowohl auf dem Server (`/srv/pir-server/`, `/srv/pir-build/`)
|
||
als auch hier im Repo. Bei Änderungen synchron halten — oder das Repo als
|
||
Single-Source-of-Truth etablieren (empfohlen).
|
||
- **App-Store-Distribution:** braucht zusätzlich Apples OHTTP-Onboarding-Formular,
|
||
ein OHTTP-Gateway und einen DNS-TXT-Record `apple-url-filter=org.rebreak.app`.
|
||
Für TestFlight/Dev NICHT nötig. → Phase 2.
|
||
- **Entitlement** `com.apple.developer.networking.networkextension.url-filter-provider`:
|
||
Dev-Builds ausgenommen; vor TestFlight/Store über den „Capability Requests"-Tab im
|
||
Apple-Developer-Portal (Certificates, Identifiers & Profiles → Identifiers) beantragen.
|
||
- Apple zu `pir-service-example`: „example service, not for production" —
|
||
Produktionshärtung (persistente PrivacyPass-Keys statt ephemerer, Monitoring,
|
||
Ressourcen-Tuning) ist ein eigener späterer Schritt.
|
||
|
||
---
|
||
|
||
## 9. Dateien in diesem Verzeichnis
|
||
|
||
| Datei | Zweck |
|
||
|---|---|
|
||
| `README.md` | dieses Dokument |
|
||
| `Dockerfile` | Multi-Stage Swift-Build (Kopie von `/srv/pir-build/Dockerfile`) |
|
||
| `build-and-deploy.sh` | Deploy-Script (Kopie von `/srv/pir-server/`) |
|
||
| `gen-test-input.sh` | Test-DB-Generator (10 Domains, Fallback) |
|
||
| `process-config.json` | `PIRProcessDatabase`-Parameter |
|
||
| `service-config.template.json` | Service-Config-Vorlage (Token-Platzhalter) |
|
||
| `nginx-pir.staging.rebreak.org.conf` | nginx-Reverse-Proxy-Config (Referenz) |
|
||
| `patches/*.patch` | Quell-Patches für `pir-service-example`, von `build-and-deploy.sh` Step 3b angewandt |
|
||
|
||
Der `input.txtpb`-Generator selbst liegt bei `backend/scripts/generate-pir-input.ts`.
|