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..614814a --- /dev/null +++ b/certs/ipa-ca.crt @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIESDCCArCgAwIBAgIBATANBgkqhkiG9w0BAQsFADA1MRMwEQYDVQQKDApTV0VF +VC5IT01FMR4wHAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMjYwNzI2 +MjExMzQxWhcNNDYwNzI2MjExMzQxWjA1MRMwEQYDVQQKDApTV0VFVC5IT01FMR4w +HAYDVQQDDBVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggGiMA0GCSqGSIb3DQEBAQUA +A4IBjwAwggGKAoIBgQCzljYktbHdMGVJ6Wq0XQJuHLN6dkCSOgtoIzQtriPQkkNI +uo28LwobaiQQ8sX4kGRH/BTKnH8QlId/jug4Uc+sDHnABYu++AiOhPbBX8gCpRQ0 +hebBjZiktHSBUEJR31siWOVdBoKBDJEoxehx7XUXvcxIJcaRN+LHYjO86nJN55HB +VwFU2JcYDk98c+144dFJxXdr++MjWe4Z/oVVU8JHIOtNtKhVhvij6oOSWxcYoJO/ +S80LRj1vx/o6o/3G6bYug7PjY7JjZk/Oj61whijZkcsoO1MXSYI6UywJZGflv+ZB +7HyufdYAsK3WhE8O2FX3/kq64Ol83HNtoR8Dt68rTg1xpW6K45jS6iDPKueYGkb0 +oSx7e++90VAW2PDhj6QQ3JJ4O5VQwrrecekJzUrAean0FOEbmgyi4PsEp1Vk6LDQ +SsIn1x0euyxVivQMlzNX2XrZL3urn1BNPqAdntXQMkR0Wl8sbUiJPe0kxG52CGXs +6yfNEXbPmVGcC0TBdGECAwEAAaNjMGEwHQYDVR0OBBYEFLh5QbI1UWMH0WR4z8bG +lhrOX3X5MB8GA1UdIwQYMBaAFLh5QbI1UWMH0WR4z8bGlhrOX3X5MA8GA1UdEwEB +/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMA0GCSqGSIb3DQEBCwUAA4IBgQCVodVN +owwo53OQe02QhtEbIur2PL7zIfvhvCTRD4J8gwpbMIqT7JQK0tV6Mvsg2L8yTb2O +KjrWeLKHGWaZZlhGSPTbkMFdb/Ls8M9FSnkc2bwcdWW3Z1lOiCjBYYqwLCG6JhvB +5SXVwWNJwXeasL2m7oFTSwhsqPpARJ2t25u2N35o+tqIoCjijKwkmEOT66N9EAbu +2VQjtYZWPkBtP4YCe0Ey6u4oy7sy8ThNAjOylZok+J4JW7QEFjK4Q/emhA4aQq5H +gg9qgMuG+5oi6D1g2Wy+fMTRBaukJtLYZbBpQMQhMYWg44uPp/2bbNPTID/nV1KB +GcPyHaskcVxPdYWxAPMwk3AeJXWyOq7atAPTF5sbk0kQQf2m+vyOqcli5CxRMUgV +rcyi9l6+dZW4U+38Q0ET5M3OuxNI4hA7kVY2cfTakXWNqh97+TIHnstblDhAxECK +6ZLMJQYUy7LqJTX84H27CBWLexEMjXwdr5HCV88Fj6mAK0fRufnIw5FeneA= +-----END CERTIFICATE----- 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..db83042 --- /dev/null +++ b/modules/ipa/client.nix @@ -0,0 +1,75 @@ +# 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 +# (must run from repo root; sops matches creation rules against the file +# path, so copy to secrets/ first and encrypt in-place): +# cp /tmp/.keytab secrets/.keytab +# sops -e --input-type binary -i 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..65aa101 --- /dev/null +++ b/secrets/nix-cache.keytab @@ -0,0 +1,26 @@ +{ + "data": "ENC[AES256_GCM,data:z2bazlADvtWkGcY4an7NgZgIDSDq8KmYpSq8tcBleW/33HlElMNhdmje8m8hd7WxgrVvCQDdlAfodBLj2mjbgSwBptlPX2zbwcmJdm0g0Ope+YyQZucIio7NnBaYa+OK8meWqmxO2WEeOk+hnNfNPbt8MkzF/FMMbP8xv9wSTKP9CDdmO7s8rdg8G1JxVfn9dyaSDUTuA2JGNtbh+osd4CtNCV/bc84MjXDZTgTfT0W/uv8VH3iPNT7BhQbU9FIY4dsFRYBf758VtJ+3mTBiQkR38IevJBvhFB7Wiqn118LSBgjdTKJnBwmGo6lMdo0LRYHEgUnb2ejhWMNie19x+LEAErDki0RBmjc2BbnOF908kUlW2fUPo5fz/hlfqlhDLS1uOxESKhfnKt7s5qzWiMX/nohpknVfyMbmu9j/M571z9sgFnJAylpW3NLp53lX8N9mINwRGXsyL/ijbNE=,iv:M/MNquAGN+lIIyVVvFRgKR6DKAhLclc/OY1HV5Xwoog=,tag:eEuURMErSqsoh+l9ZaHY9Q==,type:str]", + "sops": { + "age": [ + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqZlUxRG0zR3IzMlpDL3Vl\nNmRWQXUvc1NDek5wbWtKVEFTcTlxL3ZoWldNCmJVMkFtbDkxN1FGQ2VLTlhBUW5Q\nZ3VzL0UydUQ2YnlUMmtqa1Z0d0FSVFUKLS0tIGJFelc4Rm84T3ZHSGZDdnNUbUFM\nRXZHV3N3aVlSOXhUZk04UFRMWU5TbFUKhPU80PVYuDFUCxu1CA8+W8bqkr+Ne2fh\n+nBUPJbGxfN9TyD9tUC77AMbcL1R2L5x+SSAh0bEgSWE24/RjsuVKw==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1njap586hc0q43kr03g6c8eqhdsmk8zcafkl3f83xwlc2gqhlmfgs4tmwad" + }, + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3M1o3S2FibHV6N2tIOFEx\nRytJRDFIUU05azNDbWlpaDdFQXFRWlBUNnlFCnBwNVB0SEVwL3NWNS9aeG4xSUI1\nV3NOcUFxUUhaODYyWXdFMEhWeFpvancKLS0tIHY4NHFQRGJwUURQbWZ2cGlSdm9s\nOFJodGZkekk3UnBRRUwxb2YrQ1ZPcU0K0tCapb1hfVHFSNpmESXexYa5k9OE9Tha\n51QpU4mZHKGrnWPK/kyj7rHiz95TLsmwUdA8Q2hOiYNSxEaVBjYDQw==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1jcx3yajjhghn8qh8za3yeu8nxykzlg3p4nrv03vnfvzl0mzayg2qmg940e" + }, + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkTmNrbUFoVFVGcytzQkk1\nMUxDdmJGL0xDcUNZaDFRQnUwNXoyMU90SEVvCi9EaTlVOGlMeUZtQzRJS1J2TTJY\neWdkMGNFMWZzQXVKOU5KcXVvalk1ekkKLS0tIDN2MlRQcG5aQnN3bUMrSDdvc09X\nM29hQ0pJRzk1amRHaS9mRlhrb2FMZG8KEWkSSP+MqGRU75qo7ctOCL7qHhyCM3l4\nL+ga1XOKxRsQkKQPozCi5Bm+k28W9hIDaC34Rw9difxICM9Z1rAbOA==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1ufg390ydrmma849t9xfkxxl5xvdkk6mngnlzhmy7mvuaje8sgcmsmnq6l7" + }, + { + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1TUtxVjJ2STFpU3RNdWpJ\nVjcwUHVORVN6L1pKVC9TeWt4cTNoR2pPWVRBCkR4QVJ1bHFUa2Z5aWw1bG0vQWVy\ncjNGRDl5REZzbzdZWTFMRkU1eHFTc1kKLS0tIHNLVDFiRkRKTHhrSEx2S0J1R3cx\nSkdEQWJya1ptTTRqUjJZT3FFaFhkd0kKgecHrnzW9+Eb+b7c0z1yR+Y0Czsr9Kjh\nx1UUOfleHntJUCs8oYcOogKknEnhBJJoJ5oe/gWruRSwle1fs3cBqQ==\n-----END AGE ENCRYPTED FILE-----\n", + "recipient": "age1jlltcv5jcnm40z5k0q6hv053k2rqpqvemtuecdwn527uw8uqz4es3x7m68" + } + ], + "lastmodified": "2026-07-27T21:22:58Z", + "mac": "ENC[AES256_GCM,data:VG2ygV4X6yMxGlJgq3sN4GAzgVqiD06noyMxB2yb/FCmiFVYZAH/9LqF//G7MTInfIjFrTDyoHAny+m/ztUrJkHXI1Fzg0U8R/zYyQ6wj/GguL0gyhA70uriQhvHsRHefHPa7Km61Blo9cME6CBJWejtRvh1IMw9itAioVrSIZE=,iv:zjxNG6IYxkcNqLk08NADoDiIbS6at86y136SBzadW1U=,tag:sj4ZRR7ixU8h7aOUJmTCYg==,type:str]", + "version": "3.13.2" + } +} diff --git a/variables.nix b/variables.nix index ec50ec2..e438178 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 = "domain-controller.sweet.home"; # FreeIPA server hostname (used by security.ipa and Kerberos; must be a resolvable FQDN, not an IP) # Cross-host references (LAN hostnames/users other hosts reach over the network) nixCacheHost = "nix-cache"; # substituter/remote-builder hostname