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