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