at v206 23 kB view raw
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 nsdPkg = pkgs.nsd.override { 13 bind8Stats = cfg.bind8Stats; 14 ipv6 = cfg.ipv6; 15 ratelimit = cfg.ratelimit.enable; 16 rootServer = cfg.rootServer; 17 zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0; 18 }; 19 20 zoneFiles = pkgs.stdenv.mkDerivation { 21 preferLocalBuild = true; 22 name = "nsd-env"; 23 buildCommand = concatStringsSep "\n" 24 [ "mkdir -p $out" 25 (concatStrings (mapAttrsToList (zoneName: zoneOptions: '' 26 cat > "$out/${zoneName}" <<_EOF_ 27 ${zoneOptions.data} 28 _EOF_ 29 '') zoneConfigs)) 30 ]; 31 }; 32 33 configFile = pkgs.writeText "nsd.conf" '' 34 server: 35 username: ${username} 36 chroot: "${stateDir}" 37 38 # The directory for zonefile: files. The daemon chdirs here. 39 zonesdir: "${stateDir}" 40 41 # the list of dynamically added zones. 42 zonelistfile: "${stateDir}/var/zone.list" 43 database: "${stateDir}/var/nsd.db" 44 pidfile: "${pidFile}" 45 xfrdfile: "${stateDir}/var/xfrd.state" 46 xfrdir: "${stateDir}/tmp" 47 48 # interfaces 49 ${forEach " ip-address: " cfg.interfaces} 50 51 server-count: ${toString cfg.serverCount} 52 ip-transparent: ${yesOrNo cfg.ipTransparent} 53 do-ip4: ${yesOrNo cfg.ipv4} 54 do-ip6: ${yesOrNo cfg.ipv6} 55 port: ${toString cfg.port} 56 verbosity: ${toString cfg.verbosity} 57 hide-version: ${yesOrNo cfg.hideVersion} 58 identity: "${cfg.identity}" 59 ${maybeString "nsid: " cfg.nsid} 60 tcp-count: ${toString cfg.tcpCount} 61 tcp-query-count: ${toString cfg.tcpQueryCount} 62 tcp-timeout: ${toString cfg.tcpTimeout} 63 ipv4-edns-size: ${toString cfg.ipv4EDNSSize} 64 ipv6-edns-size: ${toString cfg.ipv6EDNSSize} 65 ${if cfg.statistics == null then "" else "statistics: ${toString cfg.statistics}"} 66 xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout} 67 zonefiles-check: ${yesOrNo cfg.zonefilesCheck} 68 69 rrl-size: ${toString cfg.ratelimit.size} 70 rrl-ratelimit: ${toString cfg.ratelimit.ratelimit} 71 rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit} 72 ${maybeString "rrl-slip: " cfg.ratelimit.slip} 73 ${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength} 74 ${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength} 75 76 ${keyConfigFile} 77 78 remote-control: 79 control-enable: ${yesOrNo cfg.remoteControl.enable} 80 ${forEach " control-interface: " cfg.remoteControl.interfaces} 81 control-port: ${toString cfg.port} 82 server-key-file: "${cfg.remoteControl.serverKeyFile}" 83 server-cert-file: "${cfg.remoteControl.serverCertFile}" 84 control-key-file: "${cfg.remoteControl.controlKeyFile}" 85 control-cert-file: "${cfg.remoteControl.controlCertFile}" 86 87 # zone files reside in "${zoneFiles}" linked to "${stateDir}/zones" 88 ${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)} 89 90 ${cfg.extraConfig} 91 ''; 92 93 yesOrNo = b: if b then "yes" else "no"; 94 maybeString = pre: s: if s == null then "" else ''${pre} "${s}"''; 95 forEach = pre: l: concatMapStrings (x: pre + x + "\n") l; 96 97 98 keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: '' 99 key: 100 name: "${keyName}" 101 algorithm: "${keyOptions.algorithm}" 102 include: "${stateDir}/private/${keyName}" 103 '') cfg.keys); 104 105 copyKeys = concatStrings (mapAttrsToList (keyName: keyOptions: '' 106 secret=$(cat "${keyOptions.keyFile}") 107 dest="${stateDir}/private/${keyName}" 108 echo " secret: \"$secret\"" > "$dest" 109 ${pkgs.coreutils}/bin/chown ${username}:${username} "$dest" 110 ${pkgs.coreutils}/bin/chmod 0400 "$dest" 111 '') cfg.keys); 112 113 114 zoneConfigFile = name: zone: '' 115 zone: 116 name: "${name}" 117 zonefile: "${stateDir}/zones/${name}" 118 ${maybeString "zonestats: " zone.zoneStats} 119 ${maybeString "outgoing-interface: " zone.outgoingInterface} 120 ${forEach " rrl-whitelist: " zone.rrlWhitelist} 121 122 ${forEach " allow-notify: " zone.allowNotify} 123 ${forEach " request-xfr: " zone.requestXFR} 124 allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback} 125 126 ${forEach " notify: " zone.notify} 127 notify-retry: ${toString zone.notifyRetry} 128 ${forEach " provide-xfr: " zone.provideXFR} 129 ''; 130 131 zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; }; 132 133 zoneConfigs' = parent: name: zone: 134 if !(zone ? children) || zone.children == null || zone.children == { } 135 # leaf -> actual zone 136 then listToAttrs [ (nameValuePair name (parent // zone)) ] 137 138 # fork -> pattern 139 else zipAttrsWith (name: head) ( 140 mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child) 141 zone.children 142 ); 143 144 # fighting infinite recursion 145 zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true; 146 zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false; 147 zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false; 148 zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false; 149 zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false; 150 zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false; 151 zoneOptions6 = zoneOptionsRaw // childConfig null false; 152 153 childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; }; 154 155 zoneOptionsRaw = types.submodule { 156 options = { 157 children = mkOption { 158 default = {}; 159 description = '' 160 Children zones inherit all options of their parents. Attributes 161 defined in a child will overwrite the ones of its parent. Only 162 leaf zones will be actually served. This way it's possible to 163 define maybe zones which share most attributes without 164 duplicating everything. This mechanism replaces nsd's patterns 165 in a save and functional way. 166 ''; 167 }; 168 169 allowNotify = mkOption { 170 type = types.listOf types.str; 171 default = [ ]; 172 example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name" 173 "10.0.3.4&255.255.0.0 BLOCKED" 174 ]; 175 description = '' 176 Listed primary servers are allowed to notify this secondary server. 177 <screen><![CDATA[ 178 Format: <ip> <key-name | NOKEY | BLOCKED> 179 180 <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges: 181 * 10.0.0.0/24 # via subnet size 182 * 10.0.0.0&255.255.255.0 # via subnet mask 183 * 10.0.0.1-10.0.0.254 # via range 184 185 A optional port number could be added with a '@': 186 * 2001:1234::1@1234 187 188 <key-name | NOKEY | BLOCKED> 189 * <key-name> will use the specified TSIG key 190 * NOKEY no TSIG signature is required 191 * BLOCKED notifies from non-listed or blocked IPs will be ignored 192 * ]]></screen> 193 ''; 194 }; 195 196 requestXFR = mkOption { 197 type = types.listOf types.str; 198 default = []; 199 example = []; 200 description = '' 201 Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code> 202 ''; 203 }; 204 205 allowAXFRFallback = mkOption { 206 type = types.bool; 207 default = true; 208 description = '' 209 If NSD as secondary server should be allowed to AXFR if the primary 210 server does not allow IXFR. 211 ''; 212 }; 213 214 notify = mkOption { 215 type = types.listOf types.str; 216 default = []; 217 example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ]; 218 description = '' 219 This primary server will notify all given secondary servers about 220 zone changes. 221 <screen><![CDATA[ 222 Format: <ip> <key-name | NOKEY> 223 224 <ip> a plain IPv4/IPv6 address with on optional port number (ip@port) 225 226 <key-name | NOKEY> 227 * <key-name> sign notifies with the specified key 228 * NOKEY don't sign notifies 229 ]]></screen> 230 ''; 231 }; 232 233 notifyRetry = mkOption { 234 type = types.int; 235 default = 5; 236 description = '' 237 Specifies the number of retries for failed notifies. Set this along with notify. 238 ''; 239 }; 240 241 provideXFR = mkOption { 242 type = types.listOf types.str; 243 default = []; 244 example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ]; 245 description = '' 246 Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED 247 address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40 248 ''; 249 }; 250 251 outgoingInterface = mkOption { 252 type = types.nullOr types.str; 253 default = null; 254 example = "2000::1@1234"; 255 description = '' 256 This address will be used for zone-transfere requests if configured 257 as a secondary server or notifications in case of a primary server. 258 Supply either a plain IPv4 or IPv6 address with an optional port 259 number (ip@port). 260 ''; 261 }; 262 263 rrlWhitelist = mkOption { 264 type = types.listOf types.str; 265 default = []; 266 description = '' 267 Whitelists the given rrl-types. 268 The RRL classification types are: nxdomain, error, referral, any, 269 rrsig, wildcard, nodata, dnskey, positive, all 270 ''; 271 }; 272 273 data = mkOption { 274 type = types.str; 275 default = ""; 276 example = ""; 277 description = '' 278 The actual zone data. This is the content of your zone file. 279 Use imports or pkgs.lib.readFile if you don't want this data in your config file. 280 ''; 281 }; 282 283 zoneStats = mkOption { 284 type = types.nullOr types.str; 285 default = null; 286 example = "%s"; 287 description = '' 288 When set to something distinct to null NSD is able to collect 289 statistics per zone. All statistics of this zone(s) will be added 290 to the group specified by this given name. Use "%s" to use the zones 291 name as the group. The groups are output from nsd-control stats 292 and stats_noreset. 293 ''; 294 }; 295 }; 296 }; 297 298in 299{ 300 options = { 301 services.nsd = { 302 303 enable = mkOption { 304 type = types.bool; 305 default = false; 306 description = '' 307 Whether to enable the NSD authoritative domain name server. 308 ''; 309 }; 310 311 bind8Stats = mkOption { 312 type = types.bool; 313 default = false; 314 example = true; 315 description = '' 316 Wheter to enable BIND8 like statisics. 317 ''; 318 }; 319 320 rootServer = mkOption { 321 type = types.bool; 322 default = false; 323 description = '' 324 Wheter if this server will be a root server (a DNS root server, you 325 usually don't want that). 326 ''; 327 }; 328 329 interfaces = mkOption { 330 type = types.listOf types.str; 331 default = [ "127.0.0.0" "::1" ]; 332 description = '' 333 What addresses the server should listen to. 334 ''; 335 }; 336 337 serverCount = mkOption { 338 type = types.int; 339 default = 1; 340 description = '' 341 Number of NSD servers to fork. Put the number of CPUs to use here. 342 ''; 343 }; 344 345 ipTransparent = mkOption { 346 type = types.bool; 347 default = false; 348 description = '' 349 Allow binding to non local addresses. 350 ''; 351 }; 352 353 ipv4 = mkOption { 354 type = types.bool; 355 default = true; 356 description = '' 357 Wheter to listen on IPv4 connections. 358 ''; 359 }; 360 361 ipv6 = mkOption { 362 type = types.bool; 363 default = true; 364 description = '' 365 Wheter to listen on IPv6 connections. 366 ''; 367 }; 368 369 port = mkOption { 370 type = types.int; 371 default = 53; 372 description = '' 373 Port the service should bind do. 374 ''; 375 }; 376 377 verbosity = mkOption { 378 type = types.int; 379 default = 0; 380 description = '' 381 Verbosity level. 382 ''; 383 }; 384 385 hideVersion = mkOption { 386 type = types.bool; 387 default = true; 388 description = '' 389 Wheter NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries. 390 ''; 391 }; 392 393 identity = mkOption { 394 type = types.str; 395 default = "unidentified server"; 396 description = '' 397 Identify the server (CH TXT ID.SERVER entry). 398 ''; 399 }; 400 401 nsid = mkOption { 402 type = types.nullOr types.str; 403 default = null; 404 description = '' 405 NSID identity (hex string, or "ascii_somestring"). 406 ''; 407 }; 408 409 tcpCount = mkOption { 410 type = types.int; 411 default = 100; 412 description = '' 413 Maximum number of concurrent TCP connections per server. 414 ''; 415 }; 416 417 tcpQueryCount = mkOption { 418 type = types.int; 419 default = 0; 420 description = '' 421 Maximum number of queries served on a single TCP connection. 422 0 means no maximum. 423 ''; 424 }; 425 426 tcpTimeout = mkOption { 427 type = types.int; 428 default = 120; 429 description = '' 430 TCP timeout in seconds. 431 ''; 432 }; 433 434 ipv4EDNSSize = mkOption { 435 type = types.int; 436 default = 4096; 437 description = '' 438 Preferred EDNS buffer size for IPv4. 439 ''; 440 }; 441 442 ipv6EDNSSize = mkOption { 443 type = types.int; 444 default = 4096; 445 description = '' 446 Preferred EDNS buffer size for IPv6. 447 ''; 448 }; 449 450 statistics = mkOption { 451 type = types.nullOr types.int; 452 default = null; 453 description = '' 454 Statistics are produced every number of seconds. Prints to log. 455 If null no statistics are logged. 456 ''; 457 }; 458 459 xfrdReloadTimeout = mkOption { 460 type = types.int; 461 default = 1; 462 description = '' 463 Number of seconds between reloads triggered by xfrd. 464 ''; 465 }; 466 467 zonefilesCheck = mkOption { 468 type = types.bool; 469 default = true; 470 description = '' 471 Wheter to check mtime of all zone files on start and sighup. 472 ''; 473 }; 474 475 476 extraConfig = mkOption { 477 type = types.str; 478 default = ""; 479 description = '' 480 Extra nsd config. 481 ''; 482 }; 483 484 485 ratelimit = { 486 enable = mkOption { 487 type = types.bool; 488 default = false; 489 description = '' 490 Enable ratelimit capabilities. 491 ''; 492 }; 493 494 size = mkOption { 495 type = types.int; 496 default = 1000000; 497 description = '' 498 Size of the hashtable. More buckets use more memory but lower 499 the chance of hash hash collisions. 500 ''; 501 }; 502 503 ratelimit = mkOption { 504 type = types.int; 505 default = 200; 506 description = '' 507 Max qps allowed from any query source. 508 0 means unlimited. With an verbosity of 2 blocked and 509 unblocked subnets will be logged. 510 ''; 511 }; 512 513 whitelistRatelimit = mkOption { 514 type = types.int; 515 default = 2000; 516 description = '' 517 Max qps allowed from whitelisted sources. 518 0 means unlimited. Set the rrl-whitelist option for specific 519 queries to apply this limit instead of the default to them. 520 ''; 521 }; 522 523 slip = mkOption { 524 type = types.nullOr types.int; 525 default = null; 526 description = '' 527 Number of packets that get discarded before replying a SLIP response. 528 0 disables SLIP responses. 1 will make every response a SLIP response. 529 ''; 530 }; 531 532 ipv4PrefixLength = mkOption { 533 type = types.nullOr types.int; 534 default = null; 535 description = '' 536 IPv4 prefix length. Addresses are grouped by netblock. 537 ''; 538 }; 539 540 ipv6PrefixLength = mkOption { 541 type = types.nullOr types.int; 542 default = null; 543 description = '' 544 IPv6 prefix length. Addresses are grouped by netblock. 545 ''; 546 }; 547 }; 548 549 550 remoteControl = { 551 enable = mkOption { 552 type = types.bool; 553 default = false; 554 description = '' 555 Wheter to enable remote control via nsd-control(8). 556 ''; 557 }; 558 559 interfaces = mkOption { 560 type = types.listOf types.str; 561 default = [ "127.0.0.1" "::1" ]; 562 description = '' 563 Which interfaces NSD should bind to for remote control. 564 ''; 565 }; 566 567 port = mkOption { 568 type = types.int; 569 default = 8952; 570 description = '' 571 Port number for remote control operations (uses TLS over TCP). 572 ''; 573 }; 574 575 serverKeyFile = mkOption { 576 type = types.path; 577 default = "/etc/nsd/nsd_server.key"; 578 description = '' 579 Path to the server private key, which is used by the server 580 but not by nsd-control. This file is generated by nsd-control-setup. 581 ''; 582 }; 583 584 serverCertFile = mkOption { 585 type = types.path; 586 default = "/etc/nsd/nsd_server.pem"; 587 description = '' 588 Path to the server self signed certificate, which is used by the server 589 but and by nsd-control. This file is generated by nsd-control-setup. 590 ''; 591 }; 592 593 controlKeyFile = mkOption { 594 type = types.path; 595 default = "/etc/nsd/nsd_control.key"; 596 description = '' 597 Path to the client private key, which is used by nsd-control 598 but not by the server. This file is generated by nsd-control-setup. 599 ''; 600 }; 601 602 controlCertFile = mkOption { 603 type = types.path; 604 default = "/etc/nsd/nsd_control.pem"; 605 description = '' 606 Path to the client certificate signed with the server certificate. 607 This file is used by nsd-control and generated by nsd-control-setup. 608 ''; 609 }; 610 }; 611 612 613 keys = mkOption { 614 type = types.attrsOf (types.submodule { 615 options = { 616 algorithm = mkOption { 617 type = types.str; 618 default = "hmac-sha256"; 619 description = '' 620 Authentication algorithm for this key. 621 ''; 622 }; 623 624 keyFile = mkOption { 625 type = types.path; 626 description = '' 627 Path to the file which contains the actual base64 encoded 628 key. The key will be copied into "${stateDir}/private" before 629 NSD starts. The copied file is only accessibly by the NSD 630 user. 631 ''; 632 }; 633 }; 634 }); 635 default = {}; 636 example = { 637 "tsig.example.org" = { 638 algorithm = "hmac-md5"; 639 secret = "aaaaaabbbbbbccccccdddddd"; 640 }; 641 }; 642 description = '' 643 Define your TSIG keys here. 644 ''; 645 }; 646 647 zones = mkOption { 648 type = types.attrsOf zoneOptions; 649 default = {}; 650 example = { 651 "serverGroup1" = { 652 provideXFR = [ "10.1.2.3 NOKEY" ]; 653 children = { 654 "example.com." = { 655 data = '' 656 $ORIGIN example.com. 657 $TTL 86400 658 @ IN SOA a.ns.example.com. admin.example.com. ( 659 ... 660 ''; 661 }; 662 "example.org." = { 663 data = '' 664 $ORIGIN example.org. 665 $TTL 86400 666 @ IN SOA a.ns.example.com. admin.example.com. ( 667 ... 668 ''; 669 }; 670 }; 671 }; 672 673 "example.net." = { 674 provideXFR = [ "10.3.2.1 NOKEY" ]; 675 data = ''...''; 676 }; 677 }; 678 description = '' 679 Define your zones here. Zones can cascade other zones and therefore 680 inherit settings from parent zones. Look at the definition of 681 children to learn about inheritance and child zones. 682 The given example will define 3 zones (example.(com|org|net).). Both 683 example.com. and example.org. inherit their configuration from 684 serverGroup1. 685 ''; 686 }; 687 688 }; 689 }; 690 691 config = mkIf cfg.enable { 692 693 users.extraGroups = singleton { 694 name = username; 695 gid = config.ids.gids.nsd; 696 }; 697 698 users.extraUsers = singleton { 699 name = username; 700 description = "NSD service user"; 701 home = stateDir; 702 createHome = true; 703 uid = config.ids.uids.nsd; 704 group = username; 705 }; 706 707 systemd.services.nsd = { 708 description = "NSD authoritative only domain name service"; 709 wantedBy = [ "multi-user.target" ]; 710 after = [ "network.target" ]; 711 712 serviceConfig = { 713 PIDFile = pidFile; 714 Restart = "always"; 715 ExecStart = "${nsdPkg}/sbin/nsd -d -c ${configFile}"; 716 }; 717 718 preStart = '' 719 ${pkgs.coreutils}/bin/mkdir -m 0700 -p "${stateDir}/private" 720 ${pkgs.coreutils}/bin/mkdir -m 0700 -p "${stateDir}/tmp" 721 ${pkgs.coreutils}/bin/mkdir -m 0700 -p "${stateDir}/var" 722 723 ${pkgs.coreutils}/bin/touch "${stateDir}/don't touch anything in here" 724 725 ${pkgs.coreutils}/bin/rm -f "${stateDir}/private/"* 726 ${pkgs.coreutils}/bin/rm -f "${stateDir}/tmp/"* 727 728 ${pkgs.coreutils}/bin/chown nsd:nsd -R "${stateDir}/private" 729 ${pkgs.coreutils}/bin/chown nsd:nsd -R "${stateDir}/tmp" 730 ${pkgs.coreutils}/bin/chown nsd:nsd -R "${stateDir}/var" 731 732 ${pkgs.coreutils}/bin/rm -rf "${stateDir}/zones" 733 ${pkgs.coreutils}/bin/cp -r "${zoneFiles}" "${stateDir}/zones" 734 735 ${copyKeys} 736 ''; 737 }; 738 739 }; 740}