# Fully declarative FreeIPA domain membership. # # Configures security.ipa (SSSD, Kerberos, PAM, NSSwitch) and places a # pre-provisioned host keytab via sops-nix so no imperative ipa-client-install # step is needed after deployment. # # Usage (in a host.nix imports list): # (import ../../modules/ipa/client.nix { # keytabSopsFile = ../../secrets/.keytab; # caCertFile = ../../certs/ipa-ca.crt; # already committed — do not re-fetch # }) # # The host.nix networking block must also set: # networking.domain = vars.homeDomain; # needed for Kerberos FQDN # networking.nameservers = [ vars.domainControllerIp ]; # IPA DNS # # One-time operator setup per host (do this BEFORE deploying): # # 0. Generate SSH host keys and the host's age key for sops: # scripts/secrets/sync-host-keys.sh # This must run before step 1 so the host age key is in .sops.yaml # and the keytab can be encrypted for the host to read at boot. # # 1. Add the IPA host account and produce the sops-encrypted keytab: # scripts/ipa/create-nixos-ipa-host-account.sh [--ip ] # The script handles ipa host-add, ipa-getkeytab, .sops.yaml patching, # and sops encryption in one step. See the script header for details. # # 2. Wire up the host (see "Usage" above), then deploy: # nixos-rebuild switch (or create-proxmox-resource.sh) # No further manual enrollment steps are required after deployment. # # 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 { keytabSopsFile, caCertFile }: { config, lib, pkgs, vars, ... }: let 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 caCertFile); in { security.ipa = { enable = true; domain = vars.homeDomain; realm = realm; server = vars.ipaServer; certificate = caCertPkg; basedn = 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 = keytabSopsFile; format = "binary"; path = "/etc/krb5.keytab"; owner = "root"; group = "root"; mode = "0600"; restartUnits = [ "sssd.service" ]; }; }