328 lines
9.9 KiB
Nix
328 lines
9.9 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
let
|
|
cfg = config.fudo.auth.kdc;
|
|
|
|
database-file = "${cfg.state-directory}/principals.db";
|
|
iprop-log = "${cfg.state-directory}/iprop.log";
|
|
acl-file = generate-acl-file cfg.acl;
|
|
kdc-conf =
|
|
generate-kdc-conf cfg.realm database-file cfg.master-key-file acl-file;
|
|
|
|
get-domain-hosts = domain:
|
|
attrNames
|
|
(filterAttrs (host: hostOpts: hostOpts.domain == domain) config.fudo.hosts);
|
|
|
|
get-host-principals = realm: hostname:
|
|
let host = config.fudo.hosts.${hostname};
|
|
in map (service: "${service}/${hostname}.${toLower realm}@${realm}")
|
|
host.kerberos-services;
|
|
|
|
add-principal-str = kdc-conf: principal:
|
|
"${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- add --random-key --use-defaults ${principal}";
|
|
|
|
add-hosts-principals = realm: kdc-conf:
|
|
concatStringsSep "\n" (map (add-principal-str kdc-conf)
|
|
(concatMap (get-host-principals realm)
|
|
(get-domain-hosts (toLower realm))));
|
|
|
|
initialize-db =
|
|
realm: user: group: kdc-conf: key-file: db-name: max-lifetime: max-renewal: primary-keytab: kadmin-keytab: kpasswd-keytab: local-hostname:
|
|
pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
|
if [ ! -e ${key-file} ]; then
|
|
${pkgs.heimdalFull}/bin/kstash --key-file=${key-file} --random-key
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" ${realm}
|
|
${add-hosts-principals realm kdc-conf}
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${primary-keytab} */${local-hostname}@${realm}
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kadmin-keytab} kadmin/admin@${realm}
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} -- ext_keytab --keytab=${kpasswd-keytab} kadmin/changepw@${realm}
|
|
#${pkgs.coreutils}/bin/chown ${user}:${group} ${key-file}
|
|
#${pkgs.coreutils}/bin/chown ${user}:${group} ${db-name}
|
|
#${pkgs.coreutils}/bin/chown ${user}:${group} ${iprop-log}
|
|
#${pkgs.coreutils}/bin/chown ${user}:${group} ${primary-keytab}
|
|
#${pkgs.coreutils}/bin/chown ${user}:${group} ${kadmin-keytab}
|
|
fi
|
|
'';
|
|
|
|
generate-kdc-conf = realm: db-file: key-file: acl-file:
|
|
pkgs.writeText "kdc.conf" ''
|
|
[kdc]
|
|
database = {
|
|
dbname = sqlite:${db-file}
|
|
realm = ${realm}
|
|
mkey_file = ${key-file}
|
|
acl_file = ${acl-file}
|
|
log_file = ${iprop-log}
|
|
}
|
|
|
|
[realms]
|
|
${realm} = {
|
|
enable-http = false
|
|
}
|
|
|
|
[logging]
|
|
kdc = FILE:/var/kerberos/kerberos.log
|
|
default = FILE:/var/kerberos/kerberos.log
|
|
'';
|
|
|
|
aclEntry = { principal, ... }: {
|
|
options = with types; {
|
|
perms = let
|
|
perms = [
|
|
"change-password"
|
|
"add"
|
|
"list"
|
|
"delete"
|
|
"modify"
|
|
"get"
|
|
"get-keys"
|
|
"all"
|
|
];
|
|
in mkOption {
|
|
type = listOf (enum perms);
|
|
description = "List of permissions.";
|
|
default = [ ];
|
|
};
|
|
|
|
target = mkOption {
|
|
type = nullOr str;
|
|
description = "Target principals.";
|
|
default = null;
|
|
example = "hosts/*@REALM.COM";
|
|
};
|
|
};
|
|
};
|
|
|
|
perms-to-permstring = perms: concatStringsSep "," perms;
|
|
|
|
generate-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));
|
|
|
|
kadmin-local = kdc-conf: kadmin-keytab:
|
|
pkgs.writeShellScriptBin "kadmin.local" ''
|
|
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} --keytab=${kadmin-keytab}
|
|
'';
|
|
|
|
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 = { "*/admin" = [ "all" ]; };
|
|
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";
|
|
};
|
|
|
|
master-key-file = mkOption {
|
|
type = str;
|
|
description = "File containing the master key for the realm.";
|
|
default = "/var/kerberos/master.key";
|
|
};
|
|
|
|
primary-keytab = mkOption {
|
|
type = str;
|
|
description = "Location of keytab for kadmind.";
|
|
default = "/var/kerberos/host.keytab";
|
|
};
|
|
|
|
kadmin-keytab = mkOption {
|
|
type = str;
|
|
description = "Location of keytab for kadmind.";
|
|
default = "/var/kerberos/kadmind.keytab";
|
|
};
|
|
|
|
kpasswdd-keytab = mkOption {
|
|
type = str;
|
|
description = "Location of keytab for kpasswdd.";
|
|
default = "/var/kerberos/kpasswdd.keytab";
|
|
};
|
|
|
|
kdc-internal-port = mkOption {
|
|
type = port;
|
|
description =
|
|
"Localhost port on which to listen for KDC traffic. Port 88 will be forwarded";
|
|
default = 4088;
|
|
};
|
|
|
|
# k5login-directory = mkOption {
|
|
# type = str;
|
|
# description =
|
|
# "Directory in which k5login files are stored for local users (equivalent to ~/.k5login).";
|
|
# default = "/var/kerberos/k5login";
|
|
# };
|
|
|
|
max-ticket-lifetime = mkOption {
|
|
type = str;
|
|
description = "Maximum lifetime of a single ticket in this realm.";
|
|
default = "1d";
|
|
};
|
|
|
|
max-ticket-renewal = mkOption {
|
|
type = str;
|
|
description = "Maximum time a ticket may be renewed in this realm.";
|
|
default = "7d";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
users = {
|
|
users.${cfg.user} = {
|
|
isSystemUser = true;
|
|
home = "/var/kerberos";
|
|
group = cfg.group;
|
|
};
|
|
|
|
groups.${cfg.group} = { members = [ cfg.user ]; };
|
|
};
|
|
|
|
krb5 = {
|
|
libdefaults = {
|
|
# Stick to ~/.k5login
|
|
# k5login_directory = cfg.k5login-directory;
|
|
ticket_lifetime = cfg.max-ticket-lifetime;
|
|
renew_lifetime = cfg.max-ticket-renewal;
|
|
};
|
|
realms = { ${cfg.realm} = { enable-http = false; }; };
|
|
extraConfig = ''
|
|
default = FILE:/var/kerberos/kerberos.log
|
|
'';
|
|
};
|
|
|
|
environment = {
|
|
systemPackages =
|
|
[ pkgs.heimdalFull (kadmin-local kdc-conf cfg.kadmin-keytab) ];
|
|
|
|
etc = {
|
|
"krb5.keytab" = {
|
|
user = "root";
|
|
group = "root";
|
|
mode = "0400";
|
|
source = cfg.primary-keytab;
|
|
};
|
|
};
|
|
};
|
|
|
|
fudo.system = {
|
|
ensure-directories = {
|
|
"${cfg.state-directory}" = {
|
|
user = cfg.user;
|
|
group = cfg.group;
|
|
perms = "0740";
|
|
};
|
|
};
|
|
|
|
services = {
|
|
heimdal-kdc = let
|
|
listen-addrs = concatStringsSep " "
|
|
(map (addr: "--addresses=${addr}") cfg.bind-addresses);
|
|
command =
|
|
"${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
|
in {
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" ];
|
|
description =
|
|
"Heimdal Kerberos Key Distribution Center (ticket server).";
|
|
execStart = command;
|
|
user = cfg.user;
|
|
group = cfg.group;
|
|
workingDirectory = cfg.state-directory;
|
|
privateNetwork = false;
|
|
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
|
requiredCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
|
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
|
};
|
|
|
|
heimdal-kdc-init = {
|
|
requires = [ "heimdal-kdc.service" ];
|
|
wantedBy = [ "multi-user.target" ];
|
|
description = "Initialization script for Heimdal KDC.";
|
|
type = "oneshot";
|
|
execStart = "${initialize-db cfg.realm cfg.user cfg.group kdc-conf
|
|
cfg.master-key-file database-file cfg.max-ticket-lifetime
|
|
cfg.max-ticket-renewal cfg.primary-keytab cfg.kadmin-keytab
|
|
cfg.kpasswdd-keytab
|
|
"${config.networking.hostName}.${toLower cfg.realm}"}";
|
|
user = cfg.user;
|
|
group = cfg.group;
|
|
protectSystem = "full";
|
|
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
|
workingDirectory = cfg.state-directory;
|
|
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
|
};
|
|
};
|
|
};
|
|
|
|
# FIXME: is this even allowed to be a link?
|
|
# systemd.tmpfiles.rules = mkIf (cfg.primary-keytab != "/etc/krb5.keytab")
|
|
# [ "L /etc/krb5.keytab - - - - ${cfg.primary-keytab}" ];
|
|
|
|
services.xinetd = {
|
|
enable = true;
|
|
|
|
services = [
|
|
{
|
|
name = "kerberos-adm";
|
|
user = cfg.user;
|
|
server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
|
|
protocol = "tcp";
|
|
serverArgs =
|
|
"--config-file=${kdc-conf} --keytab=${cfg.kadmin-keytab}";
|
|
}
|
|
{
|
|
name = "kpasswd";
|
|
user = cfg.user;
|
|
server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
|
|
protocol = "udp";
|
|
serverArgs =
|
|
"--config-file=${kdc-conf} --keytab=${cfg.kpasswdd-keytab}";
|
|
}
|
|
];
|
|
};
|
|
|
|
networking.firewall = {
|
|
allowedTCPPorts = [ 88 749 ];
|
|
allowedUDPPorts = [ 88 464 ];
|
|
};
|
|
};
|
|
}
|