nixos-config/lib/fudo/kdc.nix
2021-03-14 00:22:23 +00:00

293 lines
8.2 KiB
Nix

{ 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}";
};
};
};
};
}