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