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&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] <ip-address> <key-name | NOKEY></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}