platform/docs/01-architecture-and-threat-model.md
Bart Van Geyt 9f819df4d9 Phase 0: architecture docs, ADRs, and repo scaffold
Establish the design foundation for the heleosv2 multi-tenant hosting
platform before any implementation code:

- Monorepo skeleton: docs/, platform-infra/, site-templates/,
  deployments/, control-panel/ with orientation READMEs.
- docs/: roadmap index, architecture + threat model, naming conventions,
  site profiles, provisioning workflow, backup & DR runbook, repo/GitOps
  layout, and the approved architecture plan.
- docs/adr/: 9 ADRs recording the rationale for single-host Compose,
  Traefik edge, nginx+fpm split, shared MariaDB, ZFS-per-customer,
  decoupled backup streams, Forgejo, CLI-first, and SFTP-only.
- Secrets hygiene: .gitignore (only *.enc.* committed) and .gitattributes
  (LF for scripts/Dockerfiles/YAML run on the Linux host).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 12:26:00 +02:00

124 lines
6.7 KiB
Markdown

# 01 — Architecture & Threat Model
## 1. Purpose
Rebuild the hosting business as an **isolation-first, backup-clean,
repeatably-provisioned** platform on a single bare-metal host. Serves a mix of
WordPress, custom PHP, static HTML, and redirect-only sites.
## 2. High-level architecture
```
Internet
(80/443, SFTP, admin SSH)
┌────────▼────────┐
│ Traefik │ edge: TLS termination, ACME,
│ (edge router) │ dynamic label-based routing
└───┬─────────┬───┘
│ │ shared "proxy" network
┌───────────────┘ └───────────────┐
│ per-customer network A │ per-customer network B
┌────▼─────┐ ┌──────────┐ ┌─────▼────┐ ┌──────────┐
│ nginx │──▶│ php-fpm │ │ nginx │──▶│ php-fpm │
│ (site A) │ │ (site A) │ │ (site B) │ │ (site B) │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ web root vol │ │ web root vol │
│ └──────────┐ ┌──────────┘ │
│ ▼ ▼ │
│ ┌─────────────────┐ │
│ │ shared MariaDB │ per-site DB + │
│ │ (platform net) │ least-priv user │
│ └─────────────────┘ │
▼ ▼
ZFS tank/customers/A/web ZFS tank/customers/B/web
```
Platform services (not shown per-customer): Forgejo + registry, Prometheus /
Grafana / Loki, Uptime-Kuma. All run as their own compose projects on the host.
### Request path
1. DNS points the customer domain at the host.
2. Traefik terminates TLS (Let's Encrypt) and routes by Host rule (from compose
labels) onto the customer's network.
3. For PHP profiles: nginx serves static assets and proxies `.php` over FastCGI
to that site's php-fpm. For static/redirect: Traefik or a tiny nginx answers
directly.
4. php-fpm talks to shared MariaDB over the platform network using the site's
own database + least-privilege credentials.
### Why Traefik *and* nginx (not either/or)
Traefik does not speak FastCGI, so it cannot talk to php-fpm directly. Traefik
owns the edge (TLS, routing, ACME); a small per-site nginx bridges HTTP→FastCGI.
This is deliberate, not redundancy.
## 3. Isolation boundaries
| Boundary | Mechanism | Protects against |
|----------|-----------|------------------|
| Process/filesystem | Separate containers per site | One site reading another's files/processes |
| Network (east-west) | Per-customer Docker bridge; only Traefik bridges to `proxy` | Site A reaching Site B's containers |
| Data at rest | ZFS dataset per customer, bind-mounted web root | Cross-customer data access; enables clean restore |
| Database | Per-site DB + least-privilege user on shared instance | Site A reading Site B's tables |
| Privilege | php-fpm non-root; read-only rootfs where possible; writable web root only | Privilege escalation within a container |
| Egress | Firewall egress filtering (esp. SMTP) | Hacked site sending spam / exfiltration |
## 4. Threat model
### 4.1 Assets
- Customer website files and databases.
- The host itself (kernel, Docker daemon, ZFS pool).
- Platform credentials (DB root, Traefik/ACME, Forgejo, SSH).
- Backups (onsite snapshots + offsite copies).
### 4.2 Primary threat actors & scenarios
1. **Compromised WordPress/PHP app** (most likely). Attacker gets code execution
inside one site's php-fpm container.
2. **Malicious/abusive tenant.**
3. **Credential theft** (leaked DB or SFTP creds).
4. **External network attacker** probing exposed ports.
### 4.3 What each scenario can and cannot do
**Compromised app container (S1):**
- ✅ Contained to: that site's files, that site's DB (its creds only), its own
network namespace.
- ❌ Blocked from: other customers' files (separate datasets/containers), other
DBs (least-privilege user), other customers' networks (no route), spamming
(egress SMTP filtered).
- ⚠️ **Residual risk:** containers share the host kernel — a kernel or Docker
escape breaks isolation. Mitigate with: patched host, non-root FPM, dropped
capabilities, no `--privileged`, read-only rootfs, seccomp defaults; later
gVisor and/or Falco runtime detection.
**Malicious tenant (S2):** same containment as S1, plus resource limits
(CPU/memory per compose project) to prevent noisy-neighbour DoS.
**Credential theft (S3):** blast radius limited to that one site because every
site has its own DB user and its own SFTP chroot. Rotate via CLI.
**Network attacker (S4):** only 80/443, SFTP, and admin SSH are exposed;
everything else denied by nftables. Admin SSH key-only + hardened.
### 4.4 Explicit non-goals / accepted risks
- **Containers are not a hardened sandbox.** We accept shared-kernel risk on a
single host and mitigate in depth rather than claiming VM-grade isolation.
- **Backups are crash-consistent, not transactionally atomic** across the
web-file and DB streams (see [06](06-backup-and-dr.md)). Acceptable for
PHP/WordPress workloads.
- No HA / multi-host failover in the initial design (single host by choice).
## 5. Security controls checklist (implemented across phases)
- [ ] Host: nftables default-deny inbound, egress SMTP filtered, SSH key-only.
- [ ] Docker: no `--privileged`, drop capabilities, read-only rootfs where
possible, per-project resource limits, userns considered.
- [ ] Per-customer network isolation; Traefik the only cross-network bridge.
- [ ] php-fpm runs as non-root; web root the only writable mount.
- [ ] Per-site DB users with least privilege; no shared DB accounts.
- [ ] SFTP chrooted per customer.
- [ ] Images scanned (Trivy) and secrets scanned (gitleaks) in CI.
- [ ] Automatic TLS via Traefik/ACME; HSTS.
- [ ] (Later) Falco runtime anomaly detection; gVisor for higher-risk tenants.
See [ADRs](adr/) for the rationale behind each major choice.