feat(nb): SSSD LDAP identity + Kerberos login alongside local dominik #270

Merged
dominik.polakovics merged 2 commits from afk/269 into main 2026-08-07 13:33:52 +02:00

M2 of #266 / ADR-0022. Adds hosts/nb/modules/ldap-login.nix and imports it from hosts/nb/configuration.nix.

id_provider = ldap against ldap.cloonar.com, auth_provider = krb5 against the KDC on mail, services.sssd.kcm = true, cache_credentials = true, plus security.krb5 client config, pam_mkhomedir for the first graphical login, and system-level SPNEGO policy for Firefox and Chromium.

Diff is 2 files: the new module, and hosts/nb/configuration.nix (+1 import, +2 persistence entries). Nothing outside hosts/nb/.

⚠️ Not dry-built — please run the gate before this lands

The agent sandbox has no Nix at all (nix-instantiate: command not found), so scripts/test-configuration nb could not run and the commit used --no-verify. nb deploys unattended every 5 minutes, so please dry-build before merging:

./scripts/test-configuration -v nb

To compensate, every option name was verified verbatim against release-26.05 source rather than from memory: nixos/modules/services/misc/sssd.nix, security/pam.nix, security/krb5/{default,krb5-conf-format}.nix, services/display-managers/sddm.nix, programs/firefox.nix, and SSSD 2.13's own src/config/cfg_rules.ini. That verification caught three defects in the issue's spec (below).

Coexistence / lockout safety

users.users.dominik is untouched — uid, /home/dominik, its hashedPassword, hosts/nb/users/dominik.nix, and all four sops.secrets.*.owner = \"dominik\" declarations. No users.users/users.groups entry is added for the LDAP account; it resolves through NSS. No wheel/sudo for it. utils/modules/ldap-auth.nix is not edited, so web-arm is out of the blast radius.

