# MDM Setup — Phasen ## Revisions-Log | Datum | Was geändert | |-------------|-----------------------------------------------------------------------------------------------| | 2026-05-10 | Initial: Phasen A–G mit Factory-Reset-Approach für Phase F | | 2026-05-24 | Phase F pivotiert auf Backup-Sandwich (TechLockdown-Stil); Scope erweitert um DNS-/VPN-Lock | | 2026-05-24-late | DEV-Test zeigt: VPN-Restrictions blocken Rebreak-eigene NEVPNManager-Calls. Scope korrigiert: VPN-Restrictions raus, DNS bleibt als Fallback-Layer. Saubere MDM-VPN-Lösung als Phase F.2 | ## Phase A ✅ Server-Bootstrap Erledigt vor 2026-05-10. - apt-update + apt-upgrade - Pakete installiert: nginx, postgresql, docker.io, certbot, python3-certbot-nginx, ufw, fail2ban - UFW konfiguriert: 22/tcp, 80/tcp, 443/tcp erlaubt, default-deny - fail2ban aktiv (SSH-Brute-Force-Schutz) - DNS: IONOS A-Record `mdm.rebreak.org` → 178.105.101.137 ## Phase B ✅ TLS-Zertifikat Erledigt vor 2026-05-10. - `certbot --nginx -d mdm.rebreak.org` ausgeführt - Cert liegt in `/etc/letsencrypt/live/mdm.rebreak.org/` - certbot.timer (systemd) erneuert automatisch ## Phase C ✅ NanoMDM Container + nginx-Vhost Erledigt 2026-05-10. **Was gemacht wurde:** 1. PostgreSQL-Datenbank `nanomdm` mit User `nanomdm` und Passwort aus `/root/.nanomdm_db_pass` angelegt 2. `ALTER USER nanomdm WITH PASSWORD '...'` explizit gesetzt (scram-sha-256 braucht explizites Passwort) 3. `pg_hba.conf` ergänzt für Docker-Netze (172.17.0.0/16, 172.18.0.0/16) 4. `listen_addresses` in `postgresql.conf` auf `localhost,172.17.0.1,172.18.0.1` erweitert 5. MDM CA generiert: `ca.key` + `ca.crt` in `/opt/nanomdm/certs/` 6. `/opt/nanomdm/.env` mit `NANOMDM_DB_PASS` geschrieben (chmod 600) 7. `/opt/nanomdm/docker-compose.yml` mit `network_mode: host` (kritisch, sonst postgres nicht erreichbar wegen NAT-Masquerade) 8. `docker compose up -d` — Container läuft, `starting server listen=127.0.0.1:9000` bestätigt 9. nginx-Vhost `/etc/nginx/sites-available/mdm.rebreak.org` geschrieben + in sites-enabled symlinkt 10. `nginx -t && systemctl reload nginx` 11. Externer Verify: `curl -sI https://mdm.rebreak.org/` → `HTTP/2 404` von nanomdm (korrekt, kein 502) **Bekannte Tücken aus diesem Setup:** - `micromdm/nanomdm` auf Docker Hub existiert nicht. Korrektes Image: `ghcr.io/micromdm/nanomdm:latest` - nanomdm v0.9 kennt `-storage postgres` nicht. Korrekt: `-storage pgsql` (bzw. `NANOMDM_STORAGE=pgsql`) - Docker-Compose-Netzwerk (172.18.x) geht via NAT durch Host — Postgres sieht externe IP als Source. Lösung: `network_mode: host` im Compose, dann verbindet nanomdm direkt zu `127.0.0.1:5432` - nginx 1.24 kennt `http2 on;` nicht (das ist nginx 1.25+). Korrekt: `listen 443 ssl http2;` ## Phase D ✅ Apple Push CSR generiert Erledigt 2026-05-10. ``` openssl req -newkey rsa:2048 -nodes \ -keyout /opt/nanomdm/certs/push.key \ -out /opt/nanomdm/certs/push.csr \ -subj '/CN=ReBreak MDM Push/O=Raynis/C=DE' chmod 600 /opt/nanomdm/certs/push.key ``` CSR-Content liegt in `/opt/nanomdm/certs/push.csr`. Der private Key `push.key` verlässt den Server nie. ## Phase D.0.5 ✅ mdmcert.download Signing-Request Erledigt 2026-05-10. **Warum dieser Schritt notwendig ist:** Apple Push Notification Service (APNS) für MDM akzeptiert keine rohen CSRs von Self-Hostern direkt im Apple Push Portal. Apple verlangt, dass die CSR von einem akkreditierten MDM-Vendor signiert wird. Self-Hoster ohne Apple-MDM-Vendor-Status nutzen `mdmcert.download` — ein Service des MicroMDM-Teams, der die CSR mit einem akzeptierten Vendor-Key gegen-signiert und encrypted per Email zurückschickt. **Was passiert:** 1. Wir schicken unseren CSR base64-encoded + eine Encryption-Cert an `https://mdmcert.download/api/v1/signrequest` 2. mdmcert.download signiert ihn mit ihrem Apple-akkreditierten Vendor-Key 3. Sie verschlüsseln das Ergebnis mit unserer Encryption-Cert (PKCS7) und senden es per Email an `hello@chahine-brini.com` 4. Das entschlüsselte Ergebnis (nicht der raw CSR, nicht das `.b64.p7`) wird im Apple Push Portal hochgeladen **Was gemacht wurde:** 1. Encryption-Keypair auf dem MDM-Server generiert: - Cert: `/opt/nanomdm/certs/mdmcert-encryption.crt` (public, wird an mdmcert.download geschickt) - Key: `/opt/nanomdm/certs/mdmcert-encryption.key` (chmod 600, verlässt Server nie) ```bash openssl req -new -newkey rsa:2048 -nodes \ -keyout /opt/nanomdm/certs/mdmcert-encryption.key \ -x509 -days 365 \ -out /opt/nanomdm/certs/mdmcert-encryption.crt \ -subj '/CN=ReBreak mdmcert encryption' chmod 600 /opt/nanomdm/certs/mdmcert-encryption.key ``` 2. Signing-Request an mdmcert.download abgeschickt (shared public API-Key aus micromdm-Source, öffentlich dokumentiert): ```bash PUSH_CSR_B64=$(base64 -w0 /opt/nanomdm/certs/push.csr) ENC_CRT_B64=$(base64 -w0 /opt/nanomdm/certs/mdmcert-encryption.crt) curl -X POST https://mdmcert.download/api/v1/signrequest \ -H "Content-Type: application/json" \ -H "User-Agent: micromdm/certhelper" \ -d "{\"csr\":\"$PUSH_CSR_B64\",\"email\":\"hello@chahine-brini.com\",\"key\":\"\",\"encrypt\":\"$ENC_CRT_B64\"}" ``` Antwort: `{"result":"success"}` **Naechster Schritt:** Email von mdmcert.download bei `hello@chahine-brini.com` prüfen. Anhang-Name hat Format `mdm_signed_request.YYYYMMDD_HHMMSS_NNN.plist.b64.p7`. Dann weiter mit Phase D.0.7. **Technische Details (wichtig fuer Decrypt):** - Der Dateiname endet auf `.b64.p7` — irreführend. Der tatsächliche Inhalt ist **hex-encoded PKCS7**, nicht base64. (Quelle: micromdm/micromdm cmd/mdmctl/mdmcert.download.go, Decrypt-Pfad) - Der Decrypt-Befehl (`openssl cms` oder PKCS7-Tooling) muss zuerst hex→binary decodieren, dann PKCS7 mit dem mdmcert-encryption.key entschlüsseln ## Phase D.0.7 ⏳ Signed CSR entschlüsseln **Voraussetzung:** Email von mdmcert.download mit Anhang empfangen (Phase D.0.5 abgeschlossen) **Wer:** Chahine schickt den Anhang per `scp` auf den MDM-Server. Oder Backyard entschlüsselt wenn Anhang auf den Server kopiert wurde. **Schritte:** 1. Anhang von Email speichern (z.B. `mdm_signed_request.20260510_XXXXXX.plist.b64.p7`) 2. Datei auf Server kopieren: ```bash scp ~/Downloads/mdm_signed_request.*.plist.b64.p7 rebreak-mdm:/opt/nanomdm/certs/signed_request.p7 ``` 3. Hex→Binary dekodieren + PKCS7 entschlüsseln (micromdm-Tooling macht beides intern): ```bash # Hex-String aus der Datei zu Binary konvertieren xxd -r -p /opt/nanomdm/certs/signed_request.p7 > /opt/nanomdm/certs/signed_request.der # PKCS7 mit unserem Encryption-Key entschlüsseln openssl cms -decrypt \ -in /opt/nanomdm/certs/signed_request.der \ -inform DER \ -inkey /opt/nanomdm/certs/mdmcert-encryption.key \ -recip /opt/nanomdm/certs/mdmcert-encryption.crt \ -out /opt/nanomdm/certs/push_request.plist ``` 4. Ergebnis `/opt/nanomdm/certs/push_request.plist` prüfen — sollte eine Apple Plist-Datei sein. ```bash head -5 /opt/nanomdm/certs/push_request.plist # Erwartete Ausgabe: https://mdm.rebreak.org/version` → `{"version":"v0.9.0"}` ✅ **Bekannte Tücke:** Initial-setup hat das postgres-schema nicht angewendet. NanoMDM-Container hat keine eingebaute migrate-step. Schema muss manuell via `psql -f schema.sql` geladen werden bevor erster API-call funktioniert. ## Phase E ⏸ Email-Distribution an Ina — geparkt (User-Decision 2026-05-10) **Status: PARKED — alles server-side ready, Versand verschoben.** User-Entscheidung: PIN-Versand an Ina jetzt nicht — wird später nachgeholt. iPhone-Enrollment kann ohne laufen (MASTER-PIN ist Recovery-Backup, nicht Voraussetzung für enrollment). Server-Status: - ✅ MASTER-Recovery-PIN auf Server: `/root/.nanomdm_master_pin` (chmod 600) - ✅ Ina-Email-Draft auf Server: `/root/INA_EMAIL_DRAFT.md` (chmod 600) - ✅ Resend-API-Key auf Server: `/root/.resend_api_key` (chmod 600) - ⏸ Resend-Domain-Verify ungetan — Versand würde fehlschlagen ohne `chahine-brini.com` oder `rebreak.org` verified Reaktivierung: User sagt „Phase E GO", wir verifizieren Domain in Resend, senden, fertig. Files bleiben bis dahin auf Server. ## Phase F ⏳ Device-Enrollment via Backup-Sandwich **Revidiert 2026-05-24** — alter Plan (Factory-Reset + Apple Configurator) war User-Friction-Killer. Niemand reset sich freiwillig sein iPhone. Neuer Plan: Backup-Sandwich-Approach wie TechLockdown / iMazing Configurator Edition. Phase F ist NICHT mehr auf Phase E blockiert (Ina-Email-Distribution kann nachgeholt werden). ### Mechanismus ``` 1. Backup (idevicebackup2 encrypted) → vollständig auf Mac 2. Supervise (cfgutil prepare) → wiped Gerät, Supervised-Flag wird gesetzt 3. Restore (idevicebackup2 restore) → Daten zurück, Supervised-Flag bleibt persistent 4. Enroll (mobileconfig install) → via QR-Code aus Rebreak-App, OTA über mdm.rebreak.org ``` Find-My-Disable ist Voraussetzung für Step 2 (Activation Lock blockt sonst den Wipe). Apple-ID-Passwort des Users wird live abgefragt — nicht automatisierbar. ### Komponenten dieser Phase - `ops/mdm/bootstrap-tool/` — Bash-Scripts orchestrieren Backup → Supervise → Restore auf User-Mac (Mac-only Phase 1; Windows = Phase 2 via iMazing-Lizenz oder libimobiledevice-Erweiterung) - `ops/mdm/profiles/rebreak-iphone-protection.mobileconfig` — Profil-Template mit den unten genannten Restrictions - `backend/server/api/mdm/enroll.get.ts` — User-spezifisches signed Profil ausliefern, plus QR-Code-Endpoint - `apps/rebreak-native/lib/mdm.ts` + `app/(protection)/mdm-setup.tsx` — Lyra-geführter Onboarding-Flow in der App ### Scope (erweitert 2026-05-24, revidiert 2026-05-24-late nach DEV-Test) Profil enthält: | Restriction | Wirkung | |----------------------------------------------|--------------------------------------------------------------| | `allowAppRemoval = false` | Rebreak (und alle anderen Apps) nicht löschbar via Long-Press — zeigt nur "Vom Home-Screen entfernen", App bleibt in Mediathek (verifiziert auf TechLockdown-supervisem iPhone 2026-05-24) | | `PayloadRemovalDisallowed = true` | Profil nicht via Settings → Allgemein → VPN/Geräteverwaltung entfernbar | | `allowEraseContentAndSettings = false` | User kann iPhone nicht via Settings → Reset wipen | | `allowUIConfigurationProfileInstallation = false` | User kann keine konkurrierenden Profile installieren | | DNS-Settings-Payload (DoH) | System-DNS auf `dns.rebreak.org/dns-query` gelocked — always-on Fallback-Schicht | **VPN-Restrictions bewusst RAUS** (Test-Befund 2026-05-24): `allowVPNCreation=false` blockt auch Rebreak-eigene `NEVPNManager`-Aufrufe ("Permission denied"). Apple unterscheidet im API-Call nicht zwischen User und App. Konsequenz: - App-VPN (Rebreak NEPacketTunnel) bleibt App-managed + user-toggleable — wie heute - MDM-DNS-Payload ist always-on Fallback: auch wenn User Rebreak-VPN ausschaltet, DNS-Filter greift weiterhin - Bypass-Vektor: User installiert 3rd-Party-VPN (z.B. ExpressVPN). Akzeptiert für Prototype — 5-min-Friktion, trifft planenden Rückfall nicht impulsiven - Saubere Lösung wäre **Phase F.2**: MDM-pushed-VPN mit `ProviderBundleIdentifier=org.rebreak.app.PacketTunnelExtension`, dann braucht App-Code kein eigenes `NEVPNManager.saveToPreferences` mehr → echtes "VPN nur via MDM" Bewusste Trade-offs: - `allowAppRemoval=false` ist GLOBAL — kein per-Bundle-ID-Lock möglich ohne MDM-managed-Convert (zusätzlicher InstallApplication-Command, Phase F.5 später). Für Prototype akzeptiert: User der sich self-bindet darf auch andere Support-Apps nicht löschen — Feature, kein Bug. - Determinierter User kann via zweitem Mac unsupervisen (ABM-ADE wäre der einzige echte Hard-Lock, ist aber strukturell nicht erreichbar für Consumer-iPhones). Akzeptabel für DiGA-Sucht-Kontext: wir hoben Friktion, nicht Festung. Bewusst NICHT im Scope: - KEIN App-Store-Block (Casino-Apps gibt's eh nicht im iOS-App-Store) - KEINE Web-Content-Filter-Payload (Browser-Casinos werden vom Rebreak-NEFilter geblockt) - KEINE Restriktionen die nicht direkt mit Casino-Bypass-Prevention zu tun haben ### Hardware-/Tool-Voraussetzungen - Mac mit macOS (User-Mac, NICHT Server-Mac) — für cfgutil + libimobiledevice - USB-Kabel iPhone↔Mac - Apple Configurator 2 (kostenlos, Mac App Store) — für `cfgutil` CLI - libimobiledevice via `brew install libimobiledevice` — für `idevicebackup2` - Supervision-Identity einmalig generiert via cfgutil (persistent, gleicher Mac reused) - iPhone mit deaktiviertem Find-My (live während Setup) ### Akzeptanz-Test (M2) Auf einem physischen Test-iPhone nach kompletter Sandwich-Sequenz: - [ ] `Settings → Allgemein → Info` zeigt "Dieses iPhone wird verwaltet/beaufsichtigt" - [ ] Long-Press auf Rebreak-Icon → kein "App löschen" mehr - [ ] `Settings → VPN → Rebreak` → Toggle disabled / nicht entfernbar - [ ] `Settings → Allgemein → VPN, DNS und Gerätemanagement` → Profil zeigt "Nicht entfernbar" - [ ] Daten/Apps/Login-States/iMessage-History intakt nach Sandwich - [ ] Rebreak-App erkennt MDM-Enrollment-Status via Backend-Check und unlockt Pro/Legend-Schutz-UI ## Phase G ⏳ iPad-Enrollment (optional, später) Identisch zu Phase F, gleicher flow: 1. iPad via USB-C mit Mac verbinden 2. Apple Configurator 2 → Supervised-Mode → factory-reset 3. MDM-enrollment-profile von `https://mdm.rebreak.org/enroll` 4. ReBreak-iOS app installieren (läuft nativ auf iPad) 5. Verifyieren: ReBreak nicht entfernbar, MDM-profile nicht entfernbar **Aufwand:** ~30min nach Phase F. Apple Push Cert deckt iPad mit ab (kein zusätzlicher cert nötig). **Voraussetzung:** Phase F erfolgreich getestet auf iPhone. ## Phase H ⏳ MacBook-Enrollment (optional, später) Anders als iPhone/iPad weil: - **Kein ReBreak-Mac-app** existiert → MDM-profile muss eigene Blocking-Mechanik mitbringen - Lösung: **Web-Content-Filter-Payload** im profile (DNS/URL-blocklist auf OS-Ebene) - Mac-Supervised-Mode: factory-reset des MacBook nötig (analog iPad), via Apple Configurator 2 + USB-C **Schritte:** 1. ReBreak-Blocklist (~208k domains) als Web-Content-Filter-Payload formattieren - Payload-type: `com.apple.webcontent-filter` - oder `com.apple.dnsSettings.managed` für DNS-level-block 2. MDM-profile assemblen mit: - `allowMDMProfileRemoval=false` (braucht supervised-mode) - Web-Content-Filter mit Casino-Blocklist - Optional: `allowSafariAutoFill=false` (verhindert auto-login auf bekannten casino-sites) 3. MacBook factory-reset → Apple Configurator 2 → supervised-mode → MDM-enrollment 4. Verify: Casino-domain im Browser → blocked **Aufwand:** ~1 Tag (blocklist-conversion + profile-assembly + test). Plus factory-reset-zeit. **Voraussetzung:** - Phase F+G erfolgreich - User explizites GO (factory-reset MacBook = großer Schritt) - Backup von wichtigen MacBook-Daten **Tradeoff:** Kein ReBreak-Mac-app = nur URL-blocking, keine SOS-features, kein Lyra, keine Community auf Mac. Wer ReBreak-features auf Mac will, braucht später entweder native Mac-app (s. `ops/mac-version-research.md`) oder Browser-Extension.