# Fully declarative FreeIPA domain membership. # # Imported by modules/common/configuration.nix — no per-host wiring needed. # Enables itself automatically on any host that has a sops-encrypted keytab # at secrets/.keytab; is a no-op for all other hosts. # # To enroll a new host: # 0. scripts/secrets/sync-host-keys.sh # 1. scripts/ipa/create-nixos-ipa-host-account.sh [--ip ] # (adds .sops.yaml rule, runs ipa host-add, encrypts keytab in one step) # 2. git add secrets/.keytab .sops.yaml && git commit # 3. Deploy — no further steps required. # # Manual fallback (if the script isn't usable): # a. On the FreeIPA server: ipa host-add [--ip-address=] --force # b. On the FreeIPA server: ipa-getkeytab -s -p host/ -k /tmp/.keytab # c. From the repo root (path must match for sops creation rule to apply): # cp /tmp/.keytab secrets/.keytab # sops -e --input-type binary -i secrets/.keytab # d. Commit secrets/.keytab and the updated .sops.yaml, then deploy. # # vars dependencies: homeDomain, ipaServer, domainControllerIp { config, lib, pkgs, vars, ... }: let keytabPath = ../../secrets + "/${config.networking.hostName}.keytab"; enabled = builtins.pathExists keytabPath; realm = lib.strings.toUpper vars.homeDomain; fqdn = "${config.networking.hostName}.${vars.homeDomain}"; # "sweet.home" -> "dc=sweet,dc=home" basedn = lib.strings.concatMapStringsSep "," (c: "dc=${c}") (lib.strings.splitString "." vars.homeDomain); # security.ipa.certificate expects a derivation (package), not a raw path. caCertPkg = pkgs.writeText "ipa-ca.crt" (builtins.readFile ../../certs/ipa-ca.crt); in lib.mkIf enabled { networking.domain = lib.mkDefault vars.homeDomain; networking.nameservers = lib.mkDefault [ vars.domainControllerIp ]; security.ipa = { enable = true; domain = vars.homeDomain; inherit realm; server = vars.ipaServer; certificate = caCertPkg; inherit basedn; ipaHostname = fqdn; offlinePasswords = true; cacheCredentials = true; }; # Fetch SSH public keys from IPA so users can log in with the key stored # in their IPA profile rather than needing ~/.ssh/authorized_keys on every # host. sss_ssh_authorizedkeys queries SSSD (which queries IPA LDAP). # # /nix/store is 1775 (group-writable by nixbld). OpenSSH 10.0+ rejects # AuthorizedKeysCommand binaries whose path contains any group-writable # component, silently skipping the command. Copy to /usr/local/bin (all # components root-owned, 755) so the path passes sshd's safety check. systemd.tmpfiles.rules = [ "d /usr/local 0755 root root - -" "d /usr/local/bin 0755 root root - -" "C+ /usr/local/bin/sss_ssh_authorizedkeys 0555 root root - ${pkgs.sssd}/bin/sss_ssh_authorizedkeys" ]; services.openssh.extraConfig = '' AuthorizedKeysCommand /usr/local/bin/sss_ssh_authorizedkeys %u AuthorizedKeysCommandUser nobody ''; # Create the home directory on first login if it doesn't exist yet. # IPA users have no pre-created home on the host; without this sshd # opens a session to a non-existent directory and resets the connection. security.pam.services.sshd.makeHomeDir = true; # Host keytab: pre-provisioned on the IPA server, sops-encrypted binary. # Placed at /etc/krb5.keytab before SSSD starts so the host authenticates # to IPA without running ipa-client-install. sops.secrets."ipa-host-keytab" = { sopsFile = keytabPath; format = "binary"; path = "/etc/krb5.keytab"; owner = "root"; group = "root"; mode = "0600"; restartUnits = [ "sssd.service" ]; }; # security.ipa enables Kerberos (security.krb5) which causes systemd to # start auth-rpcgss-module.service and rpc-gssd.service for Kerberos NFS # authentication. LXC containers can't load the auth_rpcgss kernel module # and don't have /var/lib/nfs/rpc_pipefs, so both services fail. # # The NixOS IPA module already adds a drop-in for auth-rpcgss-module.service # with ConditionPathExists=/etc/krb5.keytab. We use lib.mkForce to win the # text conflict and add ConditionVirtualization=!container alongside it so # the service is skipped (not failed) in containers that do have a keytab. # Same fix for rpc-gssd.service which also fails in containers. systemd.units = lib.mkIf config.boot.isContainer { "auth-rpcgss-module.service" = { overrideStrategy = "asDropinIfExists"; text = lib.mkForce '' [Unit] ConditionPathExists= ConditionPathExists=/etc/krb5.keytab ConditionVirtualization=!container ''; }; # rpc-gssd also has ConditionPathExists from the NixOS IPA module (and an # X-Restart-Triggers store path from systemd.nix). Use mkForce to win; # omit X-Restart-Triggers since this service is skipped in containers anyway. "rpc-gssd.service" = { overrideStrategy = "asDropinIfExists"; text = lib.mkForce '' [Unit] ConditionPathExists= ConditionPathExists=/etc/krb5.keytab ConditionVirtualization=!container ''; }; }; }