Various updates.

This commit is contained in:
niten 2022-08-22 11:30:04 -07:00
parent 070a5e1831
commit b9def9eba9
4 changed files with 266 additions and 250 deletions

View File

@ -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 ]);
};
};
};

View File

@ -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;
};
};
};

View File

@ -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] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</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 ];
}

View File

@ -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
# '';
};
};