{ config, pkgs, lib, ... }: with lib; let reverseDomain = domain: concatStringsSep "." (reverseList (splitString "." domain)); acmeChallengeZonefile = domain: pkgs.writeText "${reverseDomain "_acme-challenge.${domain}"}.soa" '' $ORIGIN _acme-challenge.${domain}. $TTL 3600 @ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li 2023013000 10800 3600 604800 30 IN NS ns.yggdrasil.li. ''; knotKeys = let dir = ./keys; toKeyInfo = name: v: if v == "regular" || v == "symlink" then { path = dir + "/${name}"; inherit name; } else null; in filter (v: v != null) (mapAttrsToList toKeyInfo (builtins.readDir dir)); indentString = indentation: str: concatMapStringsSep "\n" (str: " ${str}") (splitString "\n" (removeSuffix "\n" str)); mkZone = {domain, path ? (./zones + "/${reverseDomain domain}.soa"), acmeDomains ? [domain], addACLs ? {}}: indentString " " (let keys = acmeDomain: [(assert (config.sops.secrets ? "${acmeDomain}_acme"); "${acmeDomain}_acme_acl")] ++ (addACLs.${acmeDomain} or []); in '' - domain: ${domain} template: inwx_zone ${optionalString (acmeDomains != []) "acl: [local_acl, inwx_acl]"} file: ${path} ${concatMapStringsSep "\n" (acmeDomain: '' - domain: _acme-challenge.${acmeDomain} template: acme_zone acl: [${concatStringsSep ", " (keys acmeDomain)}] file: ${acmeChallengeZonefile acmeDomain} '') acmeDomains} ''); in { config = { fileSystems."/var/lib/knot" = { device = "surtr/safe/var-lib-knot"; fsType = "zfs"; }; systemd.services.knot = { unitConfig.RequiresMountsFor = [ "/var/lib/knot" ]; serviceConfig.LoadCredential = map ({name, ...}: "${name}.yaml:${config.sops.secrets.${name}.path}") knotKeys; }; services.knot = { enable = true; keyFiles = map ({name, ...}: "/run/credentials/knot.service/${name}.yaml") knotKeys; extraConfig = '' server: listen: 127.0.0.1@53 listen: ::1@53 listen: 202.61.241.61@53 listen: 2a03:4000:52:ada::@53 remote: - id: inwx_notify address: 2a0a:c980::53 via: 2a03:4000:52:ada:: - id: recursive address: ::1@5353 via: ::1 - id: local address: ::1@53 via: ::1 key: local_key acl: - id: inwx_acl address: [185.181.104.96, 2a0a:c980::53] action: transfer - id: local_acl key: local_key action: update update-type: DS ${let toACMEACL = { name, ... }: if hasSuffix "_acme" name then indentString " " '' - id: ${name}_acl key: ${name}_key action: update '' else null; in concatStringsSep "\n" (filter (v: v != null) (map toACMEACL knotKeys))} mod-rrl: - id: default rate-limit: 200 slip: 2 mod-cookies: - id: default secret-lifetime: 4h badcookie-slip: 1 submission: - id: validating-resolver parent: recursive check-interval: 5m policy: - id: rsa2048 algorithm: rsasha256 ksk-size: 4096 zsk-size: 2048 ksk-submission: validating-resolver - id: ed25519 algorithm: ed25519 nsec3: on nsec3-iterations: 0 signing-threads: 2 ksk-submission: validating-resolver - id: ed25519_local-push zone-max-ttl: 1h algorithm: ed25519 nsec3: on nsec3-iterations: 0 signing-threads: 2 ksk-submission: validating-resolver cds-cdnskey-publish: double-ds propagation-delay: 0 ds-push: [local] template: - id: default global-module: [mod-cookies/default, mod-rrl/default] - id: inwx_zone storage: /var/lib/knot zonefile-sync: -1 zonefile-load: difference-no-serial serial-policy: dateserial journal-content: all semantic-checks: on dnssec-signing: on dnssec-policy: ed25519 notify: [inwx_notify] acl: [inwx_acl] - id: acme_zone storage: /var/lib/knot zonefile-sync: -1 zonefile-load: difference-no-serial serial-policy: dateserial journal-content: all semantic-checks: on dnssec-signing: on dnssec-policy: ed25519_local-push zone: ${concatMapStringsSep "\n" mkZone [ { domain = "yggdrasil.li"; addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; }; acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "app.etesync.yggdrasil.li"]; } { domain = "nights.email"; addACLs = { "nights.email" = ["ymir_acme_acl"]; }; } { domain = "141.li"; acmeDomains = ["webdav.141.li" "141.li"]; addACLs = { "141.li" = ["ymir_acme_acl"]; }; } { domain = "kleen.li"; addACLs = { "kleen.li" = ["ymir_acme_acl"]; }; } { domain = "xmpp.li"; addACLs = { "xmpp.li" = ["ymir_acme_acl"]; }; } { domain = "synapse.li"; acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"]; } { domain = "dirty-haskell.org"; addACLs = { "dirty-haskell.org" = ["ymir_acme_acl"]; }; } { domain = "praseodym.org"; addACLs = { "praseodym.org" = ["ymir_acme_acl"]; }; } { domain = "bouncy.email"; acmeDomains = ["mailin.bouncy.email" "mailsub.bouncy.email" "imap.bouncy.email" "spm.bouncy.email" "mta-sts.bouncy.email" "bouncy.email"]; } { domain = "kleen.consulting"; acmeDomains = ["mailin.kleen.consulting" "mailsub.kleen.consulting" "imap.kleen.consulting" "mta-sts.kleen.consulting" "kleen.consulting"]; } ]} ''; }; sops.secrets = listToAttrs (map ({name, path}: nameValuePair name { format = "binary"; sopsFile = path; }) knotKeys); fileSystems."/var/lib/unbound" = { device = "surtr/local/var-lib-unbound"; fsType = "zfs"; }; systemd.services.unbound.unitConfig.RequiresMountsFor = [ "/var/lib/unbound" ]; services.unbound = { enable = true; resolveLocalQueries = false; stateDir = "/var/lib/unbound"; localControlSocketPath = "/run/unbound/unbound.ctl"; enableRootTrustAnchor = false; settings = { server = { interface = ["lo@5353" "127.0.0.53"]; prefer-ip6 = true; access-control = ["127.0.0.0/8 allow" "::1/128 allow"]; root-hints = "${pkgs.dns-root-data}/root.hints"; trust-anchor-file = "${pkgs.dns-root-data}/root.key"; trust-anchor-signaling = false; num-threads = 12; so-reuseport = true; msg-cache-slabs = 16; rrset-cache-slabs = 16; infra-cache-slabs = 16; key-cache-slabs = 16; rrset-cache-size = "100m"; msg-cache-size = "50m"; outgoing-range = 8192; num-queries-per-thread = 4096; so-rcvbuf = "4m"; so-sndbuf = "4m"; # serve-expired = true; # serve-expired-ttl = 86400; # serve-expired-reply-ttl = 0; prefetch = true; prefetch-key = true; minimal-responses = false; extended-statistics = true; rrset-roundrobin = true; use-caps-for-id = true; do-not-query-localhost = false; local-zone = [ "141.10.in-addr.arpa. transparent" "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa. transparent" "yggdrasil. transparent" ]; domain-insecure = [ "141.10.in-addr.arpa." "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa." "yggdrasil." ]; }; stub-zone = map (name: { inherit name; stub-addr = "2a03:4000:52:ada:1:1::"; stub-first = true; stub-no-cache = true; stub-prime = false; }) ["yggdrasil." "arpa.in-addr.10.141." "1.0.0.0.a.d.a.0.2.5.0.0.0.0.0.4.3.0.a.2.ip6.arpa."]; }; }; }; }