Various updates.
This commit is contained in:
parent
070a5e1831
commit
b9def9eba9
230
lib/fudo/kdc.nix
230
lib/fudo/kdc.nix
@ -1,4 +1,4 @@
|
||||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
{ config, lib, pkgs, ... }@toplevel:
|
||||
|
||||
with lib;
|
||||
let
|
||||
@ -9,12 +9,10 @@ let
|
||||
localhost-ips = let
|
||||
addr-only = addrinfo: addrinfo.address;
|
||||
interface = config.networking.interfaces.lo;
|
||||
in
|
||||
(map addr-only interface.ipv4.addresses) ++
|
||||
(map addr-only interface.ipv6.addresses);
|
||||
in (map addr-only interface.ipv4.addresses)
|
||||
++ (map addr-only interface.ipv6.addresses);
|
||||
|
||||
host-ips =
|
||||
(pkgs.lib.fudo.network.host-ips hostname) ++ localhost-ips;
|
||||
host-ips = (pkgs.lib.fudo.network.host-ips hostname) ++ localhost-ips;
|
||||
|
||||
state-directory = toplevel.config.fudo.auth.kdc.state-directory;
|
||||
|
||||
@ -24,8 +22,7 @@ let
|
||||
master-server = cfg.master-config != null;
|
||||
slave-server = cfg.slave-config != null;
|
||||
|
||||
get-fqdn = hostname:
|
||||
"${hostname}.${config.fudo.hosts.${hostname}.domain}";
|
||||
get-fqdn = hostname: "${hostname}.${config.fudo.hosts.${hostname}.domain}";
|
||||
|
||||
kdc-conf = generate-kdc-conf {
|
||||
realm = cfg.realm;
|
||||
@ -34,102 +31,103 @@ let
|
||||
acl-data = if master-server then cfg.master-config.acl else null;
|
||||
};
|
||||
|
||||
initialize-db =
|
||||
{ realm, user, group, kdc-conf, key-file, db-name, max-lifetime, max-renewal,
|
||||
primary-keytab, kadmin-keytab, kpasswd-keytab, ipropd-keytab, local-hostname }: let
|
||||
initialize-db = { realm, user, group, kdc-conf, key-file, db-name
|
||||
, max-lifetime, max-renewal, primary-keytab, kadmin-keytab, kpasswd-keytab
|
||||
, ipropd-keytab, local-hostname }:
|
||||
let
|
||||
|
||||
kadmin-cmd = "kadmin -l -c ${kdc-conf} --";
|
||||
kadmin-cmd = "kadmin -l -c ${kdc-conf} --";
|
||||
|
||||
get-domain-hosts = domain: let
|
||||
get-domain-hosts = domain:
|
||||
let
|
||||
host-in-subdomain = host: hostOpts:
|
||||
(builtins.match "(.+[.])?${domain}$" hostOpts.domain) != null;
|
||||
in attrNames (filterAttrs host-in-subdomain config.fudo.hosts);
|
||||
|
||||
get-host-principals = realm: hostname: let
|
||||
host = config.fudo.hosts.${hostname};
|
||||
get-host-principals = realm: hostname:
|
||||
let host = config.fudo.hosts.${hostname};
|
||||
in map (service: "${service}/${hostname}.${host.domain}@${realm}")
|
||||
host.kerberos-services;
|
||||
host.kerberos-services;
|
||||
|
||||
add-principal-str = principal:
|
||||
"${kadmin-cmd} add --random-key --use-defaults ${principal}";
|
||||
add-principal-str = principal:
|
||||
"${kadmin-cmd} add --random-key --use-defaults ${principal}";
|
||||
|
||||
test-existence = principal:
|
||||
"[[ $( ${kadmin-cmd} get ${principal} ) ]]";
|
||||
test-existence = principal: "[[ $( ${kadmin-cmd} get ${principal} ) ]]";
|
||||
|
||||
exists-or-add = principal: ''
|
||||
if ${test-existence principal}; then
|
||||
echo "skipping ${principal}, already exists"
|
||||
else
|
||||
${add-principal-str principal}
|
||||
fi
|
||||
'';
|
||||
exists-or-add = principal: ''
|
||||
if ${test-existence principal}; then
|
||||
echo "skipping ${principal}, already exists"
|
||||
else
|
||||
${add-principal-str principal}
|
||||
fi
|
||||
'';
|
||||
|
||||
ensure-host-principals = realm:
|
||||
concatStringsSep "\n"
|
||||
(map exists-or-add
|
||||
(concatMap (get-host-principals realm)
|
||||
(get-domain-hosts (toLower realm))));
|
||||
ensure-host-principals = realm:
|
||||
concatStringsSep "\n" (map exists-or-add
|
||||
(concatMap (get-host-principals realm)
|
||||
(get-domain-hosts (toLower realm))));
|
||||
|
||||
slave-hostnames = map get-fqdn cfg.master-config.slave-hosts;
|
||||
slave-hostnames = map get-fqdn cfg.master-config.slave-hosts;
|
||||
|
||||
ensure-iprop-principals = concatStringsSep "\n"
|
||||
(map (host: exists-or-add "iprop/${host}@${realm}")
|
||||
[ local-hostname ] ++ slave-hostnames);
|
||||
ensure-iprop-principals = concatStringsSep "\n"
|
||||
(map (host: exists-or-add "iprop/${host}@${realm}") [ local-hostname ]
|
||||
++ slave-hostnames);
|
||||
|
||||
copy-slave-principals-file = let
|
||||
slave-principals = map
|
||||
(host: "iprop/${hostname}@${cfg.realm}")
|
||||
slave-hostnames;
|
||||
slave-principals-file = pkgs.writeText "heimdal-slave-principals"
|
||||
(concatStringsSep "\n" slave-principals);
|
||||
in optionalString (slave-principals-file != null) ''
|
||||
cp ${slave-principals-file} ${state-directory}/slaves
|
||||
# Since it's copied from /nix/store, this is by default read-only,
|
||||
# which causes updates to fail.
|
||||
chmod u+w ${state-directory}/slaves
|
||||
'';
|
||||
copy-slave-principals-file = let
|
||||
slave-principals =
|
||||
map (host: "iprop/${hostname}@${cfg.realm}") slave-hostnames;
|
||||
slave-principals-file = pkgs.writeText "heimdal-slave-principals"
|
||||
(concatStringsSep "\n" slave-principals);
|
||||
in optionalString (slave-principals-file != null) ''
|
||||
cp ${slave-principals-file} ${state-directory}/slaves
|
||||
# Since it's copied from /nix/store, this is by default read-only,
|
||||
# which causes updates to fail.
|
||||
chmod u+w ${state-directory}/slaves
|
||||
'';
|
||||
|
||||
in pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
||||
TMP=$(mktemp -d -t kdc-XXXXXXXX)
|
||||
if [ ! -e ${database-file} ]; then
|
||||
## CHANGING HOW THIS WORKS
|
||||
## Now we expect the key to be provided
|
||||
# kstash --key-file=${key-file} --random-key
|
||||
${kadmin-cmd} init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" ${realm}
|
||||
fi
|
||||
in pkgs.writeShellScript "initialize-kdc-db.sh" ''
|
||||
TMP=$(mktemp -d -t kdc-XXXXXXXX)
|
||||
if [ ! -e ${database-file} ]; then
|
||||
## CHANGING HOW THIS WORKS
|
||||
## Now we expect the key to be provided
|
||||
# kstash --key-file=${key-file} --random-key
|
||||
${kadmin-cmd} init --realm-max-ticket-life="${max-lifetime}" --realm-max-renewable-life="${max-renewal}" ${realm}
|
||||
fi
|
||||
|
||||
${ensure-host-principals realm}
|
||||
${ensure-host-principals realm}
|
||||
|
||||
${ensure-iprop-principals}
|
||||
${ensure-iprop-principals}
|
||||
|
||||
echo "*** BEGIN EXTRACTING KEYTABS"
|
||||
echo "*** You can probably ignore the 'principal does not exist' errors that follow,"
|
||||
echo "*** they're just testing for principal existence before creating those that"
|
||||
echo "*** don't already exist"
|
||||
echo "*** BEGIN EXTRACTING KEYTABS"
|
||||
echo "*** You can probably ignore the 'principal does not exist' errors that follow,"
|
||||
echo "*** they're just testing for principal existence before creating those that"
|
||||
echo "*** don't already exist"
|
||||
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/primary.keytab */${local-hostname}@${realm}
|
||||
mv $TMP/primary.keytab ${primary-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kadmin.keytab kadmin/admin@${realm}
|
||||
mv $TMP/kadmin.keytab ${kadmin-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kpasswd.keytab kadmin/changepw@${realm}
|
||||
mv $TMP/kpasswd.keytab ${kpasswd-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/ipropd.keytab iprop/${local-hostname}@${realm}
|
||||
mv $TMP/ipropd.keytab ${ipropd-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/primary.keytab */${local-hostname}@${realm}
|
||||
mv $TMP/primary.keytab ${primary-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kadmin.keytab kadmin/admin@${realm}
|
||||
mv $TMP/kadmin.keytab ${kadmin-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/kpasswd.keytab kadmin/changepw@${realm}
|
||||
mv $TMP/kpasswd.keytab ${kpasswd-keytab}
|
||||
${kadmin-cmd} ext_keytab --keytab=$TMP/ipropd.keytab iprop/${local-hostname}@${realm}
|
||||
mv $TMP/ipropd.keytab ${ipropd-keytab}
|
||||
|
||||
echo "*** END EXTRACTING KEYTABS"
|
||||
echo "*** END EXTRACTING KEYTABS"
|
||||
|
||||
${copy-slave-principals-file}
|
||||
'';
|
||||
${copy-slave-principals-file}
|
||||
'';
|
||||
|
||||
generate-kdc-conf = { realm, db-file, key-file, acl-data }:
|
||||
generate-kdc-conf = { realm, db-file, key-file, acl-data }:
|
||||
pkgs.writeText "kdc.conf" ''
|
||||
[kdc]
|
||||
database = {
|
||||
dbname = sqlite:${db-file}
|
||||
realm = ${realm}
|
||||
mkey_file = ${key-file}
|
||||
${optionalString (acl-data != null)
|
||||
"acl_file = ${generate-acl-file acl-data}"}
|
||||
${
|
||||
optionalString (acl-data != null)
|
||||
"acl_file = ${generate-acl-file acl-data}"
|
||||
}
|
||||
log_file = ${iprop-log}
|
||||
}
|
||||
|
||||
@ -171,18 +169,17 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
generate-acl-file = acl-entries: let
|
||||
perms-to-permstring = perms: concatStringsSep "," perms;
|
||||
in
|
||||
pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
|
||||
generate-acl-file = acl-entries:
|
||||
let perms-to-permstring = perms: concatStringsSep "," perms;
|
||||
in pkgs.writeText "kdc.acl" (concatStringsSep "\n" (mapAttrsToList
|
||||
(principal: opts:
|
||||
"${principal} ${perms-to-permstring opts.perms}${
|
||||
optionalString (opts.target != null) " ${opts.target}" }")
|
||||
acl-entries));
|
||||
optionalString (opts.target != null) " ${opts.target}"
|
||||
}") acl-entries));
|
||||
|
||||
kadmin-local = kdc-conf:
|
||||
pkgs.writeShellScriptBin "kadmin.local" ''
|
||||
${pkgs.heimdalFull}/bin/kadmin -l -c ${kdc-conf} $@
|
||||
${pkgs.heimdal}/bin/kadmin -l -c ${kdc-conf} $@
|
||||
'';
|
||||
|
||||
masterOpts = { ... }: {
|
||||
@ -329,8 +326,7 @@ in {
|
||||
}
|
||||
{
|
||||
assertion = !(master-server && slave-server);
|
||||
message =
|
||||
"Only one of master-config and slave-config may be provided.";
|
||||
message = "Only one of master-config and slave-config may be provided.";
|
||||
}
|
||||
];
|
||||
|
||||
@ -363,7 +359,7 @@ in {
|
||||
};
|
||||
|
||||
environment = {
|
||||
systemPackages = [ pkgs.heimdalFull (kadmin-local kdc-conf) ];
|
||||
systemPackages = [ pkgs.heimdal (kadmin-local kdc-conf) ];
|
||||
|
||||
## This shouldn't be necessary...every host gets a krb5.keytab
|
||||
# etc = {
|
||||
@ -376,9 +372,8 @@ in {
|
||||
# };
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${state-directory} 0740 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
systemd.tmpfiles.rules =
|
||||
[ "d ${state-directory} 0740 ${cfg.user} ${cfg.group} - -" ];
|
||||
|
||||
fudo.system = {
|
||||
services = if master-server then {
|
||||
@ -391,7 +386,8 @@ in {
|
||||
after = [ "network.target" ];
|
||||
description =
|
||||
"Heimdal Kerberos Key Distribution Center (ticket server).";
|
||||
execStart = "${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
execStart =
|
||||
"${pkgs.heimdal}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
@ -426,26 +422,29 @@ in {
|
||||
execStart = "${init-cmd}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
path = with pkgs; [ heimdal ];
|
||||
protectSystem = "full";
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
workingDirectory = state-directory;
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
heimdal-ipropd-master = mkIf (length cfg.master-config.slave-hosts > 0) {
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Propagate changes to the master KDC DB to all slaves.";
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
execStart = "${pkgs.heimdalFull}/libexec/heimdal/ipropd-master -c ${kdc-conf} -k ${cfg.master.ipropd-keytab}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
heimdal-ipropd-master =
|
||||
mkIf (length cfg.master-config.slave-hosts > 0) {
|
||||
requires = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description =
|
||||
"Propagate changes to the master KDC DB to all slaves.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
execStart =
|
||||
"${pkgs.heimdal}/libexec/heimdal/ipropd-master -c ${kdc-conf} -k ${cfg.master.ipropd-keytab}";
|
||||
user = cfg.user;
|
||||
group = cfg.group;
|
||||
workingDirectory = state-directory;
|
||||
privateNetwork = false;
|
||||
addressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
environment = { KRB5_CONFIG = "/etc/krb5.conf"; };
|
||||
};
|
||||
|
||||
} else {
|
||||
|
||||
@ -453,7 +452,7 @@ in {
|
||||
listen-addrs = concatStringsSep " "
|
||||
(map (addr: "--addresses=${addr}") cfg.bind-addresses);
|
||||
command =
|
||||
"${pkgs.heimdalFull}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
"${pkgs.heimdal}/libexec/heimdal/kdc -c ${kdc-conf} --ports=88 ${listen-addrs}";
|
||||
in {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
@ -471,10 +470,11 @@ in {
|
||||
|
||||
heimdal-ipropd-slave = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Receive changes propagated from the KDC master server.";
|
||||
path = with pkgs; [ heimdalFull ];
|
||||
description =
|
||||
"Receive changes propagated from the KDC master server.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
execStart = concatStringsSep " " [
|
||||
"${pkgs.heimdalFull}/libexec/heimdal/ipropd-slave"
|
||||
"${pkgs.heimdal}/libexec/heimdal/ipropd-slave"
|
||||
"--config-file=${kdc-conf}"
|
||||
"--keytab=${cfg.slave-config.ipropd-keytab}"
|
||||
"--realm=${cfg.realm}"
|
||||
@ -501,7 +501,7 @@ in {
|
||||
{
|
||||
name = "kerberos-adm";
|
||||
user = cfg.user;
|
||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
|
||||
server = "${pkgs.heimdal}/libexec/heimdal/kadmind";
|
||||
protocol = "tcp";
|
||||
serverArgs =
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kadmin-keytab}";
|
||||
@ -509,7 +509,7 @@ in {
|
||||
{
|
||||
name = "kpasswd";
|
||||
user = cfg.user;
|
||||
server = "${pkgs.heimdalFull}/libexec/heimdal/kpasswdd";
|
||||
server = "${pkgs.heimdal}/libexec/heimdal/kpasswdd";
|
||||
protocol = "udp";
|
||||
serverArgs =
|
||||
"--config-file=${kdc-conf} --keytab=${cfg.master-config.kpasswdd-keytab}";
|
||||
@ -519,12 +519,10 @@ in {
|
||||
|
||||
networking = {
|
||||
firewall = {
|
||||
allowedTCPPorts = [ 88 ] ++
|
||||
(optionals master-server [ 749 ]) ++
|
||||
(optionals slave-server [ 2121 ]);
|
||||
allowedUDPPorts = [ 88 ] ++
|
||||
(optionals master-server [ 464 ]) ++
|
||||
(optionals slave-server [ 2121 ]);
|
||||
allowedTCPPorts = [ 88 ] ++ (optionals master-server [ 749 ])
|
||||
++ (optionals slave-server [ 2121 ]);
|
||||
allowedUDPPorts = [ 88 ] ++ (optionals master-server [ 464 ])
|
||||
++ (optionals slave-server [ 2121 ]);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -1,8 +1,7 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.minecraft-server;
|
||||
let cfg = config.fudo.minecraft-server;
|
||||
|
||||
in {
|
||||
options.fudo.minecraft-server = {
|
||||
@ -11,7 +10,7 @@ in {
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
description = "Minecraft package to use.";
|
||||
default = pkgs.minecraft-server_1_15_1;
|
||||
default = pkgs.minecraft-current;
|
||||
};
|
||||
|
||||
data-dir = mkOption {
|
||||
@ -27,10 +26,11 @@ in {
|
||||
motd = mkOption {
|
||||
type = types.str;
|
||||
description = "Welcome message for newcomers.";
|
||||
default = "Welcome to Minecraft! Have fun building...";
|
||||
};
|
||||
|
||||
game-mode = mkOption {
|
||||
type = types.enum ["survival" "creative" "adventure" "spectator"];
|
||||
type = types.enum [ "survival" "creative" "adventure" "spectator" ];
|
||||
description = "Game mode of the server.";
|
||||
default = "survival";
|
||||
};
|
||||
@ -40,12 +40,15 @@ in {
|
||||
description = "Difficulty level, where 0 is peaceful and 3 is hard.";
|
||||
default = 2;
|
||||
};
|
||||
|
||||
allow-cheats = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [
|
||||
cfg.package
|
||||
];
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
services.minecraft-server = {
|
||||
enable = true;
|
||||
@ -58,6 +61,7 @@ in {
|
||||
motd = cfg.motd;
|
||||
difficulty = cfg.difficulty;
|
||||
gamemode = cfg.game-mode;
|
||||
allow-cheats = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
252
lib/fudo/nsd.nix
252
lib/fudo/nsd.nix
@ -1,4 +1,4 @@
|
||||
### NOTE:
|
||||
# ## NOTE:
|
||||
## This is a copy of the upstream version, which allows for overriding the state directory
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
@ -18,25 +18,26 @@ let
|
||||
ipv6 = cfg.ipv6;
|
||||
ratelimit = cfg.ratelimit.enable;
|
||||
rootServer = cfg.rootServer;
|
||||
zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0;
|
||||
zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones)
|
||||
> 0;
|
||||
};
|
||||
|
||||
mkZoneFileName = name: if name == "." then "root" else name;
|
||||
|
||||
# replaces include: directives for keys with fake keys for nsd-checkconf
|
||||
injectFakeKeys = keys: concatStrings
|
||||
(mapAttrsToList
|
||||
(keyName: keyOptions: ''
|
||||
fakeKey="$(${pkgs.bind}/bin/tsig-keygen -a ${escapeShellArgs [ keyOptions.algorithm keyName ]} | grep -oP "\s*secret \"\K.*(?=\";)")"
|
||||
sed "s@^\s*include:\s*\"${stateDir}/private/${keyName}\"\$@secret: $fakeKey@" -i $out/nsd.conf
|
||||
'')
|
||||
keys);
|
||||
injectFakeKeys = keys:
|
||||
concatStrings (mapAttrsToList (keyName: keyOptions: ''
|
||||
fakeKey="$(${pkgs.bind}/bin/tsig-keygen -a ${
|
||||
escapeShellArgs [ keyOptions.algorithm keyName ]
|
||||
} | grep -oP "\s*secret \"\K.*(?=\";)")"
|
||||
sed "s@^\s*include:\s*\"${stateDir}/private/${keyName}\"\$@secret: $fakeKey@" -i $out/nsd.conf
|
||||
'') keys);
|
||||
|
||||
nsdEnv = pkgs.buildEnv {
|
||||
name = "nsd-env";
|
||||
|
||||
paths = [ configFile ]
|
||||
++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
|
||||
++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
|
||||
|
||||
postBuild = ''
|
||||
echo "checking zone files"
|
||||
@ -64,12 +65,12 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
writeZoneData = name: text: pkgs.writeTextFile {
|
||||
name = "nsd-zone-${mkZoneFileName name}";
|
||||
inherit text;
|
||||
destination = "/zones/${mkZoneFileName name}";
|
||||
};
|
||||
|
||||
writeZoneData = name: text:
|
||||
pkgs.writeTextFile {
|
||||
name = "nsd-zone-${mkZoneFileName name}";
|
||||
inherit text;
|
||||
destination = "/zones/${mkZoneFileName name}";
|
||||
};
|
||||
|
||||
# options are ordered alphanumerically by the nixos option name
|
||||
configFile = pkgs.writeTextDir "nsd.conf" ''
|
||||
@ -86,19 +87,19 @@ let
|
||||
zonelistfile: "${stateDir}/var/zone.list"
|
||||
# interfaces
|
||||
${forEach " ip-address: " cfg.interfaces}
|
||||
ip-freebind: ${yesOrNo cfg.ipFreebind}
|
||||
hide-version: ${yesOrNo cfg.hideVersion}
|
||||
ip-freebind: ${yesOrNo cfg.ipFreebind}
|
||||
hide-version: ${yesOrNo cfg.hideVersion}
|
||||
identity: "${cfg.identity}"
|
||||
ip-transparent: ${yesOrNo cfg.ipTransparent}
|
||||
do-ip4: ${yesOrNo cfg.ipv4}
|
||||
ip-transparent: ${yesOrNo cfg.ipTransparent}
|
||||
do-ip4: ${yesOrNo cfg.ipv4}
|
||||
ipv4-edns-size: ${toString cfg.ipv4EDNSSize}
|
||||
do-ip6: ${yesOrNo cfg.ipv6}
|
||||
do-ip6: ${yesOrNo cfg.ipv6}
|
||||
ipv6-edns-size: ${toString cfg.ipv6EDNSSize}
|
||||
log-time-ascii: ${yesOrNo cfg.logTimeAscii}
|
||||
log-time-ascii: ${yesOrNo cfg.logTimeAscii}
|
||||
${maybeString "nsid: " cfg.nsid}
|
||||
port: ${toString cfg.port}
|
||||
reuseport: ${yesOrNo cfg.reuseport}
|
||||
round-robin: ${yesOrNo cfg.roundRobin}
|
||||
reuseport: ${yesOrNo cfg.reuseport}
|
||||
round-robin: ${yesOrNo cfg.roundRobin}
|
||||
server-count: ${toString cfg.serverCount}
|
||||
${maybeToString "statistics: " cfg.statistics}
|
||||
tcp-count: ${toString cfg.tcpCount}
|
||||
@ -107,16 +108,16 @@ let
|
||||
verbosity: ${toString cfg.verbosity}
|
||||
${maybeString "version: " cfg.version}
|
||||
xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
|
||||
zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
|
||||
zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
|
||||
${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
|
||||
${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
|
||||
rrl-ratelimit: ${toString cfg.ratelimit.ratelimit}
|
||||
${maybeString "rrl-slip: " cfg.ratelimit.slip}
|
||||
${maybeString "rrl-slip: " cfg.ratelimit.slip}
|
||||
rrl-size: ${toString cfg.ratelimit.size}
|
||||
rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit}
|
||||
${keyConfigFile}
|
||||
remote-control:
|
||||
control-enable: ${yesOrNo cfg.remoteControl.enable}
|
||||
control-enable: ${yesOrNo cfg.remoteControl.enable}
|
||||
control-key-file: "${cfg.remoteControl.controlKeyFile}"
|
||||
control-cert-file: "${cfg.remoteControl.controlCertFile}"
|
||||
${forEach " control-interface: " cfg.remoteControl.interfaces}
|
||||
@ -129,10 +130,10 @@ let
|
||||
|
||||
yesOrNo = b: if b then "yes" else "no";
|
||||
maybeString = prefix: x: if x == null then "" else ''${prefix} "${x}"'';
|
||||
maybeToString = prefix: x: if x == null then "" else ''${prefix} ${toString x}'';
|
||||
maybeToString = prefix: x:
|
||||
if x == null then "" else "${prefix} ${toString x}";
|
||||
forEach = pre: l: concatMapStrings (x: pre + x + "\n") l;
|
||||
|
||||
|
||||
keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: ''
|
||||
key:
|
||||
name: "${keyName}"
|
||||
@ -148,53 +149,44 @@ let
|
||||
chmod 0400 "$dest"
|
||||
'') cfg.keys);
|
||||
|
||||
|
||||
# options are ordered alphanumerically by the nixos option name
|
||||
zoneConfigFile = name: zone: ''
|
||||
zone:
|
||||
name: "${name}"
|
||||
zonefile: "${stateDir}/zones/${mkZoneFileName name}"
|
||||
${maybeString "outgoing-interface: " zone.outgoingInterface}
|
||||
${forEach " rrl-whitelist: " zone.rrlWhitelist}
|
||||
${maybeString "zonestats: " zone.zoneStats}
|
||||
${forEach " rrl-whitelist: " zone.rrlWhitelist}
|
||||
${maybeString "zonestats: " zone.zoneStats}
|
||||
${maybeToString "max-refresh-time: " zone.maxRefreshSecs}
|
||||
${maybeToString "min-refresh-time: " zone.minRefreshSecs}
|
||||
${maybeToString "max-retry-time: " zone.maxRetrySecs}
|
||||
${maybeToString "min-retry-time: " zone.minRetrySecs}
|
||||
allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
|
||||
${forEach " allow-notify: " zone.allowNotify}
|
||||
${forEach " request-xfr: " zone.requestXFR}
|
||||
${forEach " notify: " zone.notify}
|
||||
allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
|
||||
${forEach " allow-notify: " zone.allowNotify}
|
||||
${forEach " request-xfr: " zone.requestXFR}
|
||||
${forEach " notify: " zone.notify}
|
||||
notify-retry: ${toString zone.notifyRetry}
|
||||
${forEach " provide-xfr: " zone.provideXFR}
|
||||
${forEach " provide-xfr: " zone.provideXFR}
|
||||
'';
|
||||
|
||||
zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; };
|
||||
zoneConfigs = zoneConfigs' { } "" { children = cfg.zones; };
|
||||
|
||||
zoneConfigs' = parent: name: zone:
|
||||
if !(zone ? children) || zone.children == null || zone.children == { }
|
||||
# leaf -> actual zone
|
||||
then listToAttrs [ (nameValuePair name (parent // zone)) ]
|
||||
# leaf -> actual zone
|
||||
then
|
||||
listToAttrs [
|
||||
(nameValuePair name (parent // zone))
|
||||
]
|
||||
|
||||
# fork -> pattern
|
||||
else zipAttrsWith (name: head) (
|
||||
mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
|
||||
zone.children
|
||||
);
|
||||
|
||||
# fighting infinite recursion
|
||||
zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true;
|
||||
zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false;
|
||||
zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false;
|
||||
zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false;
|
||||
zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false;
|
||||
zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false;
|
||||
zoneOptions6 = zoneOptionsRaw // childConfig null false;
|
||||
|
||||
childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; };
|
||||
else
|
||||
zipAttrsWith (name: head) (mapAttrsToList (name: child:
|
||||
zoneConfigs' (parent // zone // { children = { }; }) name child)
|
||||
zone.children);
|
||||
|
||||
# options are ordered alphanumerically
|
||||
zoneOptionsRaw = types.submodule {
|
||||
zoneOptions = types.submodule {
|
||||
options = {
|
||||
|
||||
allowAXFRFallback = mkOption {
|
||||
@ -209,9 +201,11 @@ let
|
||||
allowNotify = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
|
||||
"10.0.3.4&255.255.0.0 BLOCKED"
|
||||
];
|
||||
example = [
|
||||
"192.0.2.0/24 NOKEY"
|
||||
"10.0.0.1-10.0.0.5 my_tsig_key_name"
|
||||
"10.0.3.4&255.255.0.0 BLOCKED"
|
||||
];
|
||||
description = ''
|
||||
Listed primary servers are allowed to notify this secondary server.
|
||||
<screen><![CDATA[
|
||||
@ -231,7 +225,14 @@ let
|
||||
};
|
||||
|
||||
children = mkOption {
|
||||
default = {};
|
||||
# TODO: This relies on the fact that `types.anything` doesn't set any
|
||||
# values of its own to any defaults, because in the above zoneConfigs',
|
||||
# values from children override ones from parents, but only if the
|
||||
# attributes are defined. Because of this, we can't replace the element
|
||||
# type here with `zoneConfigs`, since that would set all the attributes
|
||||
# to default values, breaking the parent inheriting function.
|
||||
type = types.attrsOf types.anything;
|
||||
default = { };
|
||||
description = ''
|
||||
Children zones inherit all options of their parents. Attributes
|
||||
defined in a child will overwrite the ones of its parent. Only
|
||||
@ -245,7 +246,6 @@ let
|
||||
data = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "";
|
||||
description = ''
|
||||
The actual zone data. This is the content of your zone file.
|
||||
Use imports or pkgs.lib.readFile if you don't want this data in your config file.
|
||||
@ -274,20 +274,22 @@ let
|
||||
};
|
||||
zsk = mkOption {
|
||||
type = keyPolicy;
|
||||
default = { keySize = 2048;
|
||||
prePublish = "1w";
|
||||
postPublish = "1w";
|
||||
rollPeriod = "1mo";
|
||||
};
|
||||
default = {
|
||||
keySize = 2048;
|
||||
prePublish = "1w";
|
||||
postPublish = "1w";
|
||||
rollPeriod = "1mo";
|
||||
};
|
||||
description = "Key policy for zone signing keys";
|
||||
};
|
||||
ksk = mkOption {
|
||||
type = keyPolicy;
|
||||
default = { keySize = 4096;
|
||||
prePublish = "1mo";
|
||||
postPublish = "1mo";
|
||||
rollPeriod = "0";
|
||||
};
|
||||
default = {
|
||||
keySize = 4096;
|
||||
prePublish = "1mo";
|
||||
postPublish = "1mo";
|
||||
rollPeriod = "0";
|
||||
};
|
||||
description = "Key policy for key signing keys";
|
||||
};
|
||||
};
|
||||
@ -329,10 +331,9 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
notify = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
|
||||
description = ''
|
||||
This primary server will notify all given secondary servers about
|
||||
@ -369,7 +370,7 @@ let
|
||||
|
||||
provideXFR = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
default = [ ];
|
||||
example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
|
||||
description = ''
|
||||
Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
|
||||
@ -379,16 +380,27 @@ let
|
||||
|
||||
requestXFR = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [];
|
||||
default = [ ];
|
||||
description = ''
|
||||
Format: <code>[AXFR|UDP] <ip-address> <key-name | NOKEY></code>
|
||||
'';
|
||||
};
|
||||
|
||||
rrlWhitelist = mkOption {
|
||||
type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
|
||||
default = [];
|
||||
type = with types;
|
||||
listOf (enum [
|
||||
"nxdomain"
|
||||
"error"
|
||||
"referral"
|
||||
"any"
|
||||
"rrsig"
|
||||
"wildcard"
|
||||
"nodata"
|
||||
"dnskey"
|
||||
"positive"
|
||||
"all"
|
||||
]);
|
||||
default = [ ];
|
||||
description = ''
|
||||
Whitelists the given rrl-types.
|
||||
'';
|
||||
@ -430,9 +442,10 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
|
||||
dnssecZones =
|
||||
(filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
|
||||
|
||||
dnssec = dnssecZones != {};
|
||||
dnssec = dnssecZones != { };
|
||||
|
||||
dnssecTools = pkgs.bind.override { enablePython = true; };
|
||||
|
||||
@ -443,32 +456,40 @@ let
|
||||
${concatStrings (mapAttrsToList signZone dnssecZones)}
|
||||
'';
|
||||
signZone = name: zone: ''
|
||||
${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
|
||||
${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${
|
||||
policyFile name zone.dnssecPolicy
|
||||
} ${name}
|
||||
${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
|
||||
${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
|
||||
'';
|
||||
policyFile = name: policy: pkgs.writeText "${name}.policy" ''
|
||||
zone ${name} {
|
||||
algorithm ${policy.algorithm};
|
||||
key-size zsk ${toString policy.zsk.keySize};
|
||||
key-size ksk ${toString policy.ksk.keySize};
|
||||
keyttl ${policy.keyttl};
|
||||
pre-publish zsk ${policy.zsk.prePublish};
|
||||
pre-publish ksk ${policy.ksk.prePublish};
|
||||
post-publish zsk ${policy.zsk.postPublish};
|
||||
post-publish ksk ${policy.ksk.postPublish};
|
||||
roll-period zsk ${policy.zsk.rollPeriod};
|
||||
roll-period ksk ${policy.ksk.rollPeriod};
|
||||
coverage ${policy.coverage};
|
||||
};
|
||||
'';
|
||||
in
|
||||
{
|
||||
policyFile = name: policy:
|
||||
pkgs.writeText "${name}.policy" ''
|
||||
zone ${name} {
|
||||
algorithm ${policy.algorithm};
|
||||
key-size zsk ${toString policy.zsk.keySize};
|
||||
key-size ksk ${toString policy.ksk.keySize};
|
||||
keyttl ${policy.keyttl};
|
||||
pre-publish zsk ${policy.zsk.prePublish};
|
||||
pre-publish ksk ${policy.ksk.prePublish};
|
||||
post-publish zsk ${policy.zsk.postPublish};
|
||||
post-publish ksk ${policy.ksk.postPublish};
|
||||
roll-period zsk ${policy.zsk.rollPeriod};
|
||||
roll-period ksk ${policy.ksk.rollPeriod};
|
||||
coverage ${policy.coverage};
|
||||
};
|
||||
'';
|
||||
in {
|
||||
# options are ordered alphanumerically
|
||||
options.fudo.nsd = {
|
||||
|
||||
enable = mkEnableOption "NSD authoritative DNS server";
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
description = "Directory at which to store NSD state data.";
|
||||
default = "/var/lib/nsd";
|
||||
};
|
||||
|
||||
bind8Stats = mkEnableOption "BIND8 like statistics";
|
||||
|
||||
dnssecInterval = mkOption {
|
||||
@ -587,6 +608,7 @@ in
|
||||
reuseport = mkOption {
|
||||
type = types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
defaultText = literalExpression "pkgs.stdenv.isLinux";
|
||||
description = ''
|
||||
Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
|
||||
processes bind to the same port. This speeds up operation especially
|
||||
@ -614,13 +636,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
description = "Directory at which to store NSD state data.";
|
||||
default = "/var/lib/nsd";
|
||||
};
|
||||
|
||||
statistics = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
@ -689,7 +704,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
keys = mkOption {
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
@ -714,8 +728,8 @@ in
|
||||
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{ "tsig.example.org" = {
|
||||
algorithm = "hmac-md5";
|
||||
keyFile = "/path/to/my/key";
|
||||
@ -727,7 +741,6 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
ratelimit = {
|
||||
|
||||
enable = mkEnableOption "ratelimit capabilities";
|
||||
@ -788,7 +801,6 @@ in
|
||||
|
||||
};
|
||||
|
||||
|
||||
remoteControl = {
|
||||
|
||||
enable = mkEnableOption "remote control via nsd-control";
|
||||
@ -849,8 +861,8 @@ in
|
||||
|
||||
zones = mkOption {
|
||||
type = types.attrsOf zoneOptions;
|
||||
default = {};
|
||||
example = literalExample ''
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{ "serverGroup1" = {
|
||||
provideXFR = [ "10.1.2.3 NOKEY" ];
|
||||
children = {
|
||||
@ -861,7 +873,7 @@ in
|
||||
@ IN SOA a.ns.example.com. admin.example.com. (
|
||||
...
|
||||
''';
|
||||
};
|
||||
};
|
||||
"example.org." = {
|
||||
data = '''
|
||||
$ORIGIN example.org.
|
||||
@ -869,16 +881,16 @@ in
|
||||
@ IN SOA a.ns.example.com. admin.example.com. (
|
||||
...
|
||||
''';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
"example.net." = {
|
||||
provideXFR = [ "10.3.2.1 NOKEY" ];
|
||||
data = '''
|
||||
...
|
||||
''';
|
||||
};
|
||||
}
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Define your zones here. Zones can cascade other zones and therefore
|
||||
@ -896,7 +908,7 @@ in
|
||||
assertions = singleton {
|
||||
assertion = zoneConfigs ? "." -> cfg.rootServer;
|
||||
message = "You have a root zone configured. If this is really what you "
|
||||
+ "want, please enable 'services.nsd.rootServer'.";
|
||||
+ "want, please enable 'services.nsd.rootServer'.";
|
||||
};
|
||||
|
||||
environment = {
|
||||
@ -909,7 +921,7 @@ in
|
||||
users.users.${username} = {
|
||||
description = "NSD service user";
|
||||
home = stateDir;
|
||||
createHome = true;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.nsd;
|
||||
group = username;
|
||||
};
|
||||
@ -921,7 +933,7 @@ in
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
startLimitBurst = 4;
|
||||
startLimitIntervalSec = 5 * 60; # 5 mins
|
||||
startLimitIntervalSec = 5 * 60; # 5 mins
|
||||
serviceConfig = {
|
||||
ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
|
||||
StandardError = "null";
|
||||
@ -975,4 +987,6 @@ in
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ hrdinka ];
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ let
|
||||
|
||||
pdns-config-dir = pkgs.writeTextDir "pdns.conf" ''
|
||||
local-address=${concatStringsSep ", " cfg.listen-v4-addresses}
|
||||
local-ipv6=${concatStringsSep ", " cfg.listen-v6-addresses}
|
||||
local-port=${toString cfg.port}
|
||||
launch=
|
||||
include-dir=${runtime-dir}
|
||||
@ -305,13 +304,14 @@ in {
|
||||
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
|
||||
fi
|
||||
'';
|
||||
# Wait until posgresql is available before starting
|
||||
ExecStartPre =
|
||||
pkgs.writeShellScript "ensure-postgresql-running.sh" ''
|
||||
while [ ! "$( psql -tAc "SELECT 1" )" ]; do
|
||||
${pkgs.coreutils}/bin/sleep 3
|
||||
done
|
||||
'';
|
||||
## Doesn't seem to use env vars, and so fails if remote
|
||||
## Wait until posgresql is available before starting
|
||||
# ExecStartPre =
|
||||
# pkgs.writeShellScript "ensure-postgresql-running.sh" ''
|
||||
# while [ ! "$( psql -tAc "SELECT 1" )" ]; do
|
||||
# ${pkgs.coreutils}/bin/sleep 3
|
||||
# done
|
||||
# '';
|
||||
};
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user