diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index e1e30c99a447..43d951e42b03 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -277,6 +277,7 @@ ./security/doas.nix ./security/duosec.nix ./security/google_oslogin.nix + ./security/ipa.nix ./security/lock-kernel-modules.nix ./security/misc.nix ./security/oath.nix diff --git a/nixos/modules/security/ipa.nix b/nixos/modules/security/ipa.nix new file mode 100644 index 000000000000..7075be95040e --- /dev/null +++ b/nixos/modules/security/ipa.nix @@ -0,0 +1,258 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.security.ipa; + pyBool = x: + if x + then "True" + else "False"; + + ldapConf = pkgs.writeText "ldap.conf" '' + # Turning this off breaks GSSAPI used with krb5 when rdns = false + SASL_NOCANON on + + URI ldaps://${cfg.server} + BASE ${cfg.basedn} + TLS_CACERT /etc/ipa/ca.crt + ''; + nssDb = + pkgs.runCommand "ipa-nssdb" + { + nativeBuildInputs = [pkgs.nss.tools]; + } '' + mkdir -p $out + certutil -d $out -N --empty-password + certutil -d $out -A --empty-password -n "${cfg.realm} IPA CA" -t CT,C,C -i ${cfg.certificate} + ''; +in { + options = { + security.ipa = { + enable = mkEnableOption (lib.mdDoc "FreeIPA domain integration"); + + certificate = mkOption { + type = types.package; + description = lib.mdDoc '' + IPA server CA certificate. + + Use `nix-prefetch-url http://$server/ipa/config/ca.crt` to + obtain the file and the hash. + ''; + example = literalExpression '' + pkgs.fetchurl { + url = http://ipa.example.com/ipa/config/ca.crt; + sha256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + }; + ''; + }; + + domain = mkOption { + type = types.str; + example = "example.com"; + description = lib.mdDoc "Domain of the IPA server."; + }; + + realm = mkOption { + type = types.str; + example = "EXAMPLE.COM"; + description = lib.mdDoc "Kerberos realm."; + }; + + server = mkOption { + type = types.str; + example = "ipa.example.com"; + description = lib.mdDoc "IPA Server hostname."; + }; + + basedn = mkOption { + type = types.str; + example = "dc=example,dc=com"; + description = lib.mdDoc "Base DN to use when performing LDAP operations."; + }; + + offlinePasswords = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to store offline passwords when the server is down."; + }; + + cacheCredentials = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to cache credentials."; + }; + + ifpAllowedUids = mkOption { + type = types.listOf types.string; + default = ["root"]; + description = lib.mdDoc "A list of users allowed to access the ifp dbus interface."; + }; + + dyndns = { + enable = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to enable FreeIPA automatic hostname updates."; + }; + + interface = mkOption { + type = types.str; + example = "eth0"; + default = "*"; + description = lib.mdDoc "Network interface to perform hostname updates through."; + }; + }; + + chromiumSupport = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Whether to whitelist the FreeIPA domain in Chromium."; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = !config.krb5.enable; + message = "krb5 must be disabled through `krb5.enable` for FreeIPA integration to work."; + } + { + assertion = !config.users.ldap.enable; + message = "ldap must be disabled through `users.ldap.enable` for FreeIPA integration to work."; + } + ]; + + environment.systemPackages = with pkgs; [krb5Full freeipa]; + + environment.etc = { + "ipa/default.conf".text = '' + [global] + basedn = ${cfg.basedn} + realm = ${cfg.realm} + domain = ${cfg.domain} + server = ${cfg.server} + host = ${config.networking.hostName} + xmlrpc_uri = https://${cfg.server}/ipa/xml + enable_ra = True + ''; + + "ipa/nssdb".source = nssDb; + + "krb5.conf".text = '' + [libdefaults] + default_realm = ${cfg.realm} + dns_lookup_realm = false + dns_lookup_kdc = true + rdns = false + ticket_lifetime = 24h + forwardable = true + udp_preference_limit = 0 + + [realms] + ${cfg.realm} = { + kdc = ${cfg.server}:88 + master_kdc = ${cfg.server}:88 + admin_server = ${cfg.server}:749 + default_domain = ${cfg.domain} + pkinit_anchors = FILE:/etc/ipa/ca.crt + } + + [domain_realm] + .${cfg.domain} = ${cfg.realm} + ${cfg.domain} = ${cfg.realm} + ${cfg.server} = ${cfg.realm} + + [dbmodules] + ${cfg.realm} = { + db_library = ${pkgs.freeipa}/lib/krb5/plugins/kdb/ipadb.so + } + ''; + + "openldap/ldap.conf".source = ldapConf; + }; + + environment.etc."chromium/policies/managed/freeipa.json" = mkIf cfg.chromiumSupport { + text = '' + { "AuthServerWhitelist": "*.${cfg.domain}" } + ''; + }; + + system.activationScripts.ipa = stringAfter ["etc"] '' + # libcurl requires a hard copy of the certificate + if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then + rm -f /etc/ipa/ca.crt + cp ${cfg.certificate} /etc/ipa/ca.crt + fi + + if [ ! -f /etc/krb5.keytab ]; then + cat <