Archived
Check NixOS configurations / eval-hosts (pull_request) Failing after 9m42s
On first enrollment /home/wayne doesn't exist until the IPA user's first login (pam_mkhomedir creates it). home-manager-<user>.service cd's into the home dir immediately and fails with ENOENT, causing the whole rebuild activation to return exit code 4. Add ConditionPathExists so systemd skips the service (condition not met, no failure) instead. After first login the dir exists and subsequent rebuilds activate Home Manager normally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
189 lines
8.3 KiB
Nix
189 lines
8.3 KiB
Nix
# 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/<hostname>.keytab; is a no-op for all other hosts.
|
|
#
|
|
# To enroll a new host:
|
|
# 0. scripts/secrets/sync-host-keys.sh <flake-target>
|
|
# 1. scripts/ipa/create-nixos-ipa-host-account.sh [--ip <addr>] <hostname>
|
|
# (adds .sops.yaml rule, runs ipa host-add, encrypts keytab in one step)
|
|
# 2. git add secrets/<hostname>.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 <fqdn> [--ip-address=<ip>] --force
|
|
# b. On the FreeIPA server: ipa-getkeytab -s <ipa-server> -p host/<fqdn> -k /tmp/<host>.keytab
|
|
# c. From the repo root (path must match for sops creation rule to apply):
|
|
# cp /tmp/<host>.keytab secrets/<host>.keytab
|
|
# sops -e --input-type binary -i secrets/<host>.keytab
|
|
# d. Commit secrets/<host>.keytab and the updated .sops.yaml, then deploy.
|
|
#
|
|
# vars dependencies: homeDomain, ipaServer, domainControllerIp, ipaUser
|
|
|
|
{ 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"
|
|
# Pre-create the IPA user's home dir so Home Manager activation succeeds
|
|
# even before their first login. On a fresh system SSSD may not have
|
|
# resolved the user yet — tmpfiles warns and skips in that case (non-fatal),
|
|
# and pam_mkhomedir covers the first-login path as a fallback.
|
|
"d /home/${vars.ipaUser} 0700 ${vars.ipaUser} ${vars.ipaUser} - -"
|
|
];
|
|
|
|
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
|
|
'';
|
|
};
|
|
};
|
|
|
|
# Home Manager config for the IPA primary user, applied on every enrolled
|
|
# host. Manages what IPA doesn't: dotfiles, user-scoped packages, session
|
|
# variables. Switch-nix/Test-nix/buildImage are system-wide (configuration.nix)
|
|
# so they don't need to be repeated here.
|
|
# NixOS requires isNormalUser/isSystemUser + group on any entry in
|
|
# users.users. HM with useUserPackages = true (set in flake.nix) adds a stub
|
|
# entry for each HM user so it can install packages to
|
|
# /etc/profiles/per-user/<name>/. This definition satisfies those assertions.
|
|
# With security.ipa setting "passwd: sss files" in nsswitch, SSSD's IPA entry
|
|
# takes priority for NSS lookups — this local stub is only a fallback when
|
|
# SSSD is unreachable (at which point auth fails anyway).
|
|
# HM with useUserPackages = true (flake.nix) sets users.users.${ipaUser}.packages,
|
|
# which forces the stub into /etc/passwd. pam_sss.so with the "localusers" flag
|
|
# (added by NixOS when SSSD is enabled) then skips SSSD for any user it finds in
|
|
# local /etc/passwd — including this stub — falling through to pam_unix, which has
|
|
# no password for the stub → sudo auth always fails.
|
|
#
|
|
# Fix: NOPASSWD for the IPA user. The IPA user already authenticated to reach a
|
|
# shell (SSH public key from IPA or Kerberos), so re-prompting via a broken PAM
|
|
# path is security theater on a single-admin homelab.
|
|
users.users.${vars.ipaUser} = {
|
|
isNormalUser = true;
|
|
group = "users";
|
|
extraGroups = [ "wheel" ];
|
|
createHome = false;
|
|
};
|
|
|
|
# home-manager-<user>.service fails on first enrollment because /home/wayne
|
|
# doesn't exist until the user's first login (pam_mkhomedir creates it then).
|
|
# ConditionPathExists makes systemd skip the service (exit 0, condition not
|
|
# met) instead of failing. After first login the dir exists and subsequent
|
|
# rebuilds activate HM normally.
|
|
systemd.services."home-manager-${vars.ipaUser}".unitConfig.ConditionPathExists =
|
|
"/home/${vars.ipaUser}";
|
|
|
|
security.sudo.extraRules = [{
|
|
users = [ vars.ipaUser ];
|
|
commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }];
|
|
}];
|
|
|
|
# Home Manager config for the IPA primary user, applied on every enrolled
|
|
# host. Manages what IPA doesn't: dotfiles, user-scoped packages, session
|
|
# variables. Switch-nix/Test-nix/buildImage are system-wide (configuration.nix)
|
|
# so they don't need to be repeated here.
|
|
#
|
|
# homeDirectory uses mkForce because HM's NixOS integration module sets it to
|
|
# "/var/empty" for users not found in config.users.users at eval time (SSSD
|
|
# users aren't visible there).
|
|
home-manager.users.${vars.ipaUser} = { pkgs, ... }: {
|
|
home = {
|
|
username = vars.ipaUser;
|
|
homeDirectory = lib.mkForce "/home/${vars.ipaUser}";
|
|
stateVersion = "26.05";
|
|
packages = with pkgs; [ tmux sshfs ];
|
|
sessionVariables.EDITOR = "nano";
|
|
};
|
|
programs.home-manager.enable = true;
|
|
programs.bash.enable = true;
|
|
};
|
|
}
|