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