network/nix/nixos-module/container/dns.nix

231 lines
7.0 KiB
Nix

{ config, dns-nix, hostName, lib, pkgs, self, ... }:
let
serial = builtins.substring 0 10 self.lastModifiedDate;
generateZoneFile = let
util = dns-nix.util.${pkgs.system};
in { name, ns, records, ... }: util.writeZone name {
TTL = 60*60;
SOA = {
nameServer = "${lib.dns.ns}.";
adminEmail = "astro@spaceboyz.net";
serial = lib.toInt serial;
refresh = 1*60*60;
retry = 5*60;
expire = 2*60*60;
minimum = 1*60;
};
NS = map (a: a+".") ns;
subdomains = lib.foldl (a: b: lib.recursiveUpdate a b) { } (map ({ name, type, data }: {
${name}.${type} = [ data ];
}) records);
};
in
{
options =
with lib;
let
recordOpts = {
name = mkOption {
description = "DNS label";
type = types.str;
};
type = mkOption {
type = types.enum [ "A" "AAAA" "MX" "SRV" "CNAME" "TXT" "PTR" ];
};
data = mkOption {
type = types.oneOf [ types.str (types.attrsOf (types.oneOf [ types.int types.str ]))];
};
};
zoneOpts = {
name = mkOption {
description = "DNS FQDN w/o trailing dot";
type = types.str;
};
ns = mkOption {
type = with types; listOf str;
};
records = mkOption {
type = with types; listOf (submodule {
options = recordOpts;
});
};
dynamic = mkOption {
type = types.bool;
default = false;
};
};
in {
site.dns.localZones = mkOption {
type = with types; listOf (submodule {
options = zoneOpts;
});
};
};
config = {
site.dns.localZones = lib.dns.localZones;
services.knot = lib.mkIf config.site.hosts.${hostName}.services.dns.enable (
let
generateZone = zone@{ name, dynamic, ... }: {
domain = name;
template = "zentralwerk";
acl = [ "zone_xfr" ] ++ lib.optional dynamic "dyndns";
file = if dynamic
then "/var/lib/knot/zones/${name}.zone"
else generateZoneFile zone;
notify = [ "all" ];
};
in {
enable = true;
settings = {
acl = [
{
id = "dyndns";
action = "update";
key = "dyndns";
}
{
id = "zone_xfr";
address = with config.site.net.serv; [
# ns.c3d2.de
hosts4.knot hosts6.dn42.knot hosts6.up4.knot
"2a00:8180:2c00:282:2041:cbff:fe0c:8516"
"fd23:42:c3d2:582:2041:cbff:fe0c:8516"
# ns.spaceboyz.net
"172.22.24.4" "95.217.229.209" "2a01:4f9:4b:39ec::4"
# ns1.supersandro.de
"188.34.196.104" "2a01:4f8:1c1c:1d38::1"
];
action = "transfer";
}
];
key = [ {
id = "dyndns";
algorithm = "hmac-sha256";
secret = config.site.dyndnsKey;
} ];
log = [ {
target = "syslog";
any = "info";
} ];
mod-stats = [ {
id = "default";
query-type = "on";
} ];
remote = let
via = with config.site.net.serv; [ hosts4.dns hosts6.up4.dns ];
in [
{
id = "ns.c3d2.de";
address = with config.site.net.serv; [ hosts4.knot hosts6.dn42.knot hosts6.up4.knot ];
inherit via;
} {
id = "ns.spaceboyz.net";
address = [ "172.22.24.4" "95.217.229.209" "2a01:4f9:4b:39ec::4" ];
inherit via;
} {
id = "ns1.supersandro.de";
address = [ /*"188.34.196.104"*/ "2a01:4f8:1c1c:1d38::1" ];
inherit via;
}
];
remotes = [ {
id = "all";
remote = [ "ns.c3d2.de" "ns.spaceboyz.net" "ns1.supersandro.de" ];
} ];
server = {
answer-rotation = true;
automatic-acl = true;
identity = "dns.serv.zentralwerk.org";
listen = with config.site.net; [
"127.0.0.1" "::1"
serv.hosts4.dns serv.hosts6.up4.dns serv.hosts6.dn42.dns
];
tcp-fastopen = true;
version = null;
};
template = [
{
# default is a magic name and is always loaded.
# Because we want to use catalog-role/catalog-zone settings for all zones *except* the catalog zone itself, we must split the templates
id = "default";
global-module = [ "mod-stats" ];
}
{
id = "zentralwerk";
catalog-role = "member";
catalog-zone = "zentralwerk.";
dnssec-signing = true;
journal-content = "all"; # required for zonefile-load=difference-no-serial and makes cold starts like zone reloads
module = "mod-stats/default";
semantic-checks = true;
serial-policy = "increment";
storage = "/var/lib/knot/zones";
zonefile-load = "difference-no-serial";
}
];
zone = [ {
acl = "zone_xfr";
catalog-role = "generate";
domain = "zentralwerk.";
notify = [ "ns1.supersandro.de" ];
storage = "/var/lib/knot/catalog";
} ] ++ map generateZone config.site.dns.localZones;
};
});
systemd.services = {
create-dynamic-zones = {
description = "Creates dynamic zone files";
requiredBy = [ "knot.service" ];
before = [ "knot.service" ];
serviceConfig.Type = "oneshot";
script = ''
mkdir -p /var/lib/knot/zones
${lib.concatMapStringsSep "\n" (zone@{ name, ... }: ''
[ -e /var/lib/knot/zones/${name}.zone ] || \
cp ${generateZoneFile zone} /var/lib/knot/zones/${name}.zone
chown -R knot /var/lib/knot/zones
chmod -R u+rwX /var/lib/knot/zones
'') (builtins.filter ({ dynamic, ... }: dynamic) config.site.dns.localZones)}
'';
};
update-dynamic-zones = {
description = "Creates initial records in dynamic zone files";
requiredBy = [ "knot.service" ];
after = [ "knot.service" ];
serviceConfig.Type = "oneshot";
path = [ pkgs.dnsutils ];
script = lib.concatMapStrings (zone: ''
nsupdate -v -y "hmac-sha256:dyndns:${config.site.dyndnsKey}" <<EOF
server localhost
${lib.concatMapStringsSep "\n" ({ name, type, data }: ''
delete ${name}.${zone.name}. IN ${type}
add ${name}.${zone.name}. 3600 IN ${type} ${data}
'') zone.records}
send
EOF
'') (builtins.filter ({ dynamic, ... }: dynamic) config.site.dns.localZones);
};
};
};
}