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