{ config, pkgs, ... }: with pkgs.lib; let hostname = config.instance.hostname; domain-name = config.fudo.services.auth.domain; domain = config.fudo.domains.${domain-name}; realm = domain.gssapi-realm; zone-name = domain.zone; ldap-server = elem hostname domain.ldap-servers; kerberos-master = hostname == domain.kerberos-master; kerberos-slave = elem hostname domain.kerberos-slaves; kerberized-domain = domain.kerberos-master != null; optionalOrNull = pred: val: if pred then val else null; cfg = config.fudo.services.auth; host-secrets = config.fudo.secrets.host-secrets."${hostname}"; krb-user = config.fudo.auth.kerberos.user; krb-group = config.fudo.auth.kerberos.group; in { options.fudo.services.auth = with types; { domain = mkOption { type = str; description = "Domain for which authentication server will operate."; default = config.fudo.hosts.${hostname}.domain; }; ldap = { hostname = mkOption { type = str; description = "Fully-qualified (and public-addressable) domain name of this host."; default = config.instance.host-fqdn; }; state-directory = mkOption { type = str; description = "Directory at which to store peristent ldap-related data."; }; }; kerberos = { hostname = mkOption { type = str; description = "Fully-qualified (and public-addressable) domain name of this host."; default = config.instance.host-fqdn; }; state-directory = mkOption { type = str; description = "Directory at which to store peristent KDC-related data."; }; master-key-file = mkOption { type = str; description = "Path (on the build server) to the KDC master key file."; }; }; }; config = { systemd = { tmpfiles.rules = mkIf (kerberos-master || kerberos-slave) [ "d ${cfg.kerberos.state-directory} 0700 ${krb-user} ${krb-group} - -" ]; paths.heimdal-kdc-initialize = mkIf kerberos-master { wantedBy = [ "heimdal-kdc.service" ]; pathConfig = { PathModified = host-secrets.kdc-principals.target-file; }; }; services = { heimdal-kdc-initialize = mkIf (kerberos-master || kerberos-slave) (let db = config.fudo.auth.kerberos.kdc.database; principals = host-secrets.kdc-principals.target-file; master-key = host-secrets.realm-master-key.target-file; in { requires = [ host-secrets.kdc-principals.service host-secrets.realm-master-key.service ]; after = [ host-secrets.kdc-principals.service host-secrets.realm-master-key.service ]; description = "Initialize and update the Heimdal KDC database."; path = with pkgs; [ kdcMergePrincipals coreutils ]; serviceConfig = { User = krb-user; Group = krb-group; Type = "oneshot"; ExecStart = let init-db-cmd = concatStringsSep " " [ "${pkgs.kdcMergePrincipals}/bin/kdc-merge-principals" "--create" "--database=${db}" "--principals=${principals}" "--key=${master-key}" "--realm=${realm}" "--verbose" ]; script = pkgs.writeShellScript "heimdal-kdc-initialize.sh" '' chown ${krb-user}:${krb-group} ${db} chmod 0700 ${db} ${init-db-cmd} ''; in "+${script}"; }; unitConfig.ConditionPathExists = [ db principals master-key ]; }); heimdal-kdc = mkIf kerberos-master { requires = [ "heimdal-kdc-initialize.service" ]; after = [ "heimdal-kdc-initialize.service" ]; }; heimdal-kdc-secondary = mkIf kerberos-slave { requires = [ "heimdal-kdc-initialize.service" ]; after = [ "heimdal-kdc-initialize.service" ]; }; }; }; fudo = { acme.host-domains.${hostname} = mkIf (ldap-server) { ${cfg.ldap.hostname}.local-copies.openldap = { user = config.services.openldap.user; part-of = [ config.fudo.auth.ldap-server.systemd-target ]; }; }; secrets.host-secrets."${hostname}" = let realm-key = config.fudo.secrets.files.kerberos.realm-master-keys."${realm}"; in { realm-master-key = mkIf (kerberos-master || kerberos-slave) { source-file = realm-key; target-file = "/run/kdc/realm.key"; user = krb-user; group = krb-group; }; kdc-principals = mkIf (kerberos-master || kerberos-slave) { source-file = config.fudo.secrets.files.kerberos.realm-principals."${realm}"; target-file = "/run/kdc/realm.principals"; user = krb-user; group = krb-group; }; kadmind-keytab = mkIf kerberos-master { source-file = extractFudoKeytab { inherit realm; principals = [ "kadmin/admin" ]; }; target-file = "/run/kdc/kadmind.keytab"; user = krb-user; group = krb-group; }; kpasswdd-keytab = mkIf kerberos-master { source-file = extractFudoKeytab { inherit realm; principals = [ "kadmin/changepw" ]; }; target-file = "/run/kdc/kpasswdd.keytab"; user = krb-user; group = krb-group; }; hprop-keytab = mkIf (kerberos-master && (domain.kerberos-slaves != [ ])) { source-file = extractFudoKeytab { inherit realm; principals = [ "kadmin/hprop" ]; }; target-file = "/run/kdc/hprop.keytab"; user = krb-user; group = krb-group; }; hpropd-keytab = mkIf kerberos-slave { source-file = extractFudoHostKeytab { inherit hostname realm; services = [ "hprop" ]; }; target-file = "/run/kdc/hpropd.keytab"; user = krb-user; group = krb-group; }; }; auth = { ldap-server = mkIf ldap-server (let ldap-cert-copy = config.fudo.acme.host-domains.${hostname}.${cfg.ldap.hostname}.local-copies.openldap; in { enable = ldap-server; base = "dc=fudo,dc=org"; organization = "Fudo"; listen-uris = [ "ldap:///" "ldaps:///" ]; required-services = [ ldap-cert-copy.service ]; # TODO: Maybe filter to Fudo-only? users = config.fudo.users; groups = config.fudo.groups; system-users = config.fudo.system-users; state-directory = "${cfg.ldap.state-directory}"; ssl-chain = ldap-cert-copy.chain; ssl-certificate = ldap-cert-copy.certificate; ssl-private-key = ldap-cert-copy.private-key; ssl-ca-certificate = "${pkgs.letsencrypt-ca}"; }); kerberos = { inherit realm; kdc = mkIf (kerberos-master || kerberos-slave) { state-directory = cfg.kerberos.state-directory; master-key-file = host-secrets.realm-master-key.target-file; primary = mkIf kerberos-master { enable = true; acl = let adminEntries = genAttrs config.instance.local-admins (admin: { perms = [ "add" "change-password" "list" ]; }); in adminEntries // { "*/root".perms = [ "all" ]; }; secondary-servers = map getHostFqdn domain.kerberos-slaves; keytabs = { kadmind = host-secrets.kadmind-keytab.target-file; kpasswdd = host-secrets.kpasswdd-keytab.target-file; hprop = host-secrets.hprop-keytab.target-file; }; }; secondary = mkIf kerberos-slave { enable = true; keytabs.hpropd = host-secrets.hpropd-keytab.target-file; }; }; }; }; zones."${zone-name}" = let make-srv-record = port: hostname: { port = port; host = hostname; }; get-fqdn = host: "${host}.${config.fudo.hosts.${host}.domain}"; kerberos-master-hosts = optional (kerberized-domain) domain.kerberos-master; kerberos-servers = map get-fqdn (kerberos-master-hosts ++ domain.kerberos-slaves); kerberos-masters = map get-fqdn kerberos-master-hosts; ldap-servers = map get-fqdn domain.ldap-servers; in { gssapi-realm = realm; srv-records = { tcp = { kerberos = map (make-srv-record 88) kerberos-servers; kerberos-adm = map (make-srv-record 749) kerberos-masters; ldap = map (make-srv-record 389) ldap-servers; ldaps = map (make-srv-record 636) ldap-servers; }; udp = { kerberos = map (make-srv-record 88) kerberos-servers; kerberos-master = map (make-srv-record 88) kerberos-masters; kpasswd = map (make-srv-record 464) kerberos-masters; }; }; }; }; }; }