Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/adr/0008-place-services-by-data-trust.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**
Expand Down
142 changes: 142 additions & 0 deletions docs/adr/0022-expire-the-sso-deferral-when-the-tier-holds-real-data.md
Original file line number Diff line number Diff line change
@@ -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.
33 changes: 31 additions & 2 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand Down
16 changes: 16 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down