{ 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; dnsSecondaryConfigs = lib.filterAttrs (peerName: _peerConfig: lib.elem peerName mapping.dns.secondary) 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 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.lasuite.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" nsARecords} ${builtins.concatStringsSep "\n" nsAAAARecords} _dmarc IN TXT "v=DMARC1; p=quarantine; ruf=mailto:postmaster@lasuite.federez.net" _mta-sts IN TXT "v=STSv1; id=1" _smtp._tls IN TXT "v=TLSRPTv1;rua=mailto:postmaster@lasuite.federez.net" default._domainkey IN TXT "${lib.concatStringsSep "\" \"" domainkeySplitted}" ''; zone-lf = pkgs.writeText "zone-lf" '' $ORIGIN lf. $TTL 60 @ IN SOA dns.lf. monitoring.lasuite.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"; } ]; }; }; }