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