at 25.11-pre 7.9 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7with lib; 8let 9 cfg = config.security.ipa; 10 pyBool = x: if x then "True" else "False"; 11 12 ldapConf = pkgs.writeText "ldap.conf" '' 13 # Turning this off breaks GSSAPI used with krb5 when rdns = false 14 SASL_NOCANON on 15 16 URI ldaps://${cfg.server} 17 BASE ${cfg.basedn} 18 TLS_CACERT /etc/ipa/ca.crt 19 ''; 20 nssDb = 21 pkgs.runCommand "ipa-nssdb" 22 { 23 nativeBuildInputs = [ pkgs.nss.tools ]; 24 } 25 '' 26 mkdir -p $out 27 certutil -d $out -N --empty-password 28 certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate} 29 ''; 30in 31{ 32 options = { 33 security.ipa = { 34 enable = mkEnableOption "FreeIPA domain integration"; 35 36 certificate = mkOption { 37 type = types.package; 38 description = '' 39 IPA server CA certificate. 40 41 Use `nix-prefetch-url http://$server/ipa/config/ca.crt` to 42 obtain the file and the hash. 43 ''; 44 example = literalExpression '' 45 pkgs.fetchurl { 46 url = http://ipa.example.com/ipa/config/ca.crt; 47 sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 48 }; 49 ''; 50 }; 51 52 domain = mkOption { 53 type = types.str; 54 example = "example.com"; 55 description = "Domain of the IPA server."; 56 }; 57 58 realm = mkOption { 59 type = types.str; 60 example = "EXAMPLE.COM"; 61 description = "Kerberos realm."; 62 }; 63 64 server = mkOption { 65 type = types.str; 66 example = "ipa.example.com"; 67 description = "IPA Server hostname."; 68 }; 69 70 basedn = mkOption { 71 type = types.str; 72 example = "dc=example,dc=com"; 73 description = "Base DN to use when performing LDAP operations."; 74 }; 75 76 offlinePasswords = mkOption { 77 type = types.bool; 78 default = true; 79 description = "Whether to store offline passwords when the server is down."; 80 }; 81 82 cacheCredentials = mkOption { 83 type = types.bool; 84 default = true; 85 description = "Whether to cache credentials."; 86 }; 87 88 ipaHostname = mkOption { 89 type = types.str; 90 example = "myworkstation.example.com"; 91 default = 92 if config.networking.domain != null then 93 config.networking.fqdn 94 else 95 "${config.networking.hostName}.${cfg.domain}"; 96 defaultText = literalExpression '' 97 if config.networking.domain != null then config.networking.fqdn 98 else "''${networking.hostName}.''${security.ipa.domain}" 99 ''; 100 description = "Fully-qualified hostname used to identify this host in the IPA domain."; 101 }; 102 103 ifpAllowedUids = mkOption { 104 type = types.listOf types.str; 105 default = [ "root" ]; 106 description = "A list of users allowed to access the ifp dbus interface."; 107 }; 108 109 dyndns = { 110 enable = mkOption { 111 type = types.bool; 112 default = true; 113 description = "Whether to enable FreeIPA automatic hostname updates."; 114 }; 115 116 interface = mkOption { 117 type = types.str; 118 example = "eth0"; 119 default = "*"; 120 description = "Network interface to perform hostname updates through."; 121 }; 122 }; 123 124 chromiumSupport = mkOption { 125 type = types.bool; 126 default = true; 127 description = "Whether to whitelist the FreeIPA domain in Chromium."; 128 }; 129 }; 130 }; 131 132 config = mkIf cfg.enable { 133 assertions = [ 134 { 135 assertion = !config.security.krb5.enable; 136 message = "krb5 must be disabled through `security.krb5.enable` for FreeIPA integration to work."; 137 } 138 { 139 assertion = !config.users.ldap.enable; 140 message = "ldap must be disabled through `users.ldap.enable` for FreeIPA integration to work."; 141 } 142 ]; 143 144 environment.systemPackages = with pkgs; [ 145 krb5Full 146 freeipa 147 ]; 148 149 environment.etc = { 150 "ipa/default.conf".text = '' 151 [global] 152 basedn = ${cfg.basedn} 153 realm = ${cfg.realm} 154 domain = ${cfg.domain} 155 server = ${cfg.server} 156 host = ${config.networking.hostName} 157 xmlrpc_uri = https://${cfg.server}/ipa/xml 158 enable_ra = True 159 ''; 160 161 "ipa/nssdb".source = nssDb; 162 163 "krb5.conf".text = '' 164 [libdefaults] 165 default_realm = ${cfg.realm} 166 dns_lookup_realm = false 167 dns_lookup_kdc = true 168 rdns = false 169 ticket_lifetime = 24h 170 forwardable = true 171 udp_preference_limit = 0 172 173 [realms] 174 ${cfg.realm} = { 175 kdc = ${cfg.server}:88 176 master_kdc = ${cfg.server}:88 177 admin_server = ${cfg.server}:749 178 default_domain = ${cfg.domain} 179 pkinit_anchors = FILE:/etc/ipa/ca.crt 180 } 181 182 [domain_realm] 183 .${cfg.domain} = ${cfg.realm} 184 ${cfg.domain} = ${cfg.realm} 185 ${cfg.server} = ${cfg.realm} 186 187 [dbmodules] 188 ${cfg.realm} = { 189 db_library = ${pkgs.freeipa}/lib/krb5/plugins/kdb/ipadb.so 190 } 191 ''; 192 193 "openldap/ldap.conf".source = ldapConf; 194 }; 195 196 environment.etc."chromium/policies/managed/freeipa.json" = mkIf cfg.chromiumSupport { 197 text = '' 198 { "AuthServerWhitelist": "*.${cfg.domain}" } 199 ''; 200 }; 201 202 systemd.services."ipa-activation" = { 203 wantedBy = [ "sysinit.target" ]; 204 before = [ 205 "sysinit.target" 206 "shutdown.target" 207 ]; 208 conflicts = [ "shutdown.target" ]; 209 unitConfig.DefaultDependencies = false; 210 serviceConfig.Type = "oneshot"; 211 serviceConfig.RemainAfterExit = true; 212 script = '' 213 # libcurl requires a hard copy of the certificate 214 if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then 215 rm -f /etc/ipa/ca.crt 216 cp ${cfg.certificate} /etc/ipa/ca.crt 217 fi 218 219 if [ ! -f /etc/krb5.keytab ]; then 220 cat <<EOF 221 222 In order to complete FreeIPA integration, please join the domain by completing the following steps: 223 1. Authenticate as an IPA user authorized to join new hosts, e.g. kinit admin@${cfg.realm} 224 2. Join the domain and obtain the keytab file: ipa-join 225 3. Install the keytab file: sudo install -m 600 krb5.keytab /etc/ 226 4. Restart sssd systemd service: sudo systemctl restart sssd 227 228 EOF 229 fi 230 ''; 231 }; 232 233 services.sssd.config = '' 234 [domain/${cfg.domain}] 235 id_provider = ipa 236 auth_provider = ipa 237 access_provider = ipa 238 chpass_provider = ipa 239 240 ipa_domain = ${cfg.domain} 241 ipa_server = _srv_, ${cfg.server} 242 ipa_hostname = ${cfg.ipaHostname} 243 244 cache_credentials = ${pyBool cfg.cacheCredentials} 245 krb5_store_password_if_offline = ${pyBool cfg.offlinePasswords} 246 ${optionalString ((toLower cfg.domain) != (toLower cfg.realm)) "krb5_realm = ${cfg.realm}"} 247 248 dyndns_update = ${pyBool cfg.dyndns.enable} 249 dyndns_iface = ${cfg.dyndns.interface} 250 251 ldap_tls_cacert = /etc/ipa/ca.crt 252 ldap_user_extra_attrs = mail:mail, sn:sn, givenname:givenname, telephoneNumber:telephoneNumber, lock:nsaccountlock 253 254 [sssd] 255 services = nss, sudo, pam, ssh, ifp 256 domains = ${cfg.domain} 257 258 [nss] 259 homedir_substring = /home 260 261 [pam] 262 pam_pwd_expiration_warning = 3 263 pam_verbosity = 3 264 265 [sudo] 266 267 [autofs] 268 269 [ssh] 270 271 [pac] 272 273 [ifp] 274 user_attributes = +mail, +telephoneNumber, +givenname, +sn, +lock 275 allowed_uids = ${concatStringsSep ", " cfg.ifpAllowedUids} 276 ''; 277 278 services.ntp.servers = singleton cfg.server; 279 services.sssd.enable = true; 280 services.ntp.enable = true; 281 282 security.pki.certificateFiles = singleton cfg.certificate; 283 }; 284}