the fleet gets a realm: kerberos + ldap on verdandi, and web + mail SSO onto it #108

Merged
lament merged 19 commits from kerberization into main 2026-07-24 03:22:08 -05:00
Owner
  • verdandi comes up - friend-hosted Proxmox VM (::53, same box as minerva/brigid), the identity root the realm hangs off

    • uefi-luks disko layout: unencrypted ESP -> LUKS -> btrfs @nix/@persist/@log, tmpfs root; TPM2 auto-unlock sealed to PCR7 (survives kernel/nixos-rebuild updates), format-time passphrase kept as the permanent recovery keyslot
    • lanzaboote SB enrolled + enabled before sealing the TPM (PCR7 measures secure-boot state); proven to reboot unattended off the vTPM
    • joins the WG mesh + directory (ip.internal/site/keys), carries the new flat identity role, site-hubs over the fate LAN like the other Fate boxes; claims borg sub4 (hestia's parked slot)
  • kerberos realm - MIT KDC on verdandi, realm LAMENT.GAY

    • services/identity/kdc.nix is server-only (identity role); the client config rides core via system/net/krb5.nix (default_realm, kdc = the identity host, rdns/dns_canonicalize_hostname off - WG has no PTR by default)
    • GSSAPI ticket-SSH fleet-wide: programs.ssh.package = pkgs.openssh_gssapi (the Debian gssapi-with-mic patch - opensshWithKerberos is the wrong one, it rejects GSSAPIAuthentication)
      • GSSAPITrustDns is reverse DNS: ssh builds host/<name> from the peer's PTR, so kresd had to stop empty-zoning the ULA reverse for our /48 (RFC 6303/8375 served it as authoritative NXDOMAIN) and emit each host's own PTR last so it wins the reverse collision against the service vhosts (athena's ::1 was reversing to vault)
    • durable keytabs, all 5 hosts: each host/<host>.lament.gay key is a binary sops secret at /etc/krb5.keytab (new mkSopsBin -> sops/keytab/<host>.bin), minted by just keytab <host>; gssapi-with-mic works fleet-wide with pubkey + password disabled
    • hardening: AS-REP roasting closed (default_principal_flags = +preauth); the realm DB backed up to borg for DR (kdb5_util dump + master stash + slapcat, guarded by repokey-blake2 - the live Berkeley/mdb is never copied directly)
  • ldap directory - OpenLDAP on verdandi, the realm migrated onto it

    • services/identity/openldap.nix: mdb at dc=lament,dc=gay (ou=people/ou=groups), listens ldapi:/// + the WG-internal address only - no ldaps, WG is the encrypted transport
    • the KDC's principal DB moved into openldap (kldap backend), SASL/EXTERNAL peercred bind, no stashed bind password
      • the withLdap krb5 build omits cyrus_sasl -> a stub SASL that dies on EXTERNAL; added it back via overrideAttrs (a nixpkgs gap worth upstreaming)
    • model-B SASL pass-through - no password ever rests in LDAP: a {SASL}princ userPassword hands every simple bind to saslauthd -a kerberos5 -> the KDC, so an app's plain LDAP bind is a kerberos auth; the stored value is just the principal name
    • cn=svc-ldap read-only bind account for directory search; memberof + refint overlays so apps map roles with (memberOf=cn=admins,...)
  • the web tier goes ldap - grafana, forgejo, wiki-js all SSO through the svc-ldap search-bind -> {SASL} -> KDC

    • group membership drives admin: cn=admins (groupOfNames, via memberof) for forgejo/wiki-js, cn=lament (posixGroup) for grafana's group-search - posixGroup and groupOfNames are both structural so they can't share one entry
    • each app re-asserts its auth source idempotently on boot: grafana declarative (ldap.toml + $__file), forgejo admin auth {add,update}-ldap CLI oneshot, wiki-js GraphQL updateStrategies oneshot; vaultwarden stays out (bootstrap deadlock)
  • mail goes on the directory too

    • mailserver binds to the realm via nixos-mailserver's native LDAP in auth-bind mode (attributes.password = null) - dovecot binds each login as the user against verdandi's slapd, so the edge holds NO kerberos, just a plain LDAP client
    • the local sarah@ account gives way to the directory: login is now the uid lament; sarah@/lament@/sarahlament@ ride a multi-valued mail attr (the alias mechanism - the native module has no alias option); homes key off uid so they survive a directory-DB rebuild; admin@ stays the local break-glass fallback
    • athena added to the identity.yaml sops group for the bind password; the existing mailbox (99 msgs) migrated onto the uid-keyed home, verified send + receive against an external provider
  • hosts derive from the directory - the per-host nixosConfigurations/<host>.nix files collapse into one hosts.nix generator that builds every host from its directory entry + static/<host>/

  • also - niri screencast routing restored through the portal, discord un-broken

  • left for later:

    • graduate the web + mail tier off LDAP-bind onto an OIDC IdP (Kanidm/Keycloak) for real browser SSO; SPNEGO/Negotiate is the endgame but no caddy module exists yet
    • operational cleanup once fully happy: drop verdandi's /var/lib/krb5kdc.pre-ldap rollback snapshot, rm the migrated /var/vmail/lament.gay/sarah.old, and point remaining mail clients' username at lament
- **`verdandi` comes up** - friend-hosted Proxmox VM (`::53`, same box as `minerva`/`brigid`), the identity root the realm hangs off - `uefi-luks` disko layout: unencrypted ESP -> LUKS -> btrfs `@nix`/`@persist`/`@log`, tmpfs root; TPM2 auto-unlock sealed to PCR7 (survives kernel/`nixos-rebuild` updates), format-time passphrase kept as the permanent recovery keyslot - lanzaboote SB enrolled + **enabled before sealing the TPM** (PCR7 measures secure-boot state); proven to reboot unattended off the vTPM - joins the WG mesh + `directory` (`ip.internal`/`site`/keys), carries the new flat `identity` role, site-hubs over the `fate` LAN like the other Fate boxes; claims borg `sub4` (hestia's parked slot) - **`kerberos` realm** - MIT KDC on `verdandi`, realm `LAMENT.GAY` - `services/identity/kdc.nix` is server-only (`identity` role); the client config rides `core` via `system/net/krb5.nix` (`default_realm`, kdc = the `identity` host, `rdns`/`dns_canonicalize_hostname` off - WG has no PTR by default) - GSSAPI ticket-SSH fleet-wide: `programs.ssh.package = pkgs.openssh_gssapi` (the Debian gssapi-with-mic patch - `opensshWithKerberos` is the wrong one, it rejects `GSSAPIAuthentication`) - `GSSAPITrustDns` is *reverse* DNS: ssh builds `host/<name>` from the peer's PTR, so `kresd` had to stop empty-zoning the ULA reverse for our `/48` (RFC 6303/8375 served it as authoritative NXDOMAIN) and emit each host's own PTR **last** so it wins the reverse collision against the service vhosts (`athena`'s `::1` was reversing to `vault`) - durable keytabs, all 5 hosts: each `host/<host>.lament.gay` key is a **binary** sops secret at `/etc/krb5.keytab` (new `mkSopsBin` -> `sops/keytab/<host>.bin`), minted by `just keytab <host>`; `gssapi-with-mic` works fleet-wide with pubkey + password disabled - hardening: AS-REP roasting closed (`default_principal_flags = +preauth`); the realm DB backed up to borg for DR (`kdb5_util dump` + master stash + `slapcat`, guarded by repokey-blake2 - the live Berkeley/mdb is never copied directly) - **`ldap` directory** - OpenLDAP on `verdandi`, the realm migrated onto it - `services/identity/openldap.nix`: mdb at `dc=lament,dc=gay` (`ou=people`/`ou=groups`), listens `ldapi:///` + the WG-internal address only - **no ldaps**, WG is the encrypted transport - the KDC's principal DB moved *into* openldap (`kldap` backend), SASL/EXTERNAL peercred bind, no stashed bind password - the `withLdap` krb5 build omits `cyrus_sasl` -> a stub SASL that dies on EXTERNAL; added it back via `overrideAttrs` (a nixpkgs gap worth upstreaming) - **model-B SASL pass-through - no password ever rests in LDAP**: a `{SASL}princ` `userPassword` hands every simple bind to `saslauthd -a kerberos5` -> the KDC, so an app's plain LDAP bind *is* a kerberos auth; the stored value is just the principal name - `cn=svc-ldap` read-only bind account for directory search; `memberof` + `refint` overlays so apps map roles with `(memberOf=cn=admins,...)` - **the web tier goes `ldap`** - `grafana`, `forgejo`, `wiki-js` all SSO through the `svc-ldap` search-bind -> `{SASL}` -> KDC - group membership drives admin: `cn=admins` (groupOfNames, via `memberof`) for forgejo/wiki-js, `cn=lament` (posixGroup) for grafana's group-search - posixGroup and groupOfNames are both structural so they can't share one entry - each app re-asserts its auth source idempotently on boot: grafana declarative (`ldap.toml` + `$__file`), forgejo `admin auth {add,update}-ldap` CLI oneshot, wiki-js GraphQL `updateStrategies` oneshot; vaultwarden stays out (bootstrap deadlock) - **mail goes on the directory too** - `mailserver` binds to the realm via nixos-mailserver's **native LDAP in auth-bind mode** (`attributes.password = null`) - `dovecot` binds each login as the user against `verdandi`'s slapd, so the edge holds NO kerberos, just a plain LDAP client - the local `sarah@` account gives way to the directory: login is now the uid `lament`; `sarah@`/`lament@`/`sarahlament@` ride a multi-valued `mail` attr (the alias mechanism - the native module has no alias option); homes key off `uid` so they survive a directory-DB rebuild; `admin@` stays the local break-glass fallback - `athena` added to the `identity.yaml` sops group for the bind password; the existing mailbox (99 msgs) migrated onto the uid-keyed home, verified send + receive against an external provider - **hosts derive from the directory** - the per-host `nixosConfigurations/<host>.nix` files collapse into one `hosts.nix` generator that builds every host from its `directory` entry + `static/<host>/` - **also** - niri screencast routing restored through the portal, discord un-broken - left for later: - graduate the web + mail tier off LDAP-bind onto an **OIDC** IdP (Kanidm/Keycloak) for real browser SSO; SPNEGO/`Negotiate` is the endgame but no caddy module exists yet - operational cleanup once fully happy: drop `verdandi`'s `/var/lib/krb5kdc.pre-ldap` rollback snapshot, `rm` the migrated `/var/vmail/lament.gay/sarah.old`, and point remaining mail clients' username at `lament`
sshd becomes kerberized
Some checks failed
CI / check (pull_request) Successful in 1m19s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 3m11s
CI / dry-activate (pull_request) Failing after 2m38s
984f3c9bc0
kresd needs to serve reverse pointers
All checks were successful
CI / check (pull_request) Successful in 1m11s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m8s
CI / dry-activate (pull_request) Successful in 48s
19b4f045a1
kerberos keytabs go durable and fleet-wide
Some checks failed
CI / check (pull_request) Successful in 1m0s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Failing after 1m57s
CI / dry-activate (pull_request) Has been skipped
5b13dfd9be
lament force-pushed kerberization from 5b13dfd9be
Some checks failed
CI / check (pull_request) Successful in 1m0s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Failing after 1m57s
CI / dry-activate (pull_request) Has been skipped
to 43246fc404
Some checks failed
CI / check (pull_request) Successful in 53s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m58s
CI / dry-activate (pull_request) Failing after 16s
2026-07-23 15:33:32 -05:00
Compare
kerberos gets backed up
Some checks failed
CI / check (pull_request) Successful in 1m12s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m46s
CI / dry-activate (pull_request) Failing after 32s
0ca53f11c9
kldap wanted in on the fun
Some checks failed
CI / check (pull_request) Successful in 1m22s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m48s
CI / dry-activate (pull_request) Failing after 44s
ab3750abf0
lament force-pushed kerberization from ab3750abf0
Some checks failed
CI / check (pull_request) Successful in 1m22s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m48s
CI / dry-activate (pull_request) Failing after 44s
to 570d804a91
Some checks failed
CI / check (pull_request) Successful in 1m0s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m29s
CI / dry-activate (pull_request) Failing after 18s
2026-07-23 21:38:31 -05:00
Compare
the other web services go ldap
Some checks failed
CI / check (pull_request) Successful in 56s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 2m10s
CI / dry-activate (pull_request) Failing after 14s
3c51d9378f
the local sarah@ gives way to the directory
Some checks failed
CI / check (pull_request) Successful in 56s
CI / build (pull_request) Successful in 2m3s
CI / deploy (pull_request) Has been skipped
CI / dry-activate (pull_request) Failing after 14s
1e58d3d2b3
lament changed title from WIP: kerberization to the fleet gets a realm: kerberos + ldap on verdandi, and web + mail SSO onto it 2026-07-24 03:01:00 -05:00
builder needed a new ssh
All checks were successful
CI / check (pull_request) Successful in 57s
CI / build (pull_request) Successful in 1m41s
CI / deploy (pull_request) Has been skipped
CI / dry-activate (pull_request) Successful in 22s
436575e8a4
lament force-pushed kerberization from 436575e8a4
All checks were successful
CI / check (pull_request) Successful in 57s
CI / build (pull_request) Successful in 1m41s
CI / deploy (pull_request) Has been skipped
CI / dry-activate (pull_request) Successful in 22s
to 01bcdf804a
All checks were successful
CI / check (pull_request) Successful in 58s
CI / deploy (pull_request) Has been skipped
CI / build (pull_request) Successful in 1m58s
CI / dry-activate (pull_request) Successful in 23s
CI / check (push) Has been skipped
CI / build (push) Has been skipped
CI / dry-activate (push) Has been skipped
CI / deploy (push) Successful in 2m37s
2026-07-24 03:11:10 -05:00
Compare
lament merged commit 01bcdf804a into main 2026-07-24 03:22:08 -05:00
lament deleted branch kerberization 2026-07-24 03:22:08 -05:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
lament/pantheon!108
No description provided.