From bac9951995efd207dc52f20d20783595cd9cad75 Mon Sep 17 00:00:00 2001 From: lyrathorpe Date: Mon, 6 Jul 2026 13:57:28 +0100 Subject: [PATCH] feat(sssd): SSSD LDAP auth against Authentik outpost --- modules/sssd.nix | 130 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 modules/sssd.nix diff --git a/modules/sssd.nix b/modules/sssd.nix new file mode 100644 index 0000000..9a49acb --- /dev/null +++ b/modules/sssd.nix @@ -0,0 +1,130 @@ +# Authentik LDAP authentication for NixOS hosts. +# +# Wires SSSD (System Security Services Daemon) to the Authentik LDAP outpost so +# every Linux host authenticates users against the same directory that backs the +# SSO stack. Enabled by default on every NixOS host via baseModules; the EDaaS +# WSL box opts out (services.authentikLdap.enable = false) because it is a +# work-managed Windows-hosted environment. +# +# The Authentik LDAP provider exposes NON-standard object classes/attributes +# (goauthentik.io/ldap/user, goauthentik.io/ldap/group) alongside the POSIX +# attributes (uid, uidNumber, gidNumber, homeDirectory), so the schema mappings +# below are explicit rather than relying on an RFC2307 default. +# +# The bind password is NOT inlined: services.sssd.config renders to the world- +# readable Nix store, so the credential is delivered out-of-band by agenix as an +# sssd.conf drop-in under /etc/sssd/conf.d/ (SSSD merges conf.d/*.conf after the +# main file). See secrets/README.md. +{ + config, + lib, + ... +}: +let + cfg = config.services.authentikLdap; + + # Directory coordinates for the Authentik LDAP provider. + ldapUri = "ldaps://ldap.lyrapup.pet:636"; + searchBase = "dc=ldap,dc=goauthentik,dc=io"; + bindDn = "cn=sssd-bind,ou=users,dc=ldap,dc=goauthentik,dc=io"; +in +{ + options.services.authentikLdap.enable = + lib.mkEnableOption "SSSD authentication against the Authentik LDAP outpost" + // { + default = true; + }; + + config = lib.mkIf cfg.enable { + services.sssd = { + enable = true; + + # Non-secret sssd.conf. The bind password is injected separately via the + # agenix conf.d drop-in (ldap_default_authtok lives there, not here) to + # keep it out of the Nix store. + config = '' + [sssd] + config_file_version = 2 + services = nss, pam + domains = default + + [nss] + # Do not walk the whole directory for `getent passwd` etc. + filter_users = root + filter_groups = root + + [pam] + + [domain/default] + # --- Providers -------------------------------------------------------- + id_provider = ldap + auth_provider = ldap + chpass_provider = none + access_provider = permit + + # --- Connection ------------------------------------------------------- + ldap_uri = ${ldapUri} + ldap_search_base = ${searchBase} + ldap_default_bind_dn = ${bindDn} + ldap_default_authtok_type = password + # ldap_default_authtok is supplied by the agenix drop-in in conf.d. + + # --- TLS (LDAPS on 636; no StartTLS) --------------------------------- + ldap_id_use_start_tls = false + ldap_tls_reqcert = demand + + # --- Schema: Authentik LDAP provider --------------------------------- + # Authentik returns DN-valued group membership (member/memberOf), so + # rfc2307bis (not rfc2307) is the correct base schema. + ldap_schema = rfc2307bis + + # Users: goauthentik.io/ldap/user, keyed by uid; POSIX attrs are + # standard names (uidNumber/gidNumber/homeDirectory). + ldap_user_object_class = goauthentik.io/ldap/user + ldap_user_name = uid + ldap_user_uid_number = uidNumber + ldap_user_gid_number = gidNumber + ldap_user_home_directory = homeDirectory + ldap_user_gecos = displayName + ldap_user_shell = loginShell + + # Groups: goauthentik.io/ldap/group, keyed by cn. + ldap_group_object_class = goauthentik.io/ldap/group + ldap_group_name = cn + ldap_group_gid_number = gidNumber + ldap_group_member = member + + # --- Behaviour -------------------------------------------------------- + cache_credentials = true + enumerate = false + ''; + }; + + # agenix delivers the bind password as an sssd.conf drop-in. The decrypted + # plaintext IS a valid conf.d snippet: + # + # [domain/default] + # ldap_default_authtok = + # + # SSSD requires conf.d files to be root-owned and 0600 or it ignores them. + age.secrets.ldap-bind = { + file = ../secrets/ldap-bind.age; + path = "/etc/sssd/conf.d/01-ldap-authtok.conf"; + owner = "root"; + group = "root"; + mode = "0600"; + }; + + # Restart SSSD when the credential drop-in changes. agenix writes secrets in + # a system activation script that runs before systemd (re)starts services on + # a `switch`, so the file is present by the time sssd starts; the trigger + # picks up rotations of the bind password. + systemd.services.sssd.restartTriggers = [ config.age.secrets.ldap-bind.path ]; + + # Create home directories on first login for LDAP users (they have no + # locally-provisioned home). NixOS wires nss + the SSSD PAM stack when + # services.sssd.enable is true; mkHomeDir adds pam_mkhomedir to it. + security.pam.services.login.makeHomeDir = true; + security.pam.services.sshd.makeHomeDir = true; + }; +}