From f4bbd6331d772fe51c599efd398ae404febd335d Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Mon, 27 Jul 2026 23:31:21 +1000 Subject: [PATCH] feat(ipa): add reusable declarative FreeIPA client module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/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 --- .sops.yaml | 10 ++++++ certs/ipa-ca.crt | 9 +++++ hosts/nix-cache/host.nix | 5 +++ modules/ipa/client.nix | 73 ++++++++++++++++++++++++++++++++++++++++ secrets/nix-cache.keytab | 0 variables.nix | 1 + 6 files changed, 98 insertions(+) create mode 100644 certs/ipa-ca.crt create mode 100644 modules/ipa/client.nix create mode 100644 secrets/nix-cache.keytab diff --git a/.sops.yaml b/.sops.yaml index a14898b..462abf5 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -63,6 +63,16 @@ creation_rules: - *lxc-nix-cache - *proxmox-nix-cache + # Host keytab for nix-cache FreeIPA enrollment (binary sops file). + # Generate with: sops -e --input-type binary /tmp/nix-cache.keytab > secrets/nix-cache.keytab + - path_regex: secrets/nix-cache\.keytab$ + key_groups: + - age: + - *admin + - *linode-nix-cache + - *lxc-nix-cache + - *proxmox-nix-cache + - path_regex: secrets/server\.yaml$ key_groups: - age: diff --git a/certs/ipa-ca.crt b/certs/ipa-ca.crt new file mode 100644 index 0000000..1894658 --- /dev/null +++ b/certs/ipa-ca.crt @@ -0,0 +1,9 @@ +# Placeholder — replace with the actual FreeIPA CA certificate before deploying. +# +# Retrieve from the IPA server (it is a public certificate, safe to commit): +# curl -o certs/ipa-ca.crt http:///ipa/config/ca.crt +# +# This file must contain a valid PEM certificate for SSSD to authenticate +# against FreeIPA over TLS. The Nix build succeeds with a placeholder, but +# the deployed host will not be able to join the domain until the real cert +# is committed and the system is rebuilt. diff --git a/hosts/nix-cache/host.nix b/hosts/nix-cache/host.nix index 89f6c55..b9b2c0f 100644 --- a/hosts/nix-cache/host.nix +++ b/hosts/nix-cache/host.nix @@ -6,10 +6,15 @@ name = "nix-cache"; sopsFile = ../../secrets/nix-cache.yaml; }) + (import ../../modules/ipa/client.nix { + keytabSopsFile = ../../secrets/nix-cache.keytab; + caCertFile = ../../certs/ipa-ca.crt; + }) ]; networking = { hostName = vars.nixCacheHost; + domain = vars.homeDomain; useDHCP = false; interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{ address = vars.nixCacheIp; diff --git a/modules/ipa/client.nix b/modules/ipa/client.nix new file mode 100644 index 0000000..bf56ae3 --- /dev/null +++ b/modules/ipa/client.nix @@ -0,0 +1,73 @@ +# 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/nix-cache.keytab; +# caCertFile = ../../certs/ipa-ca.crt; +# }) +# +# One-time operator setup per host (do this BEFORE deploying): +# +# 1. Fetch the IPA CA certificate (public — safe to commit): +# curl -o certs/ipa-ca.crt http:///ipa/config/ca.crt +# Replace the placeholder at certs/ipa-ca.crt and commit it. +# +# 2. On the FreeIPA server, add the host and generate a keytab: +# ipa host-add --ip-address= +# ipa-getkeytab -s -p host/ -k /tmp/.keytab +# +# 3. sops-encrypt the keytab as a binary secret from your admin machine: +# sops -e --input-type binary /tmp/.keytab \ +# > secrets/.keytab +# Add secrets/.keytab to .sops.yaml with the host's age key as a +# recipient (see the nix-cache.keytab entry for the pattern), then run: +# scripts/secrets/sync-host-keys.sh # if not done yet +# sops updatekeys secrets/.keytab +# Commit the encrypted file. +# +# 4. nixos-rebuild switch (or create-proxmox-resource.sh) — no further +# manual enrollment steps required. +# +# vars dependencies: homeDomain, ipaServer + +{ 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; + }; + + # 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" ]; + }; +} diff --git a/secrets/nix-cache.keytab b/secrets/nix-cache.keytab new file mode 100644 index 0000000..e69de29 diff --git a/variables.nix b/variables.nix index ec50ec2..6ebc824 100644 --- a/variables.nix +++ b/variables.nix @@ -17,6 +17,7 @@ dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed) domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS (not NixOS-managed) + ipaServer = "ipa.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP — update if different) # Cross-host references (LAN hostnames/users other hosts reach over the network) nixCacheHost = "nix-cache"; # substituter/remote-builder hostname -- 2.54.0