diff --git a/modules/build-types/docker.nix b/modules/build-types/docker.nix index d3c3be3..d83aa04 100644 --- a/modules/build-types/docker.nix +++ b/modules/build-types/docker.nix @@ -33,9 +33,6 @@ ]; users.users.${vars.primaryUser}.extraGroups = [ "docker" ]; - # Grant the IPA domain user docker access via the local group so that - # `wayne` can manage containers without sudo. - users.groups.docker.members = [ "wayne" ]; services.openssh.settings.PermitRootLogin = "yes"; networking.firewall.allowedTCPPorts = [ diff --git a/modules/docker/enable-service.nix b/modules/docker/enable-service.nix index 2642f93..e52f579 100644 --- a/modules/docker/enable-service.nix +++ b/modules/docker/enable-service.nix @@ -1,10 +1,16 @@ -{ pkgs, vars, ... }: +{ lib, pkgs, vars, ... }: { virtualisation.docker = { enable = true; package = pkgs.docker; }; + # Pin the docker group GID to match the IPA "docker-access" group so that + # IPA group membership alone grants access to the Docker socket. Any user + # whose supplementary groups (resolved by SSSD from IPA) include GID + # vars.dockerAccessGid will pass the socket group-permission check without + # any per-host users.groups.docker.members entry. + users.groups.docker.gid = lib.mkForce vars.dockerAccessGid; users.users.${vars.primaryUser}.extraGroups = [ "docker" ]; environment.systemPackages = with pkgs; [ docker-compose diff --git a/modules/ipa/client.nix b/modules/ipa/client.nix index 16a10c9..818400c 100644 --- a/modules/ipa/client.nix +++ b/modules/ipa/client.nix @@ -38,47 +38,113 @@ 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; + security = { + ipa = { + enable = true; + domain = vars.homeDomain; + inherit realm; + server = vars.ipaServer; + certificate = caCertPkg; + inherit basedn; + ipaHostname = fqdn; + offlinePasswords = true; + cacheCredentials = true; + }; + + # 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. + # lightdm also needs this so the GUI login path can create the home dir + # if it was not pre-seeded by the tmpfiles rule above (e.g. on first boot + # before SSSD has resolved the user). + pam.services = { + sshd.makeHomeDir = true; + lightdm.makeHomeDir = true; + }; + + # 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. + sudo.extraRules = [{ + users = [ vars.ipaUser ]; + commands = [{ command = "ALL"; options = [ "NOPASSWD" ]; }]; + }]; }; - # 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} - -" - ]; + systemd = { + # 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. + 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} - -" + ]; + + # 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. + 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-.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. + services."home-manager-${vars.ipaUser}".unitConfig.ConditionPathExists = + "/home/${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. @@ -92,44 +158,6 @@ lib.mkIf enabled { 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 @@ -137,15 +165,6 @@ lib.mkIf enabled { # 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"; @@ -153,19 +172,6 @@ lib.mkIf enabled { createHome = false; }; - # home-manager-.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) diff --git a/variables.nix b/variables.nix index 2e639a9..195b4df 100644 --- a/variables.nix +++ b/variables.nix @@ -85,6 +85,12 @@ # that IPA itself doesn't cover: dotfiles, user packages, session variables. ipaUser = "wayne"; + # GID of the IPA "docker-access" group (GID 50010 on the IPA server). + # The local "docker" group is pinned to this GID on every host that runs + # Docker so that IPA group membership alone grants docker socket access - + # no per-host users.groups.docker.members entry for the IPA user needed. + dockerAccessGid = 50010; + # HA file server cluster # LAN IPs (vmbr0 / ens18) — client-facing: iSCSI initiators, NFS, management. # Storage IPs (vmbr1 / ens19) — isolated internal bridge, used for DRBD