Adopt a two-level ownership model: a customer owns many sites, with isolation/containers/DB/backup at the site level and grouping (one SFTP login, recursive backup, billing, bulk delete) at the customer level. - 03-naming-conventions: rewritten for customer/site ids and slug = <customer>-<site>; nested ZFS, per-customer SFTP chroot, deployments mirror the nesting. - Propagated paths through docs 01, 04, 05, 06, 07 and deployments/README. - ADR 0005 retitled/updated to per-site datasets nested under customer. - architecture-plan.md: note pointing to doc 03 as authoritative on naming. Clarifies subdomains: same-app subdomains are aliases on one site; a separate-app subdomain is its own isolated site under the same customer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.9 KiB
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-site network (acme-shop_net) │ per-site network (bar-www_net)
┌────▼─────┐ ┌──────────┐ ┌─────▼────┐ ┌──────────┐
│ nginx │──▶│ php-fpm │ │ nginx │──▶│ php-fpm │
│acme/shop │ │acme/shop │ │ bar/www │ │ bar/www │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ web root vol │ │ web root vol │
│ └──────────┐ ┌──────────┘ │
│ ▼ ▼ │
│ ┌─────────────────┐ │
│ │ shared MariaDB │ per-site DB + │
│ │ (platform net) │ least-priv user │
│ └─────────────────┘ │
▼ ▼
tank/customers/acme/shop/web tank/customers/bar/www/web
The isolation unit is the site (<customer>/<site>). A customer may own
several sites, each fully isolated from the others but grouped under the customer
for SFTP/backup/billing (see 03).
Platform services (not shown per-site): Forgejo + registry, Prometheus / Grafana / Loki, Uptime-Kuma. All run as their own compose projects on the host.
Request path
- DNS points the customer domain at the host.
- Traefik terminates TLS (Let's Encrypt) and routes by Host rule (from compose labels) onto the customer's network.
- For PHP profiles: nginx serves static assets and proxies
.phpover FastCGI to that site's php-fpm. For static/redirect: Traefik or a tiny nginx answers directly. - 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-site Docker bridge; only Traefik bridges to proxy |
Site A reaching Site B's containers (even same customer) |
| Data at rest | ZFS dataset per site (nested under customer), bind-mounted web root | Cross-site data access; enables clean per-site 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
- Compromised WordPress/PHP app (most likely). Attacker gets code execution inside one site's php-fpm container.
- Malicious/abusive tenant.
- Credential theft (leaked DB or SFTP creds).
- 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). 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 for the rationale behind each major choice.