Adds nextcloud-client, chromium, git, vim, claude-code, fish, sops,
the Nextcloud tray service, SOPS_AGE_KEY_FILE, and Proxmox/PBS desktop
shortcuts to wayne's HM config on GUI hosts. Uses lib.mkDefault on the
EDITOR setting in ipa/client.nix so the GUI layer can override to vim.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two independent fixes:
1. hashedPassword = "!" on the IPA user stub
Without any shadow entry, pam_unix returns PAM_AUTHINFO_UNAVAIL
before prompting, so PAM_AUTHTOK is never set. The "!" marker
(account locked, not a real hash) ensures a shadow entry exists so
pam_unix prompts and sets PAM_AUTHTOK — which the subsequent
pam_sss module can then use.
2. pam_sss try_first_pass instead of use_first_pass (login + su)
use_first_pass silently fails when PAM_AUTHTOK is unset.
try_first_pass prompts independently in that case, making IPA
password auth work even if pam_unix returns early for any reason.
Root cause found during incident: wayne (uid 50002) was outside IPA's
auto-SID range (163800000+), so sidgen never assigned him an
ipaNTSecurityIdentifier. Without it, ipadb's handle_authdata step fails
with "Generic error" after SPAKE pre-auth succeeds. Fixed by manually
adding objectClass: ipaNTUserAttrs + ipaNTSecurityIdentifier RID 550002
to wayne's LDAP entry on domain-controller. Any future IPA user with a
manually-assigned uid outside the auto-range needs the same treatment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create an IPA group "docker-access" (GID 50010) and pin the local
"docker" group to that GID on all Docker hosts. Any IPA user in the
docker-access group automatically gains docker socket access through
SSSD supplementary-group resolution — no per-host docker.members
entry needed.
Specific changes:
- variables.nix: add dockerAccessGid = 50010
- modules/docker/enable-service.nix: lib.mkForce docker GID to
dockerAccessGid, removing the need to name individual IPA users
- modules/build-types/docker.nix: remove direct wayne docker.members
entry (access now comes from IPA group)
- modules/ipa/client.nix: refactor repeated security.* / systemd.*
top-level keys into merged attribute sets (fixes statix W20); add
security.pam.services.lightdm.makeHomeDir so the GUI login path
also creates the home dir on first login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
HM's useUserPackages creates a users.users stub for every configured HM
user, which lands wayne in /etc/passwd. NixOS adds pam_sss.so with the
"localusers" flag to the sudo PAM stack when SSSD is enabled; that flag
causes pam_sss to skip SSSD for any user found in local /etc/passwd,
falling through to pam_unix which has no shadow password for the stub.
Result: sudo auth always fails for the IPA user despite being in wheel.
Use NOPASSWD for the IPA user in sudoers instead. The IPA user already
authenticated to reach a shell (SSH key from IPA or Kerberos), so
re-prompting via a broken PAM path is security theater on a homelab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
initgroups() uses NSS (groups: files sss) to build the supplemental
group list at login. Adding wheel to the local users.users stub means
the IPA user gets wheel membership from /etc/group, satisfying sudo's
group check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Any enrolled host now automatically gets a Home Manager profile for the
IPA primary user (vars.ipaUser = "wayne"), covering what IPA doesn't:
dotfiles, user-scoped packages (tmux, sshfs), and EDITOR variable.
The home directory is pre-created by systemd-tmpfiles so HM activation
succeeds on steady-state systems before first login; pam_mkhomedir remains
as a fallback for fresh deploys where SSSD hasn't cached the user yet.
A minimal users.users stub satisfies NixOS's assertion requirements
(isNormalUser + group) that arise because home-manager.useUserPackages
creates a users.users entry to install packages to /etc/profiles/per-user/.
The stub is shadowed by SSSD at runtime (security.ipa sets passwd: sss files).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The NixOS IPA module adds ConditionPathExists=/etc/krb5.keytab drop-ins
for auth-rpcgss-module.service and rpc-gssd.service via systemd.units.
In LXC containers with keytabs those conditions pass, the services start,
and then fail because auth_rpcgss can't be loaded and rpc_pipefs doesn't
exist in the container namespace.
Use lib.mkForce on our systemd.units text to win the conflict with NixOS's
existing definitions, and include ConditionVirtualization=!container
alongside the ConditionPathExists conditions so the services are skipped
(inactive, not failed) in containers that have a keytab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
modules/ipa/client.nix is now a self-contained NixOS module (no longer a
parameterized function): it checks builtins.pathExists for
secrets/<hostname>.keytab at eval time and enables itself automatically if
found, making it a no-op for hosts without a keytab.
modules/common/configuration.nix imports it so every host in the flake is
a candidate for IPA enrollment — no per-host wiring needed. Adding a
keytab (via scripts/ipa/create-nixos-ipa-host-account.sh) is now the
only step required to enroll a host.
The module also sets networking.domain and networking.nameservers via
mkDefault when active, so new hosts don't need those set explicitly.
Also:
- Remove explicit IPA imports from hosts/nix-cache and hosts/tailscale-router
- Add secrets/pxe-boot.keytab + creation rule; remove incorrect
secrets/nixos.sweet.home.keytab and its creation rule
- Add .sops.yaml creation rules for all remaining host keytabs
(server, docker, tor-relay, nix-minimal, nixos) so the creation script
can target them without manual .sops.yaml edits
- Fix duplicate tailscale-router.keytab rule and corrupted gui.yaml comment
block in .sops.yaml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Script fixes:
- Rename HOSTNAME variable to TARGET (shadowed the bash builtin)
- Fix ipa-getkeytab -s to always use IPA_SERVER, not DC_HOST (diverge if
--dc is overridden to a jump host)
- Remove dead REALM variable
- Add EXIT trap to delete the plaintext keytab if the script aborts before
sops encryption completes; cleared after successful encrypt
- Distinguish real ipa host-add failures from "already exists" instead of
swallowing all errors with || true
- Warn explicitly when no platform age keys exist for the target (keytab
would be admin-only and the host couldn't decrypt it at boot)
- Fix sops fallback from pinned nixos-25.11 channel to nixpkgs (uses the
repo's own flake.lock)
- Expand "next steps" output to include networking.domain and nameservers
lines that host.nix requires for IPA membership
Module docs:
- Point to the script as the primary setup path; move manual steps to a
fallback section
- Note that certs/ipa-ca.crt is already committed (no need to re-fetch)
- Document the networking.domain and nameservers requirements in the header
- Add sync-host-keys.sh as explicit step 0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OpenSSH 10.0 tightened AuthorizedKeysCommand security by checking every
path component of the command binary for group/world-write permission.
/nix/store is 1775 (group-writable by nixbld), so sshd silently skips
the command for any binary in the Nix store — causing IPA pubkey auth to
silently fail with no diagnostic.
Fix: copy sss_ssh_authorizedkeys to /usr/local/bin via systemd tmpfiles
(C+ copies the file rather than symlinking, so the path at runtime is
root-owned/755 throughout), and point AuthorizedKeysCommand at the copy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- security.pam.services.sshd.makeHomeDir: IPA users have no pre-created
home directory on the host; without this, sshd opens a session to a
missing directory and resets the connection immediately after auth
- AuthorizedKeysCommand was already added in previous commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- modules/ipa/client.nix: add AuthorizedKeysCommand so sshd fetches SSH
public keys from IPA via sss_ssh_authorizedkeys, enabling pubkey login
without per-host authorized_keys files
- hosts/tailscale-router/host.nix: add IPA client module + networking.domain
so SSSD runs and wayne can authenticate on this host
- secrets/tailscale-router.keytab: sops-encrypted keytab for
tailscale-router.sweet.home (generated by create-nixos-ipa-host-account.sh)
- .sops.yaml: creation rule for secrets/tailscale-router.keytab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sops matches creation rules against the input file path, so encrypting
/tmp/<host>.keytab directly with stdout redirect fails to find the rule.
Copy to secrets/ first, then use -i to encrypt in-place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds modules/ipa/client.nix — a parameterized module that joins a NixOS host
to the sweet.home FreeIPA domain without ipa-client-install. It configures
security.ipa (SSSD, Kerberos, PAM, NSSwitch) and places a pre-provisioned host
keytab via sops-nix binary secret so enrollment is fully reproducible from the
flake.
- variables.nix: adds ipaServer (FQDN of the FreeIPA KDC; security.ipa.server
requires a hostname, not an IP, for Kerberos/TLS)
- certs/ipa-ca.crt: placeholder for the IPA CA public certificate (operator
replaces with: curl http://<ipa-server>/ipa/config/ca.crt)
- secrets/nix-cache.keytab: placeholder binary sops file (operator replaces
with the encrypted keytab after ipa host-add + ipa-getkeytab)
- .sops.yaml: adds creation rule for secrets/nix-cache.keytab (same recipients
as secrets/nix-cache.yaml)
- hosts/nix-cache/host.nix: imports the IPA client module; adds
networking.domain so the host's FQDN resolves correctly
Module header documents the three operator steps needed per host before deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>