Real desktop SSO: replace Authelia with Keycloak + a Kerberos KDC #266
Labels
No labels
bug
enhancement
in-progress
needs-info
needs-triage
p0
ready-for-agent
ready-for-human
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/nixos#266
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Goal
Real single sign-on for
nb: log into the notebook with the LDAP account, and be logged into the web services as a consequence — no second password prompt at the IdP.This requires the desktop login to leave behind a credential the browser can present silently. The only mature open-source mechanism for that is Kerberos + SPNEGO.
Why this means replacing Authelia
gokrb5dependency ("not maintained… uses a fork of go's X repositories which have known CVEs"). Not landing on any plannable horizon.So SPNEGO means swapping the IdP.
Proposed shape
mail— standalone Heimdal/MIT viaservices.kerberos_server(in nixpkgs atservices/system/kerberos/default.nix).userPassword: {SASL}user@REALM, slapd delegating binds to the KDC via saslauthd. nixpkgs' openldap is already built with--enable-spasswdand linkscyrus_sasl, so this works without a package override.web-armreplacing Authelia as the OIDC provider — SPNEGO via LDAP user federation + keytab.*.cloonar.comforward-auth catch-all, which Keycloak has no equivalent for.nb(services.sssd,id_provider = ldap+auth_provider = krb5,kcm = true) so SDDM login yields a TGT, with offline credential caching for a laptop.network.negotiate-auth.trusted-uris = .cloonar.com, ChromeAuthServerAllowlist.Bonus from the same Kerberos infrastructure: GSSAPI SSO to Dovecot (IMAP/SMTP in Thunderbird) and across the fleet over SSH.
Why Keycloak over authentik
authentik is the better structural fit — its proxy provider maps directly onto the existing nginx
auth_requestsetup, no oauth2-proxy needed. It was the initial lean. Resource and packaging constraints reverse it:services.keycloakin nixpkgsweb-armhas no substituters or cachix configured andnixos-rebuilds itself via bento every 5 min, so an authentik version bump means a full source build on a small ARM VM every time. That's the decider.Why not FreeIPA
Considered and rejected:
mail(seeCONTEXT.md).Cost to weigh before committing
Either IdP is roughly a 10× RAM jump from Authelia — ~100 MB to ~1 GB.
web-armalready carries Nextcloud, Immich, Collabora, Grafana, Prometheus, Loki, VictoriaMetrics, PowerSync, MySQL and PostgreSQL on an arm64 VM. Headroom needs checking before this is scheduled.Migration surface: 8 OIDC clients re-registered (Gitea, Nextcloud, Grafana, FreeScout, Synapse/MAS, claude-mcp-forgejo, Immich), plus the
access_controlrules and the threeauthorization_policiesinhosts/web-arm/modules/authelia.nix.Open questions for triage
web-arm— unverified; needsfree -m.posixAccountwithuidNumber/gidNumber?nis.ldifis in the schema set and there's aloginShellACL, but this isn't confirmed. It's a prerequisite for any LDAP desktop login.nb:dominikis currently a local user with a local uid. Switching to an LDAP-supplied uid needs achownsweep over/home, which lives under impermanence at/nix/persist.nb(tpm-fido2-thinkpad-linux, confirmed working with AMD TPM 2.0 + fprintd) gives one-fingerprint-touch web login for ~zero added RAM and no migration. It is not true SSO — desktop and web login stay separate events — but it does not foreclose this issue later.Scope
Design/spike first, not a direct implementation. Suggested sequencing if it proceeds: confirm POSIX attributes and RAM headroom → KDC +
{SASL}pass-through onmail(verifiable standalone withkinit) → SSSD onnb→ Keycloak + oauth2-proxy onweb-arm→ cut over clients → retire Authelia.An ADR is warranted for the IdP swap.
Design session resolved the major branches. First implementation slice filed as #267 (M1: standalone KDC on
mail+ the IdP-swap ADR), labelledready-for-agent.Two findings from that session change the plan as written in this issue:
The
{SASL}password bridge is rejected.hosts/mail/modules/dovecot.nix:15,23,28usesauth_bind = no+default_pass_scheme = CRYPT— Dovecot reads the rawuserPasswordand verifies it itself rather than delegating a bind to slapd. RewritinguserPasswordto{SASL}user@REALMwould break all IMAP/SMTP auth fordc=cloonar,dc=comimmediately. Kerberos is therefore additive: principals only for the humans who log intonb; LDAP stays the authoritative password store. Both GSSAPI bonuses (Dovecot/Thunderbird, fleet SSH) are additive too, so nothing is forfeited.Both open gates passed, and one of them voids an argument in this issue.
posixAccountis populated (uidNumber 5001).web-armhas 10 GB available of 15.5 GB, with Authelia using 32 MB — so the RAM column in the Keycloak-vs-authentik table does not apply. Keycloak still wins, but on packaging alone (flake-only, source-builds Node+Go+Python per bump, no substituters, bento rebuilds every 5 min). Recorded honestly in the ADR, including what that choice costs: authentik's proxy provider would have removed the entire oauth2-proxy workstream at M5.Also decided: realm
CLOONAR.COM; KDC on port 88 only, Hetzner-firewalled tofw,nbvia the existing WireGuard tunnel; Keycloak atsso.cloonar.com;nbgets full SSSD LDAP identity, reached by coexistence first (localdominikuntouched, LDAPdominik.polakovicsalongside) with the rename/uid migration deferred until SSO is proven.Sequence is M1 KDC → M2 SSSD on
nb→ M3 Keycloak beside Authelia (decision gate) → M4 clients → M5 oauth2-proxy → M6 retire Authelia. M3 proves SPNEGO end-to-end with production still on Authelia.Secrets for M1–M3 are committed (
cbb91be);keycloak-keytab-b64is deliberately deferred as an M1 artifact.Still open, needed before M4 but not blocking M1–M3:
services.keycloakhas no declarative realm/client management, so the auth layer's config would move out of git into a database unlesskeycloak-config-clior realm-JSON import is used.