nix/shared/dns/knot.nix
2025-07-31 18:21:09 +02:00

269 lines
No EOL
8.6 KiB
Nix

{ config, lib, pkgs, ... }:
let
# Import nodes
nodes = import ./../../nodes.nix;
myName = config.hostName;
myNode = nodes."${myName}";
# And mapping
mapping = import ./../../mapping.nix;
dkim = import ./dkim.nix;
supportsIPv4 = nd: lib.hasAttr "ip4" nd;
supportsIPv6 = nd: lib.hasAttr "ip6" nd;
# Domain key
domainkey = ''
v=DKIM1; k=rsa; p=${dkim.dkim_pub}'';
segments = ((lib.stringLength domainkey) / 255);
domainkeySplitted = map (x: lib.substring (x*255) 255 domainkey) (lib.range 0 segments);
#####
## Knot specific
#####
# Define remote based on current role
remotes = if myName == mapping.dns.master then
map (hostname: {
id = hostname;
address = "172.19.${toString nodes.${hostname}.zone}.${toString nodes.${hostname}.id}@53";
}) mapping.dns.secondary
else
[{
id = mapping.dns.master;
address = "172.19.${toString nodes.${mapping.dns.master}.zone}.${toString nodes.${mapping.dns.master}.id}@53";
}];
remotesNames = map (remote: remote.id) remotes;
# Remotes ACL
remotesACL = if myName == mapping.dns.master then
map (hostname: {
id = "acl_${hostname}";
address = "172.19.${toString nodes.${hostname}.zone}.${toString nodes.${hostname}.id}";
action = "transfer";
}) mapping.dns.secondary
else
[{
id = "acl_${mapping.dns.master}";
address = "172.19.${toString nodes.${mapping.dns.master}.zone}.${toString nodes.${mapping.dns.master}.id}";
action = "notify";
}];
remotesACLNames = map (remote: remote.id) remotesACL;
# Other ACL
letsencryptACL = if myName == mapping.dns.master then
[{
id = "acl_le_challenge";
address = [
"172.19.0.0/16"
"fc00::/96"
];
action = "update";
update-type = "TXT";
key = "letsencrypt";
}]
else [];
acls = remotesACL ++ letsencryptACL;
# Mod-query ACLs
modQueryACLs = [{
id = "local";
address = [
"172.19.0.0/16"
"fc00::/96"
];
}];
#####
## Zone specific
#####
# host to dn
hostToDomain = hostname: builtins.replaceStrings ["-"] ["."] hostname;
hostToLfDomain = hostname: builtins.replaceStrings [".lasuite.federez"] [".lf."] (hostToDomain hostname);
# Remove cidr notation
rmCidr = ip: builtins.head (builtins.split "/" ip);
# Gen NS
toNSRecord = host: "\tIN NS ${hostToDomain host}.net.";
nsRecords = map toNSRecord mapping.dns.secondary;
# Gen MX
toMXRecord = host: "\tIN MX 10 ${hostToDomain host}.net.";
mxRecords = map toMXRecord mapping.mail.hosts;
dnsSecondaryConfigs = lib.filterAttrs (peerName: _peerConfig: lib.elem peerName mapping.dns.secondary) nodes;
mailConfigs = lib.filterAttrs (peerName: _peerConfig: lib.elem peerName mapping.mail.hosts) nodes;
# Gen A NS
nsARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
lib.optional (supportsIPv4 node) "${hostToDomain hostname}.net. IN A ${rmCidr node.ip4}"
) dnsSecondaryConfigs);
# Gen AAAA NS
nsAAAARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
lib.optional (supportsIPv6 node) "${hostToDomain hostname}.net. IN AAAA ${rmCidr node.ip6}"
) dnsSecondaryConfigs);
# Gen A MX
mxARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
lib.optional (supportsIPv4 node) "${hostToDomain hostname}.net. IN A ${rmCidr node.ip4}"
) mailConfigs);
# Gen AAAA MX
mxAAAARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
lib.optional (supportsIPv6 node) "${hostToDomain hostname}.net. IN AAAA ${rmCidr node.ip6}"
) mailConfigs);
# Gen A records for lf zone
lfARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
"${hostToLfDomain hostname} IN A 172.19.${toString node.zone}.${toString node.id}"
) nodes);
# Gen AAAA records for lf zone
lfAAAARecords = lib.flatten (lib.mapAttrsToList (hostname: node:
"${hostToLfDomain hostname} IN AAAA fc00::${toString node.zone}:${toString node.id}"
) nodes);
# Gen first NS for SOA
firstNS = builtins.head mapping.dns.secondary;
firstNSDn = "${hostToDomain firstNS}.net.";
# Zone conf
zone-lasuite-federez-net = pkgs.writeText "zone-lasuite-federez-net" ''
$ORIGIN lasuite.federez.net.
$TTL 60
@ IN SOA ${firstNSDn} monitoring.federez.net. (
2025072701 ; serial
60 ; refresh
60 ; retry
60 ; expire
60 ) ; minimum TTL
IN TXT "v=spf1 a:lasuite.federez.net ~all"
${builtins.concatStringsSep "\n" nsRecords}
${builtins.concatStringsSep "\n" mxRecords}
${builtins.concatStringsSep "\n" nsARecords}
${builtins.concatStringsSep "\n" nsAAAARecords}
${builtins.concatStringsSep "\n" mxARecords}
${builtins.concatStringsSep "\n" mxAAAARecords}
_dmarc IN TXT "v=DMARC1; p=quarantine; ruf=mailto:monitoring@federez.net"
_mta-sts IN TXT "v=STSv1; id=1"
_smtp._tls IN TXT "v=TLSRPTv1;rua=mailto:monitoring@federez.net"
default._domainkey IN TXT "${lib.concatStringsSep "\" \"" domainkeySplitted}"
'';
zone-lf = pkgs.writeText "zone-lf" ''
$ORIGIN lf.
$TTL 60
@ IN SOA dns.lf. monitoring.federez.net. (
2025072701 ; serial
60 ; refresh
60 ; retry
60 ; expire
60 ) ; minimum TTL
${builtins.concatStringsSep "\n" lfARecords}
${builtins.concatStringsSep "\n" lfAAAARecords}
'';
in
{
age.secrets = {
"tsig" = {
file = ./../../secrets/dns/tsig.age;
owner = "knot";
group = "knot";
};
};
# Force complete restart on zone changes
systemd.services.knot = {
restartTriggers = [ zone-lasuite-federez-net zone-lf ];
reloadIfChanged = lib.mkForce false;
};
services.knot = {
enable = true;
package = pkgs.knot-dns;
# To regenerate the secret use:
# nix-shell -p knot-dns
# keymgr tsig generate -t letsencrypt hmac-sha512
keyFiles = [
config.age.secrets.tsig.path
];
settings = {
server = {
listen =
[
"172.19.${toString myNode.zone}.${toString myNode.id}@53"
"fc00::${toString myNode.zone}:${toString myNode.id}@53"
]
++ lib.optional (supportsIPv4 myNode) "${rmCidr myNode.ip4}@53"
++ lib.optional (supportsIPv6 myNode) "${rmCidr myNode.ip6}@53";
};
remote = remotes;
acl = acls;
mod-queryacl = modQueryACLs;
template = [{
id = "default";
zonefile-sync = -1;
journal-content = "all";
serial-policy = "increment";
}];
zone = if myName == mapping.dns.master then [
{
domain = "lasuite.federez.net";
file = zone-lasuite-federez-net;
dnssec-signing = "on";
dnssec-policy = "default";
zonefile-load = "difference-no-serial";
notify = remotesNames;
acl = remotesACLNames ++ [
"acl_le_challenge"
];
}
{
domain = "lf";
file = zone-lf;
zonefile-load = "difference-no-serial";
notify = remotesNames;
acl = remotesACLNames;
module = "mod-queryacl/local";
}
] else [
{
domain = "lasuite.federez.net";
master = builtins.head remotesNames;
acl = remotesACLNames;
}
{
domain = "lf";
master = builtins.head remotesNames;
acl = remotesACLNames;
module = "mod-queryacl/local";
}
];
log = [
{
target = "syslog";
any = "debug";
}
];
};
};
}