1{ config, pkgs, lib, ... }: 2 3with lib; 4 5let 6 cfg = config.services.nsd; 7 8 username = "nsd"; 9 stateDir = "/var/lib/nsd"; 10 pidFile = stateDir + "/var/nsd.pid"; 11 12 # build nsd with the options needed for the given config 13 nsdPkg = pkgs.nsd.override { 14 bind8Stats = cfg.bind8Stats; 15 ipv6 = cfg.ipv6; 16 ratelimit = cfg.ratelimit.enable; 17 rootServer = cfg.rootServer; 18 zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0; 19 }; 20 21 mkZoneFileName = name: if name == "." then "root" else name; 22 23 # replaces include: directives for keys with fake keys for nsd-checkconf 24 injectFakeKeys = keys: concatStrings 25 (mapAttrsToList 26 (keyName: keyOptions: '' 27 fakeKey="$(${pkgs.bind}/bin/tsig-keygen -a ${escapeShellArgs [ keyOptions.algorithm keyName ]} | grep -oP "\s*secret \"\K.*(?=\";)")" 28 sed "s@^\s*include:\s*\"${stateDir}/private/${keyName}\"\$@secret: $fakeKey@" -i $out/nsd.conf 29 '') 30 keys); 31 32 nsdEnv = pkgs.buildEnv { 33 name = "nsd-env"; 34 35 paths = [ configFile ] 36 ++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs; 37 38 postBuild = '' 39 echo "checking zone files" 40 cd $out/zones 41 42 for zoneFile in *; do 43 echo "|- checking zone '$out/zones/$zoneFile'" 44 ${nsdPkg}/sbin/nsd-checkzone "$zoneFile" "$zoneFile" || { 45 if grep -q \\\\\\$ "$zoneFile"; then 46 echo zone "$zoneFile" contains escaped dollar signs \\\$ 47 echo Escaping them is not needed any more. Please make sure \ 48 to unescape them where they prefix a variable name. 49 fi 50 51 exit 1 52 } 53 done 54 55 echo "checking configuration file" 56 # Save original config file including key references... 57 cp $out/nsd.conf{,.orig} 58 # ...inject mock keys into config 59 ${injectFakeKeys cfg.keys} 60 # ...do the checkconf 61 ${nsdPkg}/sbin/nsd-checkconf $out/nsd.conf 62 # ... and restore original config file. 63 mv $out/nsd.conf{.orig,} 64 ''; 65 }; 66 67 writeZoneData = name: text: pkgs.writeTextFile { 68 name = "nsd-zone-${mkZoneFileName name}"; 69 inherit text; 70 destination = "/zones/${mkZoneFileName name}"; 71 }; 72 73 74 # options are ordered alphanumerically by the nixos option name 75 configFile = pkgs.writeTextDir "nsd.conf" '' 76 server: 77 chroot: "${stateDir}" 78 username: ${username} 79 80 # The directory for zonefile: files. The daemon chdirs here. 81 zonesdir: "${stateDir}" 82 83 # the list of dynamically added zones. 84 pidfile: "${pidFile}" 85 xfrdfile: "${stateDir}/var/xfrd.state" 86 xfrdir: "${stateDir}/tmp" 87 zonelistfile: "${stateDir}/var/zone.list" 88 89 # interfaces 90 ${forEach " ip-address: " cfg.interfaces} 91 92 ip-freebind: ${yesOrNo cfg.ipFreebind} 93 hide-version: ${yesOrNo cfg.hideVersion} 94 identity: "${cfg.identity}" 95 ip-transparent: ${yesOrNo cfg.ipTransparent} 96 do-ip4: ${yesOrNo cfg.ipv4} 97 ipv4-edns-size: ${toString cfg.ipv4EDNSSize} 98 do-ip6: ${yesOrNo cfg.ipv6} 99 ipv6-edns-size: ${toString cfg.ipv6EDNSSize} 100 log-time-ascii: ${yesOrNo cfg.logTimeAscii} 101 ${maybeString "nsid: " cfg.nsid} 102 port: ${toString cfg.port} 103 reuseport: ${yesOrNo cfg.reuseport} 104 round-robin: ${yesOrNo cfg.roundRobin} 105 server-count: ${toString cfg.serverCount} 106 ${maybeToString "statistics: " cfg.statistics} 107 tcp-count: ${toString cfg.tcpCount} 108 tcp-query-count: ${toString cfg.tcpQueryCount} 109 tcp-timeout: ${toString cfg.tcpTimeout} 110 verbosity: ${toString cfg.verbosity} 111 ${maybeString "version: " cfg.version} 112 xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout} 113 zonefiles-check: ${yesOrNo cfg.zonefilesCheck} 114 zonefiles-write: ${toString cfg.zonefilesWrite} 115 116 ${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength} 117 ${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength} 118 rrl-ratelimit: ${toString cfg.ratelimit.ratelimit} 119 ${maybeString "rrl-slip: " cfg.ratelimit.slip} 120 rrl-size: ${toString cfg.ratelimit.size} 121 rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit} 122 123 ${keyConfigFile} 124 125 remote-control: 126 control-enable: ${yesOrNo cfg.remoteControl.enable} 127 control-key-file: "${cfg.remoteControl.controlKeyFile}" 128 control-cert-file: "${cfg.remoteControl.controlCertFile}" 129 ${forEach " control-interface: " cfg.remoteControl.interfaces} 130 control-port: ${toString cfg.remoteControl.port} 131 server-key-file: "${cfg.remoteControl.serverKeyFile}" 132 server-cert-file: "${cfg.remoteControl.serverCertFile}" 133 134 ${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)} 135 136 ${cfg.extraConfig} 137 ''; 138 139 yesOrNo = b: if b then "yes" else "no"; 140 maybeString = prefix: x: optionalString (x != null) ''${prefix} "${x}"''; 141 maybeToString = prefix: x: optionalString (x != null) ''${prefix} ${toString x}''; 142 forEach = pre: l: concatMapStrings (x: pre + x + "\n") l; 143 144 145 keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: '' 146 key: 147 name: "${keyName}" 148 algorithm: "${keyOptions.algorithm}" 149 include: "${stateDir}/private/${keyName}" 150 '') cfg.keys); 151 152 copyKeys = concatStrings (mapAttrsToList (keyName: keyOptions: '' 153 secret=$(cat "${keyOptions.keyFile}") 154 dest="${stateDir}/private/${keyName}" 155 install -m 0400 -o "${username}" -g "${username}" <(echo " secret: \"$secret\"") "$dest" 156 '') cfg.keys); 157 158 159 # options are ordered alphanumerically by the nixos option name 160 zoneConfigFile = name: zone: '' 161 zone: 162 name: "${name}" 163 zonefile: "${stateDir}/zones/${mkZoneFileName name}" 164 ${maybeString "outgoing-interface: " zone.outgoingInterface} 165 ${forEach " rrl-whitelist: " zone.rrlWhitelist} 166 ${maybeString "zonestats: " zone.zoneStats} 167 168 ${maybeToString "max-refresh-time: " zone.maxRefreshSecs} 169 ${maybeToString "min-refresh-time: " zone.minRefreshSecs} 170 ${maybeToString "max-retry-time: " zone.maxRetrySecs} 171 ${maybeToString "min-retry-time: " zone.minRetrySecs} 172 173 allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback} 174 multi-master-check: ${yesOrNo zone.multiMasterCheck} 175 ${forEach " allow-notify: " zone.allowNotify} 176 ${forEach " request-xfr: " zone.requestXFR} 177 178 ${forEach " notify: " zone.notify} 179 notify-retry: ${toString zone.notifyRetry} 180 ${forEach " provide-xfr: " zone.provideXFR} 181 ''; 182 183 zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; }; 184 185 zoneConfigs' = parent: name: zone: 186 if !(zone ? children) || zone.children == null || zone.children == { } 187 # leaf -> actual zone 188 then listToAttrs [ (nameValuePair name (parent // zone)) ] 189 190 # fork -> pattern 191 else zipAttrsWith (name: head) ( 192 mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child) 193 zone.children 194 ); 195 196 # options are ordered alphanumerically 197 zoneOptions = types.submodule { 198 options = { 199 200 allowAXFRFallback = mkOption { 201 type = types.bool; 202 default = true; 203 description = '' 204 If NSD as secondary server should be allowed to AXFR if the primary 205 server does not allow IXFR. 206 ''; 207 }; 208 209 allowNotify = mkOption { 210 type = types.listOf types.str; 211 default = [ ]; 212 example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name" 213 "10.0.3.4&255.255.0.0 BLOCKED" 214 ]; 215 description = '' 216 Listed primary servers are allowed to notify this secondary server. 217 218 Format: `<ip> <key-name | NOKEY | BLOCKED>` 219 220 `<ip>` either a plain IPv4/IPv6 address or range. 221 Valid patters for ranges: 222 * `10.0.0.0/24`: via subnet size 223 * `10.0.0.0&255.255.255.0`: via subnet mask 224 * `10.0.0.1-10.0.0.254`: via range 225 226 A optional port number could be added with a '@': 227 * `2001:1234::1@1234` 228 229 `<key-name | NOKEY | BLOCKED>` 230 * `<key-name>` will use the specified TSIG key 231 * `NOKEY` no TSIG signature is required 232 * `BLOCKED`notifies from non-listed or blocked IPs will be ignored 233 ''; 234 }; 235 236 children = mkOption { 237 # TODO: This relies on the fact that `types.anything` doesn't set any 238 # values of its own to any defaults, because in the above zoneConfigs', 239 # values from children override ones from parents, but only if the 240 # attributes are defined. Because of this, we can't replace the element 241 # type here with `zoneConfigs`, since that would set all the attributes 242 # to default values, breaking the parent inheriting function. 243 type = types.attrsOf types.anything; 244 default = {}; 245 description = '' 246 Children zones inherit all options of their parents. Attributes 247 defined in a child will overwrite the ones of its parent. Only 248 leaf zones will be actually served. This way it's possible to 249 define maybe zones which share most attributes without 250 duplicating everything. This mechanism replaces nsd's patterns 251 in a save and functional way. 252 ''; 253 }; 254 255 data = mkOption { 256 type = types.lines; 257 default = ""; 258 description = '' 259 The actual zone data. This is the content of your zone file. 260 Use imports or pkgs.lib.readFile if you don't want this data in your config file. 261 ''; 262 }; 263 264 dnssec = mkEnableOption "DNSSEC"; 265 266 dnssecPolicy = { 267 algorithm = mkOption { 268 type = types.str; 269 default = "RSASHA256"; 270 description = "Which algorithm to use for DNSSEC"; 271 }; 272 keyttl = mkOption { 273 type = types.str; 274 default = "1h"; 275 description = "TTL for dnssec records"; 276 }; 277 coverage = mkOption { 278 type = types.str; 279 default = "1y"; 280 description = '' 281 The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time. 282 ''; 283 }; 284 zsk = mkOption { 285 type = keyPolicy; 286 default = { keySize = 2048; 287 prePublish = "1w"; 288 postPublish = "1w"; 289 rollPeriod = "1mo"; 290 }; 291 description = "Key policy for zone signing keys"; 292 }; 293 ksk = mkOption { 294 type = keyPolicy; 295 default = { keySize = 4096; 296 prePublish = "1mo"; 297 postPublish = "1mo"; 298 rollPeriod = "0"; 299 }; 300 description = "Key policy for key signing keys"; 301 }; 302 }; 303 304 maxRefreshSecs = mkOption { 305 type = types.nullOr types.int; 306 default = null; 307 description = '' 308 Limit refresh time for secondary zones. This is the timer which 309 checks to see if the zone has to be refetched when it expires. 310 Normally the value from the SOA record is used, but this option 311 restricts that value. 312 ''; 313 }; 314 315 minRefreshSecs = mkOption { 316 type = types.nullOr types.int; 317 default = null; 318 description = '' 319 Limit refresh time for secondary zones. 320 ''; 321 }; 322 323 maxRetrySecs = mkOption { 324 type = types.nullOr types.int; 325 default = null; 326 description = '' 327 Limit retry time for secondary zones. This is the timeout after 328 a failed fetch attempt for the zone. Normally the value from 329 the SOA record is used, but this option restricts that value. 330 ''; 331 }; 332 333 minRetrySecs = mkOption { 334 type = types.nullOr types.int; 335 default = null; 336 description = '' 337 Limit retry time for secondary zones. 338 ''; 339 }; 340 341 multiMasterCheck = mkOption { 342 type = types.bool; 343 default = false; 344 description = '' 345 If enabled, checks all masters for the last zone version. 346 It uses the higher version from all configured masters. 347 Useful if you have multiple masters that have different version numbers served. 348 ''; 349 }; 350 351 notify = mkOption { 352 type = types.listOf types.str; 353 default = []; 354 example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ]; 355 description = '' 356 This primary server will notify all given secondary servers about 357 zone changes. 358 359 Format: `<ip> <key-name | NOKEY>` 360 361 `<ip>` a plain IPv4/IPv6 address with on optional port number (ip@port) 362 363 `<key-name | NOKEY>` 364 - `<key-name>` sign notifies with the specified key 365 - `NOKEY` don't sign notifies 366 ''; 367 }; 368 369 notifyRetry = mkOption { 370 type = types.int; 371 default = 5; 372 description = '' 373 Specifies the number of retries for failed notifies. Set this along with notify. 374 ''; 375 }; 376 377 outgoingInterface = mkOption { 378 type = types.nullOr types.str; 379 default = null; 380 example = "2000::1@1234"; 381 description = '' 382 This address will be used for zone-transfer requests if configured 383 as a secondary server or notifications in case of a primary server. 384 Supply either a plain IPv4 or IPv6 address with an optional port 385 number (ip@port). 386 ''; 387 }; 388 389 provideXFR = mkOption { 390 type = types.listOf types.str; 391 default = []; 392 example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ]; 393 description = '' 394 Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED 395 address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40 396 ''; 397 }; 398 399 requestXFR = mkOption { 400 type = types.listOf types.str; 401 default = []; 402 description = '' 403 Format: `[AXFR|UDP] <ip-address> <key-name | NOKEY>` 404 ''; 405 }; 406 407 rrlWhitelist = mkOption { 408 type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]); 409 default = []; 410 description = '' 411 Whitelists the given rrl-types. 412 ''; 413 }; 414 415 zoneStats = mkOption { 416 type = types.nullOr types.str; 417 default = null; 418 example = "%s"; 419 description = '' 420 When set to something distinct to null NSD is able to collect 421 statistics per zone. All statistics of this zone(s) will be added 422 to the group specified by this given name. Use "%s" to use the zones 423 name as the group. The groups are output from nsd-control stats 424 and stats_noreset. 425 ''; 426 }; 427 }; 428 }; 429 430 keyPolicy = types.submodule { 431 options = { 432 keySize = mkOption { 433 type = types.int; 434 description = "Key size in bits"; 435 }; 436 prePublish = mkOption { 437 type = types.str; 438 description = "How long in advance to publish new keys"; 439 }; 440 postPublish = mkOption { 441 type = types.str; 442 description = "How long after deactivation to keep a key in the zone"; 443 }; 444 rollPeriod = mkOption { 445 type = types.str; 446 description = "How frequently to change keys"; 447 }; 448 }; 449 }; 450 451 dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs); 452 453 dnssec = dnssecZones != {}; 454 455 dnssecTools = pkgs.bind.override { enablePython = true; }; 456 457 signZones = optionalString dnssec '' 458 install -m 0600 -o "${username}" -g "${username}" -d "${stateDir}/dnssec" 459 460 ${concatStrings (mapAttrsToList signZone dnssecZones)} 461 ''; 462 signZone = name: zone: '' 463 ${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name} 464 ${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name} 465 ${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name} 466 ''; 467 policyFile = name: policy: pkgs.writeText "${name}.policy" '' 468 zone ${name} { 469 algorithm ${policy.algorithm}; 470 key-size zsk ${toString policy.zsk.keySize}; 471 key-size ksk ${toString policy.ksk.keySize}; 472 keyttl ${policy.keyttl}; 473 pre-publish zsk ${policy.zsk.prePublish}; 474 pre-publish ksk ${policy.ksk.prePublish}; 475 post-publish zsk ${policy.zsk.postPublish}; 476 post-publish ksk ${policy.ksk.postPublish}; 477 roll-period zsk ${policy.zsk.rollPeriod}; 478 roll-period ksk ${policy.ksk.rollPeriod}; 479 coverage ${policy.coverage}; 480 }; 481 ''; 482in 483{ 484 # options are ordered alphanumerically 485 options.services.nsd = { 486 487 enable = mkEnableOption "NSD authoritative DNS server"; 488 489 bind8Stats = mkEnableOption "BIND8 like statistics"; 490 491 dnssecInterval = mkOption { 492 type = types.str; 493 default = "1h"; 494 description = '' 495 How often to check whether dnssec key rollover is required 496 ''; 497 }; 498 499 extraConfig = mkOption { 500 type = types.lines; 501 default = ""; 502 description = '' 503 Extra nsd config. 504 ''; 505 }; 506 507 hideVersion = mkOption { 508 type = types.bool; 509 default = true; 510 description = '' 511 Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries. 512 ''; 513 }; 514 515 identity = mkOption { 516 type = types.str; 517 default = "unidentified server"; 518 description = '' 519 Identify the server (CH TXT ID.SERVER entry). 520 ''; 521 }; 522 523 interfaces = mkOption { 524 type = types.listOf types.str; 525 default = [ "127.0.0.0" "::1" ]; 526 description = '' 527 What addresses the server should listen to. 528 ''; 529 }; 530 531 ipFreebind = mkOption { 532 type = types.bool; 533 default = false; 534 description = '' 535 Whether to bind to nonlocal addresses and interfaces that are down. 536 Similar to ip-transparent. 537 ''; 538 }; 539 540 ipTransparent = mkOption { 541 type = types.bool; 542 default = false; 543 description = '' 544 Allow binding to non local addresses. 545 ''; 546 }; 547 548 ipv4 = mkOption { 549 type = types.bool; 550 default = true; 551 description = '' 552 Whether to listen on IPv4 connections. 553 ''; 554 }; 555 556 ipv4EDNSSize = mkOption { 557 type = types.int; 558 default = 4096; 559 description = '' 560 Preferred EDNS buffer size for IPv4. 561 ''; 562 }; 563 564 ipv6 = mkOption { 565 type = types.bool; 566 default = true; 567 description = '' 568 Whether to listen on IPv6 connections. 569 ''; 570 }; 571 572 ipv6EDNSSize = mkOption { 573 type = types.int; 574 default = 4096; 575 description = '' 576 Preferred EDNS buffer size for IPv6. 577 ''; 578 }; 579 580 logTimeAscii = mkOption { 581 type = types.bool; 582 default = true; 583 description = '' 584 Log time in ascii, if false then in unix epoch seconds. 585 ''; 586 }; 587 588 nsid = mkOption { 589 type = types.nullOr types.str; 590 default = null; 591 description = '' 592 NSID identity (hex string, or "ascii_somestring"). 593 ''; 594 }; 595 596 port = mkOption { 597 type = types.port; 598 default = 53; 599 description = '' 600 Port the service should bind do. 601 ''; 602 }; 603 604 reuseport = mkOption { 605 type = types.bool; 606 default = pkgs.stdenv.isLinux; 607 defaultText = literalExpression "pkgs.stdenv.isLinux"; 608 description = '' 609 Whether to enable SO_REUSEPORT on all used sockets. This lets multiple 610 processes bind to the same port. This speeds up operation especially 611 if the server count is greater than one and makes fast restarts less 612 prone to fail 613 ''; 614 }; 615 616 rootServer = mkOption { 617 type = types.bool; 618 default = false; 619 description = '' 620 Whether this server will be a root server (a DNS root server, you 621 usually don't want that). 622 ''; 623 }; 624 625 roundRobin = mkEnableOption "round robin rotation of records"; 626 627 serverCount = mkOption { 628 type = types.int; 629 default = 1; 630 description = '' 631 Number of NSD servers to fork. Put the number of CPUs to use here. 632 ''; 633 }; 634 635 statistics = mkOption { 636 type = types.nullOr types.int; 637 default = null; 638 description = '' 639 Statistics are produced every number of seconds. Prints to log. 640 If null no statistics are logged. 641 ''; 642 }; 643 644 tcpCount = mkOption { 645 type = types.int; 646 default = 100; 647 description = '' 648 Maximum number of concurrent TCP connections per server. 649 ''; 650 }; 651 652 tcpQueryCount = mkOption { 653 type = types.int; 654 default = 0; 655 description = '' 656 Maximum number of queries served on a single TCP connection. 657 0 means no maximum. 658 ''; 659 }; 660 661 tcpTimeout = mkOption { 662 type = types.int; 663 default = 120; 664 description = '' 665 TCP timeout in seconds. 666 ''; 667 }; 668 669 verbosity = mkOption { 670 type = types.int; 671 default = 0; 672 description = '' 673 Verbosity level. 674 ''; 675 }; 676 677 version = mkOption { 678 type = types.nullOr types.str; 679 default = null; 680 description = '' 681 The version string replied for CH TXT version.server and version.bind 682 queries. Will use the compiled package version on null. 683 See hideVersion for enabling/disabling this responses. 684 ''; 685 }; 686 687 xfrdReloadTimeout = mkOption { 688 type = types.int; 689 default = 1; 690 description = '' 691 Number of seconds between reloads triggered by xfrd. 692 ''; 693 }; 694 695 zonefilesCheck = mkOption { 696 type = types.bool; 697 default = true; 698 description = '' 699 Whether to check mtime of all zone files on start and sighup. 700 ''; 701 }; 702 703 zonefilesWrite = mkOption { 704 type = types.int; 705 default = 0; 706 description = '' 707 Write changed secondary zones to their zonefile every N seconds. 708 If the zone (pattern) configuration has "" zonefile, it is not written. 709 Zones that have received zone transfer updates are written to their zonefile. 710 0 disables writing to zone files. 711 ''; 712 }; 713 714 715 keys = mkOption { 716 type = types.attrsOf (types.submodule { 717 options = { 718 719 algorithm = mkOption { 720 type = types.str; 721 default = "hmac-sha256"; 722 description = '' 723 Authentication algorithm for this key. 724 ''; 725 }; 726 727 keyFile = mkOption { 728 type = types.path; 729 description = '' 730 Path to the file which contains the actual base64 encoded 731 key. The key will be copied into "${stateDir}/private" before 732 NSD starts. The copied file is only accessibly by the NSD 733 user. 734 ''; 735 }; 736 737 }; 738 }); 739 default = {}; 740 example = literalExpression '' 741 { "tsig.example.org" = { 742 algorithm = "hmac-md5"; 743 keyFile = "/path/to/my/key"; 744 }; 745 } 746 ''; 747 description = '' 748 Define your TSIG keys here. 749 ''; 750 }; 751 752 753 ratelimit = { 754 755 enable = mkEnableOption "ratelimit capabilities"; 756 757 ipv4PrefixLength = mkOption { 758 type = types.nullOr types.int; 759 default = null; 760 description = '' 761 IPv4 prefix length. Addresses are grouped by netblock. 762 ''; 763 }; 764 765 ipv6PrefixLength = mkOption { 766 type = types.nullOr types.int; 767 default = null; 768 description = '' 769 IPv6 prefix length. Addresses are grouped by netblock. 770 ''; 771 }; 772 773 ratelimit = mkOption { 774 type = types.int; 775 default = 200; 776 description = '' 777 Max qps allowed from any query source. 778 0 means unlimited. With an verbosity of 2 blocked and 779 unblocked subnets will be logged. 780 ''; 781 }; 782 783 slip = mkOption { 784 type = types.nullOr types.int; 785 default = null; 786 description = '' 787 Number of packets that get discarded before replying a SLIP response. 788 0 disables SLIP responses. 1 will make every response a SLIP response. 789 ''; 790 }; 791 792 size = mkOption { 793 type = types.int; 794 default = 1000000; 795 description = '' 796 Size of the hashtable. More buckets use more memory but lower 797 the chance of hash hash collisions. 798 ''; 799 }; 800 801 whitelistRatelimit = mkOption { 802 type = types.int; 803 default = 2000; 804 description = '' 805 Max qps allowed from whitelisted sources. 806 0 means unlimited. Set the rrl-whitelist option for specific 807 queries to apply this limit instead of the default to them. 808 ''; 809 }; 810 811 }; 812 813 814 remoteControl = { 815 816 enable = mkEnableOption "remote control via nsd-control"; 817 818 controlCertFile = mkOption { 819 type = types.path; 820 default = "/etc/nsd/nsd_control.pem"; 821 description = '' 822 Path to the client certificate signed with the server certificate. 823 This file is used by nsd-control and generated by nsd-control-setup. 824 ''; 825 }; 826 827 controlKeyFile = mkOption { 828 type = types.path; 829 default = "/etc/nsd/nsd_control.key"; 830 description = '' 831 Path to the client private key, which is used by nsd-control 832 but not by the server. This file is generated by nsd-control-setup. 833 ''; 834 }; 835 836 interfaces = mkOption { 837 type = types.listOf types.str; 838 default = [ "127.0.0.1" "::1" ]; 839 description = '' 840 Which interfaces NSD should bind to for remote control. 841 ''; 842 }; 843 844 port = mkOption { 845 type = types.port; 846 default = 8952; 847 description = '' 848 Port number for remote control operations (uses TLS over TCP). 849 ''; 850 }; 851 852 serverCertFile = mkOption { 853 type = types.path; 854 default = "/etc/nsd/nsd_server.pem"; 855 description = '' 856 Path to the server self signed certificate, which is used by the server 857 but and by nsd-control. This file is generated by nsd-control-setup. 858 ''; 859 }; 860 861 serverKeyFile = mkOption { 862 type = types.path; 863 default = "/etc/nsd/nsd_server.key"; 864 description = '' 865 Path to the server private key, which is used by the server 866 but not by nsd-control. This file is generated by nsd-control-setup. 867 ''; 868 }; 869 870 }; 871 872 zones = mkOption { 873 type = types.attrsOf zoneOptions; 874 default = {}; 875 example = literalExpression '' 876 { "serverGroup1" = { 877 provideXFR = [ "10.1.2.3 NOKEY" ]; 878 children = { 879 "example.com." = { 880 data = ''' 881 $ORIGIN example.com. 882 $TTL 86400 883 @ IN SOA a.ns.example.com. admin.example.com. ( 884 ... 885 '''; 886 }; 887 "example.org." = { 888 data = ''' 889 $ORIGIN example.org. 890 $TTL 86400 891 @ IN SOA a.ns.example.com. admin.example.com. ( 892 ... 893 '''; 894 }; 895 }; 896 }; 897 898 "example.net." = { 899 provideXFR = [ "10.3.2.1 NOKEY" ]; 900 data = ''' 901 ... 902 '''; 903 }; 904 } 905 ''; 906 description = '' 907 Define your zones here. Zones can cascade other zones and therefore 908 inherit settings from parent zones. Look at the definition of 909 children to learn about inheritance and child zones. 910 The given example will define 3 zones (example.(com|org|net).). Both 911 example.com. and example.org. inherit their configuration from 912 serverGroup1. 913 ''; 914 }; 915 }; 916 917 config = mkIf cfg.enable { 918 919 assertions = singleton { 920 assertion = zoneConfigs ? "." -> cfg.rootServer; 921 message = "You have a root zone configured. If this is really what you " 922 + "want, please enable 'services.nsd.rootServer'."; 923 }; 924 925 environment = { 926 systemPackages = [ nsdPkg ]; 927 etc."nsd/nsd.conf".source = "${configFile}/nsd.conf"; 928 }; 929 930 users.groups.${username}.gid = config.ids.gids.nsd; 931 932 users.users.${username} = { 933 description = "NSD service user"; 934 home = stateDir; 935 createHome = true; 936 uid = config.ids.uids.nsd; 937 group = username; 938 }; 939 940 systemd.services.nsd = { 941 description = "NSD authoritative only domain name service"; 942 943 after = [ "network.target" ]; 944 wantedBy = [ "multi-user.target" ]; 945 946 startLimitBurst = 4; 947 startLimitIntervalSec = 5 * 60; # 5 mins 948 serviceConfig = { 949 ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf"; 950 StandardError = "null"; 951 PIDFile = pidFile; 952 Restart = "always"; 953 RestartSec = "4s"; 954 }; 955 956 preStart = '' 957 rm -Rf "${stateDir}/private/" 958 rm -Rf "${stateDir}/tmp/" 959 960 install -dm 0700 -o "${username}" -g "${username}" "${stateDir}/private" 961 install -dm 0700 -o "${username}" -g "${username}" "${stateDir}/tmp" 962 install -dm 0700 -o "${username}" -g "${username}" "${stateDir}/var" 963 964 cat > "${stateDir}/don't touch anything in here" << EOF 965 Everything in this directory except NSD's state in var and dnssec 966 is automatically generated and will be purged and redeployed by 967 the nsd.service pre-start script. 968 EOF 969 970 rm -rf "${stateDir}/zones" 971 cp -rL "${nsdEnv}/zones" "${stateDir}/zones" 972 973 ${copyKeys} 974 ''; 975 }; 976 977 systemd.timers.nsd-dnssec = mkIf dnssec { 978 description = "Automatic DNSSEC key rollover"; 979 980 wantedBy = [ "nsd.service" ]; 981 982 timerConfig = { 983 OnActiveSec = cfg.dnssecInterval; 984 OnUnitActiveSec = cfg.dnssecInterval; 985 }; 986 }; 987 988 systemd.services.nsd-dnssec = mkIf dnssec { 989 description = "DNSSEC key rollover"; 990 991 wantedBy = [ "nsd.service" ]; 992 before = [ "nsd.service" ]; 993 994 script = signZones; 995 996 postStop = '' 997 /run/current-system/systemd/bin/systemctl kill -s SIGHUP nsd.service 998 ''; 999 }; 1000 1001 }; 1002 1003 meta.maintainers = with lib.maintainers; [ hrdinka ]; 1004}