{ config, lib, pkgs, ... }: with lib; let cfg = config.fudo.auth.kdc; get-domain-hosts = domain: mapAttrsToList (host: hostOpts: "${host}.${domain}") (filterAttrs (host: hostOpts: hostOpts.domain == domain) config.fudo.hosts); add-host-principals = realm: host: '' ${pkgs.kerberos}/bin/kadmin.local addprinc -randkey host/${host} -r ${realm} ${pkgs.kerberos}/bin/kadmin.local addprinc -randkey ssh/${host} -r ${realm} ''; initialize-db = realm: user: group: key-file: db-file: let domain = toLower realm; hosts = get-domain-hosts domain; in pkgs.writeShellScript "initialize-kdc-db.sh" '' if [ ! -e ${db-file} ]; then PWD=$(${pkgs.pwgen}/bin/pwgen -n1 -y 40) ${pkgs.krb5}/bin/kdb5_util -r ${realm} -sf ${key-file} -d ${db-file} -P $PWD -m create -s ${pkgs.coreutils}/bin/chown -R ${user}:${group} $(dirname ${db-file}) ${concatStringsSep "\n" (map (add-host-principals realm) hosts)} fi ''; initialize-kadmin = realm: user: group: kadmin-keytab: host: let domain = toLower realm; in pkgs.writeShellScript "initialize-kadmin.sh" '' if [ ! -e ${kadmin-keytab} ]; then ${pkgs.krb5}/bin/kadmin.local addprinc -randkey kadmin/${host}.${domain} ${pkgs.krb5}/bin/kadmin.local ktadd -k ${kadmin-keytab} kadmin/${host}.${domain} # TODO: extract kadmin keytab fi ''; generate-kdc-conf = realm: database: kdc-listen-ips: kadmind-port: acl-file: kadmin-keytab: key-stash-file: pkgs.writeText "kdc.conf" '' [kdcdefaults] kdc_listen = ${concatStringsSep "," kdc-listen-ips} kdc_tcp_listen = ${concatStringsSep "," kdc-listen-ips} [realm] ${realm} = { kadmind_port = ${toString kadmind-port} max_life = 24h 0m 0s max_renewable_life = 14d 0h 0m 0s acl_file = ${acl-file} admin_keytab = ${kadmin-keytab} key_stash_file = ${key-stash-file} } [dbmodules] ${realm} = { database_name = ${database} db_library = db2 } ''; perm-map = { "add" = "a"; "all" = "x"; "password" = "c"; "delete" = "d"; "inquire" = "i"; "list" = "l"; "modify" = "m"; "propagate" = "p"; "set-key" = "s"; }; perms-to-permstring = perms: concatStringsSep "" (map (perm: perm-map.${perm}) perms); aclEntry = { principal, ... }: { options = with types; { perms = mkOption { type = listOf (enum (attrNames perm-map)); description = "List of permissions."; default = [ ]; }; target = mkOption { type = nullOr str; description = "Target principals."; default = null; example = "hosts/*@REALM.COM"; }; }; }; kdc-acl-file = acl-entries: pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList (principal: opts: "${principal} ${perms-to-permstring opts.perms}${ optionalString (opts.target != null) " ${opts.target}" }") acl-entries)); in { options.fudo.auth.kdc = with types; { enable = mkEnableOption "Fudo KDC"; realm = mkOption { type = str; description = "The realm for which we are the acting KDC."; }; acl = mkOption { type = attrsOf (submodule aclEntry); description = "Mapping of pricipals to a list of permissions."; default = { }; example = { "*/root" = [ "all" ]; "admin-user" = [ "add" "list" "modify" ]; }; }; bind-addresses = mkOption { type = listOf str; description = "A list of IP addresses on which to bind."; default = [ ]; }; user = mkOption { type = str; description = "User as which to run Heimdal servers."; default = "kerberos"; }; group = mkOption { type = str; description = "Group as which to run Heimdal servers."; default = "kerberos"; }; state-directory = mkOption { type = str; description = "Path at which to store kerberos database."; default = "/var/kerberos"; }; kdc-pid-file = mkOption { type = str; description = "PID file for the KDC server."; default = "/var/run/kerberos-kdc.pid"; }; kadmind-pid-file = mkOption { type = str; description = "PID file for the Kerberos admin server."; default = "/var/run/kerberos-kadmin.pid"; }; kadmind-internal-port = mkOption { type = port; description = "Local port on which to run kadmind."; default = 7749; }; kdc-internal-port = mkOption { type = port; description = "Local port on which to run kdc."; default = 7088; }; master-key-file = mkOption { type = str; description = "File containing the master key for the realm."; default = "/var/kerberos/master.key"; }; kadmin-keytab = mkOption { type = str; description = "Location of keytab for kadmind."; default = "/var/kerberos/kadmind.keytab"; }; }; config = mkIf cfg.enable { users = { users.${cfg.user} = { isSystemUser = true; home = "/var/heimdal"; group = cfg.group; }; groups.${cfg.group} = { members = [ cfg.user ]; }; }; krb5.libdefaults = { default_realm = mkForce cfg.realm; }; environment = { systemPackages = [ pkgs.kerberos ]; }; # services.xinitd = { # enable = true; # services = [ # { # name = "kdc"; # unlisted = true; # port = 88; # server = "/usr/bin/env"; # extraConfig = "redirect = localhost ${cfg.kdc-internal-port}"; # } # { # name = "kadmin"; # unlisted = true; # port = 749; # server = "/usr/bin/env"; # extraConfig = "redirect = localhost ${cfg.kadmin-internal-port}"; # } # ]; # }; fudo.system = { ensure-directories = { "${cfg.state-directory}" = { user = cfg.user; group = cfg.group; perms = "0740"; }; }; internal-port-map = { kdc = { internal-port = cfg.kdc-internal-port; external-port = 88; }; kadmin = { internal-port = cfg.kadmind-internal-port; external-port = 749; }; }; services = let kerberos-database = "${cfg.state-directory}/kerberos.db"; acl-file = kdc-acl-file cfg.acl; kdc-listen-addrs = map (ip: "${ip}:${toString cfg.kdc-internal-port}") [ "127.0.0.1" "::1" ]; kdc-conf = generate-kdc-conf cfg.realm kerberos-database kdc-listen-addrs cfg.kadmind-internal-port acl-file cfg.kadmin-keytab cfg.master-key-file; in { mit-kdc = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; type = "forking"; description = "MIT Kerberos Key Distribution Center (ticket server)."; execStart = "${pkgs.krb5}/bin/krb5kdc -r ${cfg.realm} -d ${kerberos-database} -P ${cfg.kdc-pid-file} -M ${cfg.master-key-file}"; environment = { KRB5_CONFIG = "/etc/krb5.conf"; KRB5_KDC_PROFILE = "${kdc-conf}"; }; user = cfg.user; group = cfg.group; workingDirectory = cfg.state-directory; preStart = "${initialize-db cfg.realm cfg.user cfg.group cfg.master-key-file kerberos-database}"; }; mit-kadmin = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; requires = [ "mit-kdc.service" ]; description = "MIT Kerberos Remote Administration Server."; execStart = "${pkgs.kerberos}/bin/kadmind -r ${cfg.realm} -P ${cfg.kadmind-pid-file}"; environment = { KRB5_CONFIG = "/etc/krb5.conf"; KRB5_KDC_PROFILE = "${kdc-conf}"; }; user = cfg.user; group = cfg.group; workingDirectory = cfg.state-directory; preStart = "${initialize-kadmin cfg.realm cfg.user cfg.group cfg.kadmin-keytab config.networking.hostName}"; }; }; }; }; }