diff --git a/docs/adr/0008-place-services-by-data-trust.md b/docs/adr/0008-place-services-by-data-trust.md index 4ea3a24..423902e 100644 --- a/docs/adr/0008-place-services-by-data-trust.md +++ b/docs/adr/0008-place-services-by-data-trust.md @@ -72,6 +72,12 @@ TOTP is proportionate, and an SSO layer is operational weight that has not yet earned its place. This leaves the "no MFA on the internal services" gap in `security.md` open, knowingly. +> *The deferral above has an expiry as of +> [ADR-0022](0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md), +> which also finds that per-application TOTP is not available on two of the +> services this paragraph assumes it for. The reasoning above is unchanged and +> this ADR is not superseded.* + ## Consequences - **Winterfell becomes busier and less special, and this is the real cost.** diff --git a/docs/adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md b/docs/adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md new file mode 100644 index 0000000..815a0cc --- /dev/null +++ b/docs/adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md @@ -0,0 +1,142 @@ +# ADR-0022: Expire the SSO deferral when the tier holds real data + +**Status:** Accepted · 2026-09 + +## Context + +[ADR-0008](0008-place-services-by-data-trust.md) defers Authelia knowingly and +records why: two users, no external exposure, and *"per-application +authentication with TOTP is proportionate."* What it does not record is when the +deferral stops being the right answer. A deferral with no end condition and a +decision never to do the thing are the same document six months later, and by +then nobody remembers which one was meant. + +The gap today is genuinely small, and it is worth being precise about why rather +than taking it on faith. **Grafana is the only authenticated service the estate +runs.** It has one account; anonymous access and sign-up are off and the password +comes from SOPS (`stacks/observability/compose.yaml`); and the Hicks tab reaches +`10.0.99.20` on `3000` and nothing else on that host, above a logged *Block +access to Winterfell*. Nothing untrusted reaches it at all. + +That stops being the shape of the problem the moment ADR-0008's sensitive tier +exists. [#102](https://github.com/Gerrrt/HomeLab/issues/102) puts Vaultwarden, +Immich, Paperless-ngx and Home Assistant on Winterfell — by ADR-0008's own trust +argument, the four things in the estate most worth a second factor, which is +exactly why the argument for deferring gets weaker as the build gets closer +rather than staying where it was. + +**The premise that does not survive checking is the third one.** ADR-0008 offered +per-application TOTP *in place of* SSO. Read against the services that actually +authenticate a person, that substitute exists for half of them: + +| Service | Its own second factor | +| --- | --- | +| Vaultwarden | TOTP and WebAuthn, built in | +| Paperless-ngx | TOTP and WebAuthn, through `django-allauth` | +| Home Assistant | TOTP, built in | +| Immich | **None.** Upstream has declined it repeatedly and points at OAuth — which in this house means Authelia or Authentik | +| Grafana OSS | **None, in any edition.** "Grafana and the Grafana Cloud portal currently do not include built-in support for multi-factor authentication"; the documented route is an external identity provider | +| AdGuard Home | **None.** One admin account, password only | + +ntfy and Homepage are left out of that table because neither authenticates a +household identity — ntfy has basic auth on a topic, Homepage has no login at +all. + +Two things follow, and both cut against the deferral as written. **For Grafana +and Immich an identity provider is not a heavier alternative to per-application +TOTP — it is the only route to a second factor there is.** And Grafana is the one +of the six that is deployed *today*, so the substitute ADR-0008 named has never +existed in this estate. The line it points at in `security.md` is not a gap +awaiting a decision; for the only service it currently describes, it is a +standing property. + +AdGuard Home is the third, and its data is easy to under-rate. Under +[ADR-0010](0010-keep-the-resolver-on-the-gateway.md) Unbound forwards to it, so +its query log is the whole household's browsing history — without per-client +attribution, which is the one mitigation, and which ADR-0010 arrived at for +unrelated reasons. + +Finally, `SECURITY.md` records the monitoring host's unencrypted disk and swap as +an accepted residual, measured on `prometheus`. **The mini PC is not bought**, so +full-disk encryption there is a build-time choice rather than a retrofit — and a +password vault behind one factor on an unencrypted disk is a different bet from a +metrics dashboard behind one factor on the same disk. The two decisions belong in +the same sitting, which is the observation +[#103](https://github.com/Gerrrt/HomeLab/issues/103) was filed on. + +## Decision + +**The deferral stands, and it now ends on a stated condition.** ADR-0008 is +neither superseded nor amended — [ADR-0001](0001-record-architecture-decisions.md) +keeps it immutable, and its SSO paragraph gets a forward pointer to this document +and nothing else. Everything it decided about placement holds. + +**The expiry is a state, not a date.** Nothing about this risk is driven by the +calendar, so a date would be arbitrary, and an arbitrary date is a deadline +everyone learns to move. Three triggers, whichever comes first: + +1. **The sensitive tier holds real data** — the first real credential in + Vaultwarden, the first real photo in Immich, or the first real document in + Paperless-ngx. Seeded test entries do not count. Deciding *before* the data + arrives rather than after is the whole point. +2. **Any of it becomes reachable from outside the house**, by any means, + including a VPN terminating on 99. This is ADR-0008's *no external exposure* + premise made testable. +3. **A third person gets an account on any of it.** ADR-0008's *two users* + premise, likewise. That ADR already says the household growing should force a + revisit; this makes it a trigger rather than an aspiration. + +**Expiry means a decision gets recorded, not that Authelia gets deployed.** At +the first trigger, a new ADR either stands an identity provider up or re-accepts +the deferral with its reasons. Re-accepting is a legitimate outcome — it is what +happened here once already. What this document removes is the third option, which +is arriving at the same place by never looking. + +**Three things are due before #102's tier holds anything.** They are the floor +the deferral rests on, and none of them is automatic: + +- **TOTP enrolled at first login on Vaultwarden, Paperless-ngx and Home + Assistant.** This is precisely what ADR-0008 claims is in place of SSO, and all + three ship with it off. +- **Immich and AdGuard Home named in `security.md` as unable to carry a factor + at all**, rather than folded into a single line about MFA that reads as uniform + and is not. +- **The mini PC's disk encryption decided at build time**, on #102, rather than + inherited from `prometheus` by default. Recorded here; not decided here. + +**Grafana does not wait for the tier and its gap does not close on this +timetable.** No edition of Grafana OSS can carry a second factor, so its only +path is the same identity provider, and until one exists "no MFA" is true of it +permanently rather than pending. `docs/security.md` now says that instead of +implying otherwise. + +## Consequences + +- **#102 cannot be finished without meeting the floor or explicitly declining + it.** A deferral with a trigger is something a reader can check the estate + against; one without is a sentence. +- **The decision now lands before hardware is bought rather than after data is + loaded.** Authelia in front of Immich is a compose-file change on a box with + nothing on it. The same change under a live photo library and a working vault + is a migration, a re-enrolment and a household outage. +- **"No MFA on the internal services" turns out not to be one gap.** Three + services can close it themselves today and three cannot close it at all. The + single line in `security.md` flattened the distinction that decides the answer, + and the flattening is what made the deferral look cheaper than it is. +- **The cost of Authelia is unchanged and still real.** It puts one container in + the authentication path of everything, and its failure is a house-wide login + outage that looks like every service breaking at once — the same shape of + failure ADR-0010 declined for DNS, for the same reason. That is why this ADR + sets a trigger rather than mandating an identity provider now. +- **The unencrypted disk and single-factor auth compound, and the compounding is + new.** `SECURITY.md` accepts plaintext at rest on `prometheus` on the strength + of a threat model that excludes physical access to the rack. That exclusion was + written when the most valuable thing on the disk was 30 days of metrics. The + mini PC is the last moment the disk half of that bet is cheap to change. +- **Some of the estate will re-accept the deferral, and should.** ntfy, Homepage + and the whole streaming tier hold nothing whose exposure costs anything, and an + identity provider in front of a page of links is exactly the operational weight + ADR-0008 was right about. +- Nothing here changes a rule, a container or a byte of configuration. It is a + condition written down, which is the smallest possible artefact and the one the + deferral was missing. diff --git a/docs/roadmap.md b/docs/roadmap.md index 4ace329..faf5e8a 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -69,6 +69,28 @@ issues intact. Nothing was summarised away. agent pushes to them and has no other path, which is why #70 could close Alertmanager and not these. Firewall default-deny is the whole control. Accepted residual, recorded in `SECURITY.md`. +- **[#103](https://github.com/Gerrrt/HomeLab/issues/103) Give ADR-0008's SSO + deferral an expiry.** Answered by + [ADR-0022](adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md): + the deferral ends on a state rather than a date — the first real secret, photo + or document in the sensitive tier, any reachability from outside the house, or + a third account holder, whichever comes first. At the first of those a + decision gets recorded. Re-accepting is allowed; arriving at the same place by + never looking is what the expiry removes. + + **Writing it turned up that ADR-0008's substitute for SSO does not exist for + half the tier.** Per-application TOTP is available on Vaultwarden, + Paperless-ngx and Home Assistant, and on none of Grafana, Immich or AdGuard + Home — Grafana OSS has no MFA in any edition, Immich's upstream has declined + it and points at OAuth, and AdGuard has one password-only admin. Grafana is + the only one of the six deployed, so the thing ADR-0008 offered *in place of* + SSO has never been available here, and for those three an identity provider is + the only route to a second factor rather than a heavier alternative to one. + What is outstanding belongs to #102: TOTP enrolled at first login on the three + that can carry it, and the mini PC's disk encryption decided at build time + rather than inherited from `prometheus` — a vault behind one factor on an + unencrypted disk is not the bet `SECURITY.md` accepted for a metrics + dashboard. - **[#235](https://github.com/Gerrrt/HomeLab/issues/235) Decide whether the iLO stays on the lab segment.** ADR-0014 puts `ifrit`'s attack VM on ImaginationLAN, so `shiva` — the BMC of the box being defended, on firmware @@ -259,7 +281,11 @@ what left this one unfireable for months. cannot host it — ADR-0015 measured 2549 MiB available behind a 5400 rpm disk and a 100 Mb/s NIC — and ADR-0007 keeps household services off the lab hypervisor. The SSO this box deliberately does not get is - [#103](https://github.com/Gerrrt/HomeLab/issues/103). + [#103](https://github.com/Gerrrt/HomeLab/issues/103), and as of + [ADR-0022](adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md) + it does not get it *until this box holds real data* rather than indefinitely — + which puts two things on this build: TOTP enrolled on the three services that + can carry it, and a disk encryption decision made here rather than inherited. - **[#95](https://github.com/Gerrrt/HomeLab/issues/95) Plan and build the NAS on VLAN 40.** Planned; [ADR-0016](adr/0016-open-casabonita-inward-and-keep-it-terminal-outward.md) @@ -400,7 +426,10 @@ months. **Infrastructure** above, because it has a shape now rather than only a decision. - **[#103](https://github.com/Gerrrt/HomeLab/issues/103)** The SSO deferral - ADR-0008 takes knowingly — give it an expiry. + ADR-0008 takes knowingly, given an expiry by + [ADR-0022](adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md). + Under **Security** above, because it has a condition now rather than only a + decision. - **[#105](https://github.com/Gerrrt/HomeLab/issues/105)** Confirm the unconfigured Snort package actually went. diff --git a/docs/security.md b/docs/security.md index 4800e0d..49eda09 100644 --- a/docs/security.md +++ b/docs/security.md @@ -28,6 +28,22 @@ domain or by port says why not for the lab, and the reason generalises), and no MFA on the internal services. +**The MFA gap has an expiry now, and it is not one gap.** +[ADR-0022](adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md) +ends [ADR-0008](adr/0008-place-services-by-data-trust.md)'s SSO deferral on a +state rather than leaving it open: the first real secret, photo or document in +the sensitive tier, any reachability from outside the house, or a third account +holder — whichever comes first. Until then the floor is per-application TOTP, +and it does not reach everything. Vaultwarden, Paperless-ngx and Home Assistant +can each carry a second factor; **Grafana, Immich and AdGuard Home cannot** — +Grafana OSS has no MFA in any edition, Immich's upstream has declined it and +points at OAuth, and AdGuard has one password-only admin account. For those +three an identity provider is the only route to a second factor rather than a +heavier alternative to one. **Grafana is the only one of the six deployed +today**, which makes "no MFA" a standing property of the estate rather than a +pending piece of work — it closes when an identity provider exists and not +before. + **Intrusion detection has been running** on **Skids (VLAN 20)** since 2026-08-21 and on **Degens (VLAN 10)** since 2026-09-02, one Suricata process per interface.