From 9f7ab64d09a3db134daa6dad1d127af966144974 Mon Sep 17 00:00:00 2001 From: Niten Date: Sun, 14 Mar 2021 00:22:23 +0000 Subject: [PATCH] A bunch of changes... --- config/hosts/clunk.nix | 7 +- config/profiles/common.nix | 12 ++ lib/fudo/kdc.nix | 258 +++++++++++++++++++++++++++++-------- lib/fudo/system.nix | 64 +++++++++ 4 files changed, 287 insertions(+), 54 deletions(-) diff --git a/config/hosts/clunk.nix b/config/hosts/clunk.nix index 2e4fabc..3be32ea 100644 --- a/config/hosts/clunk.nix +++ b/config/hosts/clunk.nix @@ -88,9 +88,12 @@ in { auth.kdc = { enable = true; - realm = "SELBY.CA"; - acl-file = "/var/heimdal/access.acl"; + realm = "RUS.SELBY.CA"; bind-addresses = [ "10.0.0.1" "127.0.0.1" "::1" ]; + acl = { + "niten" = { perms = [ "all" ]; }; + "*/root" = { perms = [ "password" "list" ]; }; + }; }; secure-dns-proxy = { diff --git a/config/profiles/common.nix b/config/profiles/common.nix index d09767f..b3aec76 100644 --- a/config/profiles/common.nix +++ b/config/profiles/common.nix @@ -22,6 +22,18 @@ in { system.autoUpgrade.enable = true; + krb5 = { + libdefaults = { + allow_weak_crypto = false; + dns_lookup_kdc = true; + forwardable = true; + ignore_acceptor_hostname = true; + noaddresses = true; + rdns = false; + proxiable = true; + }; + }; + services = { openssh = { enable = true; diff --git a/lib/fudo/kdc.nix b/lib/fudo/kdc.nix index c1b59da..b387ae3 100644 --- a/lib/fudo/kdc.nix +++ b/lib/fudo/kdc.nix @@ -4,13 +4,101 @@ with lib; let cfg = config.fudo.auth.kdc; - initialize-db = realm: key-file: - pkgs.writeShellScript "initialize-heimdal-db.sh" '' - if [ ! -e ${key-file} ]; then - ${pkgs.heimdalFull}/bin/kadmin -l init --realm=${realm} --realm-max-ticket-life=1d --realm-max-renewable-life=2w ${realm} + 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; { @@ -21,21 +109,14 @@ in { description = "The realm for which we are the acting KDC."; }; - config-file = mkOption { - type = str; - description = "Path to configuartion file."; - default = "/etc/krb5.conf"; - }; - - master-key-file = mkOption { - type = str; - description = "The path to the master key file."; - default = "/var/heimdal/master.key"; - }; - - acl-file = mkOption { - type = str; - description = "The path to the Access Control file."; + 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 { @@ -59,7 +140,43 @@ in { state-directory = mkOption { type = str; description = "Path at which to store kerberos database."; - default = "/srv/kerberos"; + 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"; }; }; @@ -74,63 +191,100 @@ in { groups.${cfg.group} = { members = [ cfg.user ]; }; }; - environment = { - systemPackages = [ pkgs.heimdalFull ]; + krb5.libdefaults = { default_realm = mkForce cfg.realm; }; - etc."krb5.conf" = { - text = mkAfter '' - [kdc] - database = { - realm = ${cfg.realm} - mkey_file = ${cfg.master-key-file} - acl_file = ${cfg.acl-file} - } - addresses = ${concatStringsSep " " cfg.bind-addresses} + environment = { systemPackages = [ pkgs.kerberos ]; }; - # Binds to port 80! - enable-http = false - ''; - }; - }; - - systemd = { - tmpfiles.rules = [ "L /var/heimdal - - - - ${cfg.state-directory}" ]; - }; + # 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"; }; }; - services = { - heimdal-kdc = { + 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" ]; - description = - "Heimdal Kerberos Key Distribution Center (ticket server)."; + type = "forking"; + description = "MIT Kerberos Key Distribution Center (ticket server)."; execStart = - "${pkgs.heimdalFull}/libexec/heimdal/kdc --config-file=${cfg.config-file}"; - privateNetwork = false; + "${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.master-key-file}"; + preStart = + "${initialize-db cfg.realm cfg.user cfg.group cfg.master-key-file + kerberos-database}"; }; - heimdal-admin-server = { + mit-kadmin = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - description = "Heimdal Kerberos Remote Administration Server."; + requires = [ "mit-kdc.service" ]; + description = "MIT Kerberos Remote Administration Server."; execStart = - "${pkgs.heimdalFull}/libexec/heimdal/kadmind --config-file=${cfg.config-file} --key-file=${cfg.master-key-file}"; - privateNetwork = false; + "${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-db cfg.realm cfg.master-key-file}"; + preStart = + "${initialize-kadmin cfg.realm cfg.user cfg.group cfg.kadmin-keytab + config.networking.hostName}"; }; }; }; diff --git a/lib/fudo/system.nix b/lib/fudo/system.nix index 0856e1a..76d71a9 100644 --- a/lib/fudo/system.nix +++ b/lib/fudo/system.nix @@ -332,6 +332,25 @@ let }; }; + portMappingOpts = { name, ... }: { + options = with types; { + internal-port = mkOption { + type = port; + description = "Port on localhost to recieve traffic"; + }; + external-port = mkOption { + type = port; + description = "External port on which to listen for traffic."; + }; + protocol = mkOption { + type = nullOr str; + description = + "Protocol for which to forward ports. Default is tcp & udp."; + default = null; + }; + }; + }; + in { options.fudo.system = with types; { services = mkOption { @@ -351,6 +370,20 @@ in { description = "A map of required directories to directory properties."; default = { }; }; + + internal-port-map = mkOption { + type = attrsOf (submodule portMappingOpts); + description = + "Sets of external ports to internal (i.e. localhost) ports to forward."; + default = { }; + example = { + sshmap = { + internal-port = 2222; + external-port = 22; + protocol = "udp"; + }; + }; + }; }; config = { @@ -361,6 +394,37 @@ in { # }; # }) (filterAttrs (name: opts: opts.networkWhitelist != null) cfg.services); + boot.kernel.sysctl = mkIf (cfg.internal-port-map != { }) { + "net.ipv4.conf.all.route_localhost" = "1"; + }; + + networking.firewall = let + ip-forward-line = protocols: internal: external: + concatStringsSep "\n" (map (protocol: + "${pkgs.iptables}/bin/iptables -t nat -I PREROUTING -p ${protocol} --dport ${ + toString external + } -j DNAT --to 127.0.0.1:${toString internal}") protocols); + + ip-unforward-line = protocols: internal: external: + concatStringsSep "\n" (map (protocol: + "${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -p ${protocol} --dport ${ + toString external + } -j DNAT --to 127.0.0.1:${toString internal} || true") protocols); + + protocol-list = protocol: + if (protocol == null) then [ "tcp" "udp" ] else [ protocol ]; + in { + extraCommands = mkAfter (concatStringsSep "\n" (mapAttrsToList + (name: opts: + ip-forward-line (protocol-list opts.protocol) opts.internal-port + opts.external-port) cfg.internal-port-map)); + + extraStopCommands = mkAfter (concatStringsSep "\n" (mapAttrsToList + (name: opts: + ip-unforward-line (protocol-list opts.protocol) opts.internal-port + opts.external-port) cfg.internal-port-map)); + }; + systemd.timers = mapAttrs (name: opts: { enable = true; description = opts.description;