155 lines
8.9 KiB
Markdown
155 lines
8.9 KiB
Markdown
---
|
|
status: "accepted"
|
|
date: 2026-10-11
|
|
decision-makers: ["Moritz Graf", "Antigravity"]
|
|
consulted: []
|
|
informed: []
|
|
---
|
|
|
|
# ADR 0005: Personal Data Isolation, Authentik Authentication Boundaries, and Hosting Segregation
|
|
|
|
## Context and Problem Statement
|
|
|
|
On 2026-10-11, during the development of mobile-first PWA dashboards for baby monitoring (Mila Graf) and personal nutrition/Garmin energy balance (Moritz Graf), personal health, sleep, diaper, and nutrition data was temporarily made available at unauthenticated root URLs:
|
|
- `https://hosting.haumdaucher.de/mila.html`
|
|
- `https://hosting.haumdaucher.de/moritz.html`
|
|
|
|
While [ADR 0004](0004-structured-personal-data-datastore.md) established the conceptual routing design (`/family/*` protected by Traefik ForwardAuth, public recipes at `/recipes/*`), a gap analysis revealed critical systemic vulnerabilities:
|
|
1. **Unapplied Kubernetes Ingress:** The ForwardAuth Ingress (`minio-hosting-family`) declared in `k8s/minio/hosting-ingress.yaml` was never applied to the Kubernetes cluster.
|
|
2. **Missing Authentik Identity Provider:** Authentik had no Proxy Provider or Application configured for `hosting.haumdaucher.de`, returning HTTP 404 on ForwardAuth checks.
|
|
3. **Dual-Publishing Anti-Pattern:** Dashboard generator scripts (`update_website.py`, `update_moritz_website.py`) mirrored files to root (`/mila.html`, `/moritz.html`) for convenience, completely bypassing the planned `/family/` path boundary.
|
|
4. **Lack of Automated Ingress & Storage Auditing:** No automated test or CI/Cron audit prevented personal data from being uploaded to public prefixes.
|
|
|
|
How do we establish a permanent, verifiable, and automated security boundary that guarantees personal and family data can never be exposed publicly without authentication?
|
|
|
|
---
|
|
|
|
## Decision Drivers
|
|
|
|
* **Zero Public Exposure for Personal Data:** Health, sleep, nutrition, body, and family data must NEVER be accessible without authentication.
|
|
* **Defense-in-Depth:** Security must not rely on a single layer (e.g. secret URLs or developer memory). Protection must be enforced at identity provider, reverse proxy, storage bucket, and generator script levels.
|
|
* **Declarative Infrastructure as Code (ADR 0003):** All Authentik providers, policies, groups, and Kubernetes Ingresses must be versioned in Git and applied via Terraform/Kubectl.
|
|
* **Automated Guardrails & Fast Feedback:** Continuous audits must detect and quarantine any unauthorized files outside protected paths.
|
|
* **Seamless PWA Experience:** The PWA capabilities (manifest, service worker, offline shell) must function within the authenticated cookie session without breaking ForwardAuth.
|
|
|
|
---
|
|
|
|
## Decision Outcome
|
|
|
|
Chosen Strategy: **Multi-Layer Boundary Enforcement with ForwardAuth, Strict Storage Segregation, and Continuous Auditing**.
|
|
|
|
### 1. Storage & URL Path Segmentation Architecture
|
|
|
|
```
|
|
Internet Request to https://hosting.haumdaucher.de
|
|
│
|
|
▼
|
|
┌───────────────────┐
|
|
│ Traefik Ingress │
|
|
└─────────┬─────────┘
|
|
│
|
|
Path Inspection & Priority
|
|
│
|
|
┌────────────────┴────────────────┐
|
|
│ │
|
|
Path: /family/* Path: /* (Fallback)
|
|
Priority: 100 Priority: 10
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────────┐ ┌─────────────────────────┐
|
|
│ Authentik ForwardAuth │ │ Public Ingress │
|
|
│ • Host: hosting.haumd...│ │ • ONLY /recipes/* │
|
|
│ • Allowed: 'family' & │ │ • ONLY /showcase/* │
|
|
│ 'infra-admins' groups │ │ • NO personal files │
|
|
└────────────┬────────────┘ └────────────┬────────────┘
|
|
│ │
|
|
HTTP 200 (Authed) │
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────┐ ┌──────────────────┐
|
|
│ MinIO Bucket │ │ MinIO Bucket │
|
|
│ key: family/* │ │ key: public/* │
|
|
│ (Protected Data) │ │ (Public Static) │
|
|
└──────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
#### Rule 1: Canonical Protected Prefix `/family/`
|
|
- All dashboards containing personal, familial, or physiological metrics MUST reside under `/family/`:
|
|
- `https://hosting.haumdaucher.de/family/mila.html` (Baby sleep, feeds, diapers)
|
|
- `https://hosting.haumdaucher.de/family/moritz.html` (Personal nutrition, Garmin deficits, health)
|
|
- `https://hosting.haumdaucher.de/family/nutrition.html` (Family nutrition overview)
|
|
- `https://hosting.haumdaucher.de/family/sleep.html` (Sleep analytics)
|
|
- Associated PWA assets (manifests, service workers) MUST also be scoped within `/family/` (e.g. `/family/mila.webmanifest`, `/family/sw.js`).
|
|
|
|
#### Rule 2: Root Prefix `/` Strictly Restricted
|
|
- The root of `hosting.haumdaucher.de` is restricted to public static assets (`/recipes/*`, `/showcase/*`).
|
|
- Publishing scripts MUST NEVER write personal dashboards to the bucket root.
|
|
|
|
---
|
|
|
|
### 2. Declarative Authentik ForwardAuth Configuration
|
|
|
|
Authentik configuration is codified in `terraform/authentik.tf`:
|
|
1. **Proxy Provider (`authentik_provider_proxy.hosting_family`):**
|
|
- External Host: `https://hosting.haumdaucher.de`
|
|
- Mode: `forward_single`
|
|
- Bound to `authentik_outpost.embedded`
|
|
- Session duration: 30 days (`access_token_validity = "days=30"`)
|
|
2. **Application (`authentik_application.hosting_family`):**
|
|
- Slug: `hosting-family`
|
|
- Protocol Provider: `authentik_provider_proxy.hosting_family.id`
|
|
3. **Role-Based Policy Bindings:**
|
|
- Group `family` (Moritz & Amelie Graf): Granted access
|
|
- Group `infra-admins`: Granted access
|
|
- Unauthenticated requests are redirected (HTTP 302) to `https://auth.haumdaucher.de/application/o/authorize/...`
|
|
|
|
---
|
|
|
|
### 3. Kubernetes Ingress Deployment
|
|
|
|
`k8s/minio/hosting-ingress.yaml` defines the two ingress tiers:
|
|
- **`minio-hosting-family` (Priority 100):**
|
|
- Path: `/family` (Prefix)
|
|
- Middlewares: `auth-authentik-forwardauth@kubernetescrd,minio-hosting-add-prefix@kubernetescrd`
|
|
- **`minio-hosting` (Priority 10):**
|
|
- Path: `/` (Prefix)
|
|
- Middlewares: `minio-hosting-add-prefix@kubernetescrd`
|
|
|
|
---
|
|
|
|
### 4. Generator Scripts & Codebase Hardening
|
|
|
|
1. **Elimination of Root Duplication:**
|
|
- `skills/datastore/scripts/update_website.py`: Removed all S3 upload and local sync calls to root `mila.html`. Target is strictly `family/mila.html`.
|
|
- `skills/datastore/scripts/update_moritz_website.py`: Removed S3 upload and local sync calls to root `moritz.html`. Target is strictly `family/moritz.html` (and `family/nutrition.html`).
|
|
2. **PWA Scope Alignment:**
|
|
- Web App Manifests configure `start_url: "/family/mila.html"` and `scope: "/family/"`.
|
|
- Service workers are registered at `/family/sw.js`.
|
|
3. **Telegram Link Consistency:**
|
|
- Agent prompts for `mila` (Topic 20) and `koch` (Topic 21) only broadcast the secure URL: `https://hosting.haumdaucher.de/family/...`.
|
|
|
|
---
|
|
|
|
### 5. Automated Continuous Leak Audit Guard
|
|
|
|
An automated audit check runs continuously (via OpenClaw periodic audit and datastore maintenance):
|
|
- Inspects the MinIO `hosting` bucket root (`prefix=""`).
|
|
- Detects any forbidden files matching patterns: `*mila*`, `*moritz*`, `*nutrition*`, `*sleep*`, `*schlaf*`, `*health*`, `*diaper*`.
|
|
- If detected:
|
|
1. Immediately deletes/quarantines the offending object from the public bucket root.
|
|
2. Raises a critical notification/event to the system log.
|
|
|
|
---
|
|
|
|
## Consequences
|
|
|
|
### Positive
|
|
- **Guaranteed Confidentiality:** Family and personal health data is physically impossible to access without passing Authentik ForwardAuth.
|
|
- **Reproducible Infrastructure:** The entire authentication chain is codified in Terraform and Kubernetes manifests.
|
|
- **Persistent Mobile PWA:** Authentik's 30-day session lifetime ensures seamless standalone mobile app operation without repetitive login prompts.
|
|
- **Automated Safety Net:** Even if a developer or agent script mistakenly attempts to mirror a file to root, the automated audit deletes it.
|
|
|
|
### Negative & Mitigations
|
|
- **Authentication Step on First Mobile Launch:**
|
|
- *Mitigation*: When installing the PWA on iOS/Android, the user authenticates once via Authentik. The persistent session cookie maintains login across app launches.
|