pam_sss is required nowhere. From nixos/modules/security/pam.nix:

  • authpam_unix.so (sufficient, try_first_pass likeauth) is listed before pam_sss.so (sufficient); autoOrderRules assigns order by list position, so pam_unix runs first and short-circuits for a local user.
  • accountpam_sss.so sufficient first, pam_unix.so required last (upstream's own comment explains why). sufficient discards failures, so all three of sssd-healthy (USER_UNKNOWN), sssd-dead (AUTHINFO_UNAVAIL) and network-down are ignored and pam_unix succeeds from /etc/passwd.
  • sssdStrictAccess is left unset, so the strict [default=bad …] control is not used.
  • /etc/nsswitch.conf is files sss … (mkBefore [ \"files\" ]), so a hung sssd cannot block local getpwnam.

Three deliberate divergences from the issue

Each has an in-file comment explaining why, so the reasoning is not lost.

  1. krb5_store_password_if_offline, not krb5_store_password_if_no_ccache. The spelling in the issue is not an SSSD option — it is absent from sssd 2.13's cfg_rules.ini, and sss_ini_call_validators()'s return is discarded in monitor.c, so the daemon would have started, logged Attribute ... is not allowed, and silently ignored the line. Worth correcting in the issue so the next agent doesn't reintroduce it.
  2. security.pam.services.login.makeHomeDir, not .sddm. sddm.nix sets useDefaultRules = false and supplies its own rules, while makeHomeDir only injects pam_mkhomedir.so into the default session rule list (rules = lib.optionalAttrs cfg.useDefaultRules) — so sddm.makeHomeDir renders nothing. SDDM's session stack is include login, so login.makeHomeDir is what actually creates the home directory.
  3. enumerate is not set. It cannot do what it was asked for on NixOS: system.nssModules only reaches nsncd's own LD_LIBRARY_PATH, and the nscd wire protocol has no enumeration request, so getpwent() — which is how SDDM builds its user list — never sees SSSD. Enumeration would still walk the directory every few minutes, a repeating failing LDAP round trip off-VPN. Typing the username at the SDDM prompt is the fallback the issue pre-approved. Single lookups are unaffected.

Two additions beyond the issue's scope

Both are hosts/nb-only and additive; flagging them explicitly since the issue didn't ask for either.

  • Persist /var/lib/sss/{db,secrets}. nb's root is tmpfs and the sssd unit's StateDirectory= covers only /var/lib/sssd (the rendered config); /var/lib/sss/* is a plain mkdir -p in preStart. Without this the offline cache is wiped on every reboot, so a reboot off-VPN would leave dominik.polakovics unable to log in at all — defeating cache_credentials = true. Only those two subdirs are persisted; pubconf/, mc/, pipes/, gpo_cache/ are runtime-only. Recovery note for a channel rollback (newer sysdb sssd refuses to open) is in the comment.
  • Drop sssd's Requires=/After=network-online.target. Upstream sets both and Before=systemd-user-sessions.service, which chains display-manager → systemd-user-sessions → sssd → network-online, backed by nm-online -s with NM_ONLINE_TIMEOUT=60. Every link-down boot would sit in front of the greeter for up to a minute. Not a lockout (wait-online is only WantedBy= the target), but a real regression on a laptop that boots off-network constantly. Safe to drop because going offline is SSSD's normal mode and the point of cache_credentials; before is left as upstream set it so sssd is still up before logins are allowed.

ADR

No amendment needed — this matches ADR-0022's M2 description (id_provider = ldap + auth_provider = krb5 with kcm and offline caching, coexistence per the decision). The divergences above are spelling/mechanism corrections, not changes of intent.

Verification (yours — the agent cannot reach the fleet)

  1. getent passwd dominik.polakovics → uid 5001, home /home/dominik.polakovics.
  2. getent group 5001 resolves.
  3. id dominik.polakovics — note whether supplementary LDAP groups appear. This is the unresolved rfc2307/memberUid vs groupOfNames/member question flagged in a comment; primary gid is what login needs.
  4. Confirm local dominik still logs in with its own password before logging out.
  5. Log in as dominik.polakovics with the Kerberos password from #267type the username, it will not be in the picker (divergence 3).
  6. In that session: klist shows krbtgt/CLOONAR.COM@CLOONAR.COM; echo \$KRB5CCNAME shows a KCM cache.
  7. Offline check: drop WireGuard, log out, log back in — should succeed from cache with no TGT.

Heads-up on 5: pam_mkhomedir runs with nixpkgs' defaults skel=/var/empty umask=0077, so /home/dominik.polakovices will be mode 0700 with no dotfiles — no .bashrc, no .profile. The shell will look bare. That is expected for a deliberately home-manager-less test session, not a failure.

Closes #269

M2 of #266 / ADR-0022. Adds `hosts/nb/modules/ldap-login.nix` and imports it from `hosts/nb/configuration.nix`. `id_provider = ldap` against `ldap.cloonar.com`, `auth_provider = krb5` against the KDC on `mail`, `services.sssd.kcm = true`, `cache_credentials = true`, plus `security.krb5` client config, `pam_mkhomedir` for the first graphical login, and system-level SPNEGO policy for Firefox and Chromium. Diff is 2 files: the new module, and `hosts/nb/configuration.nix` (+1 import, +2 persistence entries). Nothing outside `hosts/nb/`. ## ⚠️ Not dry-built — please run the gate before this lands The agent sandbox has no Nix at all (`nix-instantiate: command not found`), so `scripts/test-configuration nb` could not run and the commit used `--no-verify`. **`nb` deploys unattended every 5 minutes, so please dry-build before merging:** ./scripts/test-configuration -v nb To compensate, every option name was verified verbatim against `release-26.05` source rather than from memory: `nixos/modules/services/misc/sssd.nix`, `security/pam.nix`, `security/krb5/{default,krb5-conf-format}.nix`, `services/display-managers/sddm.nix`, `programs/firefox.nix`, and SSSD 2.13's own `src/config/cfg_rules.ini`. That verification caught three defects in the issue's spec (below). ## Coexistence / lockout safety `users.users.dominik` is untouched — uid, `/home/dominik`, its `hashedPassword`, `hosts/nb/users/dominik.nix`, and all four `sops.secrets.*.owner = \"dominik\"` declarations. No `users.users`/`users.groups` entry is added for the LDAP account; it resolves through NSS. No `wheel`/sudo for it. `utils/modules/ldap-auth.nix` is **not** edited, so `web-arm` is out of the blast radius. `pam_sss` is required nowhere. From `nixos/modules/security/pam.nix`: - **auth** — `pam_unix.so` (`sufficient`, `try_first_pass likeauth`) is listed *before* `pam_sss.so` (`sufficient`); `autoOrderRules` assigns order by list position, so `pam_unix` runs first and short-circuits for a local user. - **account** — `pam_sss.so` `sufficient` first, `pam_unix.so` `required` last (upstream's own comment explains why). `sufficient` discards failures, so all three of sssd-healthy (`USER_UNKNOWN`), sssd-dead (`AUTHINFO_UNAVAIL`) and network-down are ignored and `pam_unix` succeeds from `/etc/passwd`. - `sssdStrictAccess` is left unset, so the strict `[default=bad …]` control is not used. - `/etc/nsswitch.conf` is `files sss …` (`mkBefore [ \"files\" ]`), so a hung sssd cannot block local `getpwnam`. ## Three deliberate divergences from the issue Each has an in-file comment explaining why, so the reasoning is not lost. 1. **`krb5_store_password_if_offline`, not `krb5_store_password_if_no_ccache`.** The spelling in the issue is not an SSSD option — it is absent from sssd 2.13's `cfg_rules.ini`, and `sss_ini_call_validators()`'s return is discarded in `monitor.c`, so the daemon would have started, logged `Attribute ... is not allowed`, and silently ignored the line. **Worth correcting in the issue** so the next agent doesn't reintroduce it. 2. **`security.pam.services.login.makeHomeDir`, not `.sddm`.** `sddm.nix` sets `useDefaultRules = false` and supplies its own rules, while `makeHomeDir` only injects `pam_mkhomedir.so` into the *default* session rule list (`rules = lib.optionalAttrs cfg.useDefaultRules`) — so `sddm.makeHomeDir` renders nothing. SDDM's session stack is `include login`, so `login.makeHomeDir` is what actually creates the home directory. 3. **`enumerate` is not set.** It cannot do what it was asked for on NixOS: `system.nssModules` only reaches nsncd's own `LD_LIBRARY_PATH`, and the nscd wire protocol has no enumeration request, so `getpwent()` — which is how SDDM builds its user list — never sees SSSD. Enumeration would still walk the directory every few minutes, a repeating failing LDAP round trip off-VPN. Typing the username at the SDDM prompt is the fallback the issue pre-approved. Single lookups are unaffected. ## Two additions beyond the issue's scope Both are `hosts/nb`-only and additive; flagging them explicitly since the issue didn't ask for either. - **Persist `/var/lib/sss/{db,secrets}`.** `nb`'s root is tmpfs and the sssd unit's `StateDirectory=` covers only `/var/lib/sssd` (the rendered config); `/var/lib/sss/*` is a plain `mkdir -p` in `preStart`. Without this the offline cache is wiped on every reboot, so a reboot off-VPN would leave `dominik.polakovics` unable to log in at all — defeating `cache_credentials = true`. Only those two subdirs are persisted; `pubconf/`, `mc/`, `pipes/`, `gpo_cache/` are runtime-only. Recovery note for a channel *rollback* (newer sysdb sssd refuses to open) is in the comment. - **Drop sssd's `Requires=`/`After=network-online.target`.** Upstream sets both *and* `Before=systemd-user-sessions.service`, which chains display-manager → systemd-user-sessions → sssd → network-online, backed by `nm-online -s` with `NM_ONLINE_TIMEOUT=60`. Every link-down boot would sit in front of the greeter for up to a minute. Not a lockout (wait-online is only `WantedBy=` the target), but a real regression on a laptop that boots off-network constantly. Safe to drop because going offline is SSSD's normal mode and the point of `cache_credentials`; `before` is left as upstream set it so sssd is still up before logins are allowed. ## ADR No amendment needed — this matches ADR-0022's M2 description (`id_provider = ldap` + `auth_provider = krb5` with `kcm` and offline caching, coexistence per the decision). The divergences above are spelling/mechanism corrections, not changes of intent. ## Verification (yours — the agent cannot reach the fleet) 1. `getent passwd dominik.polakovics` → uid 5001, home `/home/dominik.polakovics`. 2. `getent group 5001` resolves. 3. `id dominik.polakovics` — note whether supplementary LDAP groups appear. This is the unresolved `rfc2307`/`memberUid` vs `groupOfNames`/`member` question flagged in a comment; primary gid is what login needs. 4. **Confirm local `dominik` still logs in with its own password before logging out.** 5. Log in as `dominik.polakovics` with the Kerberos password from #267 — **type the username**, it will not be in the picker (divergence 3). 6. In that session: `klist` shows `krbtgt/CLOONAR.COM@CLOONAR.COM`; `echo \$KRB5CCNAME` shows a KCM cache. 7. Offline check: drop WireGuard, log out, log back in — should succeed from cache with **no** TGT. Heads-up on 5: `pam_mkhomedir` runs with nixpkgs' defaults `skel=/var/empty umask=0077`, so `/home/dominik.polakovices` will be mode 0700 with **no** dotfiles — no `.bashrc`, no `.profile`. The shell will look bare. That is expected for a deliberately home-manager-less test session, not a failure. Closes #269
Add hosts/nb/modules/ldap-login.nix: id_provider = ldap against
ldap.cloonar.com with auth_provider = krb5 against the KDC on mail, so an
SDDM login also yields a TGT in a KCM cache. M2 of ADR-0022.

Purely additive. users.users.dominik, its uid, home, hashedPassword and
home-manager config are untouched, and nothing makes pam_sss required:
nixpkgs orders pam_unix (sufficient) ahead of pam_sss (sufficient) in the
auth stack, so a stopped sssd.service, a wrong bind password or a dead
network all fall through to local auth. sssdStrictAccess is left unset.
utils/modules/ldap-auth.nix is deliberately not reused or edited, keeping
web-arm out of the blast radius.

Also persists /var/lib/sss/{db,secrets} so the offline credential cache and
KCM ticket store survive a reboot under impermanence, and drops sssd's
Requires=/After=network-online.target so the greeter is not gated on
NetworkManager-wait-online on a link-down boot.

Three deliberate divergences from issue #269, each with an in-file comment:

- krb5_store_password_if_offline, not krb5_store_password_if_no_ccache. The
  latter is not an SSSD option (absent from sssd 2.13 cfg_rules.ini) and
  would have been silently ignored.
- security.pam.services.login.makeHomeDir, not .sddm. SDDM sets
  useDefaultRules = false, so makeHomeDir on sddm renders no pam_mkhomedir
  rule; SDDM's session stack includes login, which does.
- enumerate is not set. On NixOS, NSS modules load only inside nsncd and the
  nscd protocol has no enumeration request, so getpwent() (what SDDM's user
  picker uses) cannot see SSSD regardless.

Closes #269
Author
Owner

Typo correction to the last line of the description: the path is /home/dominik.polakovics (not polakovices). The committed files use the correct spelling throughout — verified, 6/6 occurrences canonical.

Typo correction to the last line of the description: the path is `/home/dominik.polakovics` (not `polakovices`). The committed files use the correct spelling throughout — verified, 6/6 occurrences canonical.
Comment-only change: every executable line and every sssd.conf directive is
byte-identical to the previous commit. 267 -> 140 lines, 170 -> 45 comment
lines, so commentary no longer outweighs code.

The long-form reasoning that was inlined (nixpkgs file:line citations, quoted
upstream source, the multi-paragraph justifications) already lives in the PR
description and ADR-0022, which is where it belongs. What stays is one or two
lines per non-obvious decision, kept mainly where a future reader would
otherwise "fix" a deliberate choice back.

Also anchors the rule as a convention in CLAUDE.md so it does not recur.
Author
Owner

This was generated by AI while landing a PR.

Comment density reduced at the maintainer's request before merge — ce2d2f3.

hosts/nb/modules/ldap-login.nix goes 267 → 140 lines, 170 → 45 comment lines. hosts/nb/configuration.nix's persistence block goes from a 15-line comment to 5. Comment-only: stripping comment lines from the before and after trees gives byte-identical output, for the Nix code and for the rendered sssd.conf directives alike, so the maintainer's local scripts/test-configuration -v nb on the previous commit still stands.

What was cut: nixpkgs file:line citations, quoted upstream source, and the multi-paragraph justifications — all of which are already in this PR's description and ADR-0022. What stays: one or two lines per non-obvious decision, concentrated where a future reader would otherwise "fix" a deliberate choice back (enumerate left unset, login.makeHomeDir not sddm, default_ccache_name omitted because services.sssd.kcm sets it, the dropped network-online.target ordering).

The rule is anchored in CLAUDE.md's Conventions section in the same commit so it does not recur: comments explain non-obvious decisions in one or two lines, and long-form reasoning belongs in the PR description or the ADR rather than the module.

> *This was generated by AI while landing a PR.* Comment density reduced at the maintainer's request before merge — `ce2d2f3`. `hosts/nb/modules/ldap-login.nix` goes 267 → 140 lines, 170 → 45 comment lines. `hosts/nb/configuration.nix`'s persistence block goes from a 15-line comment to 5. **Comment-only:** stripping comment lines from the before and after trees gives byte-identical output, for the Nix code and for the rendered `sssd.conf` directives alike, so the maintainer's local `scripts/test-configuration -v nb` on the previous commit still stands. What was cut: nixpkgs `file:line` citations, quoted upstream source, and the multi-paragraph justifications — all of which are already in this PR's description and ADR-0022. What stays: one or two lines per non-obvious decision, concentrated where a future reader would otherwise "fix" a deliberate choice back (`enumerate` left unset, `login.makeHomeDir` not `sddm`, `default_ccache_name` omitted because `services.sssd.kcm` sets it, the dropped `network-online.target` ordering). The rule is anchored in `CLAUDE.md`'s Conventions section in the same commit so it does not recur: comments explain non-obvious *decisions* in one or two lines, and long-form reasoning belongs in the PR description or the ADR rather than the module.
Author
Owner

[autoland] verdict: pass

This was generated by AI while landing a PR.

PASS.

Signal relied on: the maintainer ran scripts/test-configuration -v nb locally against b1e53d9 and reported it clean. Forgejo Actions reports no check rows for this PR (labctl pr checks 270state: none), and this agent's sandbox has no Nix (no /nix/store), so the maintainer's local dry-build is the only evaluation gate that has run — and it is the same gate scripts/pre-commit invokes. The follow-up commit ce2d2f3 is provably comment-only (comment-stripped trees are byte-identical, Nix code and rendered sssd.conf directives alike), so that result carries forward.

Conventions: Conventional Commits title with host scope; body carries Closes #269; diff confined to hosts/nb/ plus the CLAUDE.md convention added at the maintainer's request while landing.

Conflicts: none — merge base equals origin/main (53118e5), fast-forward.

Reviewed by inspection against nixpkgs release-26.05 rather than taking the description's word for it: services.sssd really declares config/settings/kcm/environmentFile with the two config forms mutually exclusive by assertion; its kcm block really sets security.krb5.settings.libdefaults.default_ccache_name, so omitting that key here is correct; the sssd unit really carries after/requires = network-online.target together with before = systemd-user-sessions.service, so the mkForce targets a real greeter-blocking chain; security.krb5 declares enable/package/settings as used. The Firefox reasoning also holds — nixpkgs' wrapFirefox emits no default policies absent usesNixExtensions/smartcardSupport, and home-manager 25.11 synthesises policies only for languagePacks and profile bookmarks, neither of which dominik.nix uses, so the /etc policy file shadows nothing. Eval-conflict sweep on nb found no competing definer of makeHomeDir, security.krb5, systemd.tmpfiles.rules, the two environment.etc paths, or services.sssd; sops.secrets.sssd-environment resolves against an existing key in hosts/nb/secrets.yaml.

Lockout safety is sound as described: pam_unix is ordered before pam_sss on auth, pam_sss is sufficient on account with pam_unix required last, sssdStrictAccess is unset, and nsswitch is files sss. users.users.dominik and utils/modules/ldap-auth.nix are untouched.

No blocking concerns. Runtime behaviour (supplementary-group resolution under the unresolved rfc2307 vs groupOfNames question, and the offline-login path) remains for the maintainer's post-deploy checklist in the PR description.

[autoland] verdict: pass > *This was generated by AI while landing a PR.* **PASS.** **Signal relied on:** the maintainer ran `scripts/test-configuration -v nb` locally against `b1e53d9` and reported it clean. Forgejo Actions reports no check rows for this PR (`labctl pr checks 270` → `state: none`), and this agent's sandbox has no Nix (no `/nix/store`), so the maintainer's local dry-build is the only evaluation gate that has run — and it is the same gate `scripts/pre-commit` invokes. The follow-up commit `ce2d2f3` is provably comment-only (comment-stripped trees are byte-identical, Nix code and rendered `sssd.conf` directives alike), so that result carries forward. **Conventions:** Conventional Commits title with host scope; body carries `Closes #269`; diff confined to `hosts/nb/` plus the `CLAUDE.md` convention added at the maintainer's request while landing. **Conflicts:** none — merge base equals `origin/main` (`53118e5`), fast-forward. **Reviewed by inspection against nixpkgs `release-26.05`** rather than taking the description's word for it: `services.sssd` really declares `config`/`settings`/`kcm`/`environmentFile` with the two config forms mutually exclusive by assertion; its `kcm` block really sets `security.krb5.settings.libdefaults.default_ccache_name`, so omitting that key here is correct; the sssd unit really carries `after`/`requires = network-online.target` together with `before = systemd-user-sessions.service`, so the `mkForce` targets a real greeter-blocking chain; `security.krb5` declares `enable`/`package`/`settings` as used. The Firefox reasoning also holds — nixpkgs' `wrapFirefox` emits no default policies absent `usesNixExtensions`/`smartcardSupport`, and home-manager 25.11 synthesises policies only for `languagePacks` and profile bookmarks, neither of which `dominik.nix` uses, so the `/etc` policy file shadows nothing. Eval-conflict sweep on `nb` found no competing definer of `makeHomeDir`, `security.krb5`, `systemd.tmpfiles.rules`, the two `environment.etc` paths, or `services.sssd`; `sops.secrets.sssd-environment` resolves against an existing key in `hosts/nb/secrets.yaml`. **Lockout safety** is sound as described: `pam_unix` is ordered before `pam_sss` on auth, `pam_sss` is `sufficient` on account with `pam_unix` required last, `sssdStrictAccess` is unset, and `nsswitch` is `files sss`. `users.users.dominik` and `utils/modules/ldap-auth.nix` are untouched. **No blocking concerns.** Runtime behaviour (supplementary-group resolution under the unresolved `rfc2307` vs `groupOfNames` question, and the offline-login path) remains for the maintainer's post-deploy checklist in the PR description.
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.

Dependencies

No dependencies set.

Reference
Cloonar/nixos!270
No description provided.