python312Packages.aiohomeconnect: 0.10.0 -> 0.11.0 (#374011)
[NixPkgs.git] / nixos / modules / services / networking / nsd.nix
blobbc6aa5ae3fffbf5940c5207dcdd41b497d7a0e3b
2   config,
3   pkgs,
4   lib,
5   ...
6 }:
8 with lib;
10 let
11   cfg = config.services.nsd;
13   username = "nsd";
14   stateDir = "/var/lib/nsd";
15   pidFile = stateDir + "/var/nsd.pid";
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   };
26   mkZoneFileName = name: if name == "." then "root" else name;
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     );
43   nsdEnv = pkgs.buildEnv {
44     name = "nsd-env";
46     paths = [ configFile ] ++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
48     postBuild = ''
49       echo "checking zone files"
50       cd $out/zones
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
61           exit 1
62         }
63       done
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   };
77   writeZoneData =
78     name: text:
79     pkgs.writeTextFile {
80       name = "nsd-zone-${mkZoneFileName name}";
81       inherit text;
82       destination = "/zones/${mkZoneFileName name}";
83     };
85   # options are ordered alphanumerically by the nixos option name
86   configFile = pkgs.writeTextDir "nsd.conf" ''
87     server:
88       chroot:   "${stateDir}"
89       username: ${username}
91       # The directory for zonefile: files. The daemon chdirs here.
92       zonesdir: "${stateDir}"
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"
100       # interfaces
101     ${forEach "  ip-address: " cfg.interfaces}
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}
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}
134     ${keyConfigFile}
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}"
145     ${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)}
147     ${cfg.extraConfig}
148   '';
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;
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   );
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   );
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}
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}
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}
191     ${forEach "  notify: " zone.notify}
192       notify-retry:                        ${toString zone.notifyRetry}
193     ${forEach "  provide-xfr: " zone.provideXFR}
194   '';
196   zoneConfigs = zoneConfigs' { } "" { children = cfg.zones; };
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)) ]
206     # fork -> pattern
207     else
208       zipAttrsWith (name: head) (
209         mapAttrsToList (
210           name: child: zoneConfigs' (parent // zone // { children = { }; }) name child
211         ) zone.children
212       );
214   # options are ordered alphanumerically
215   zoneOptions = types.submodule {
216     options = {
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       };
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.
238           Format: `<ip> <key-name | NOKEY | BLOCKED>`
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
246           A optional port number could be added with a '@':
247           * `2001:1234::1@1234`
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       };
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       };
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       };
284       dnssec = mkEnableOption "DNSSEC";
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       };
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       };
337       minRefreshSecs = mkOption {
338         type = types.nullOr types.int;
339         default = null;
340         description = ''
341           Limit refresh time for secondary zones.
342         '';
343       };
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       };
355       minRetrySecs = mkOption {
356         type = types.nullOr types.int;
357         default = null;
358         description = ''
359           Limit retry time for secondary zones.
360         '';
361       };
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       };
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.
384           Format: `<ip> <key-name | NOKEY>`
386           `<ip>` a plain IPv4/IPv6 address with on optional port number (ip@port)
388           `<key-name | NOKEY>`
389           - `<key-name>` sign notifies with the specified key
390           - `NOKEY` don't sign notifies
391         '';
392       };
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       };
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       };
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       };
427       requestXFR = mkOption {
428         type = types.listOf types.str;
429         default = [ ];
430         description = ''
431           Format: `[AXFR|UDP] <ip-address> <key-name | NOKEY>`
432         '';
433       };
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       };
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   };
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   };
492   dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
494   dnssec = dnssecZones != { };
496   dnssecTools = pkgs.bind.override { enablePython = true; };
498   signZones = optionalString dnssec ''
499     install -m 0600 -o "${username}" -g "${username}" -d "${stateDir}/dnssec"
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     '';
527   # options are ordered alphanumerically
528   options.services.nsd = {
530     enable = mkEnableOption "NSD authoritative DNS server";
532     bind8Stats = mkEnableOption "BIND8 like statistics";
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     };
542     extraConfig = mkOption {
543       type = types.lines;
544       default = "";
545       description = ''
546         Extra nsd config.
547       '';
548     };
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     };
558     identity = mkOption {
559       type = types.str;
560       default = "unidentified server";
561       description = ''
562         Identify the server (CH TXT ID.SERVER entry).
563       '';
564     };
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     };
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     };
586     ipTransparent = mkOption {
587       type = types.bool;
588       default = false;
589       description = ''
590         Allow binding to non local addresses.
591       '';
592     };
594     ipv4 = mkOption {
595       type = types.bool;
596       default = true;
597       description = ''
598         Whether to listen on IPv4 connections.
599       '';
600     };
602     ipv4EDNSSize = mkOption {
603       type = types.int;
604       default = 4096;
605       description = ''
606         Preferred EDNS buffer size for IPv4.
607       '';
608     };
610     ipv6 = mkOption {
611       type = types.bool;
612       default = true;
613       description = ''
614         Whether to listen on IPv6 connections.
615       '';
616     };
618     ipv6EDNSSize = mkOption {
619       type = types.int;
620       default = 4096;
621       description = ''
622         Preferred EDNS buffer size for IPv6.
623       '';
624     };
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     };
634     nsid = mkOption {
635       type = types.nullOr types.str;
636       default = null;
637       description = ''
638         NSID identity (hex string, or "ascii_somestring").
639       '';
640     };
642     port = mkOption {
643       type = types.port;
644       default = 53;
645       description = ''
646         Port the service should bind do.
647       '';
648     };
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     };
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     };
671     roundRobin = mkEnableOption "round robin rotation of records";
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     };
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     };
690     tcpCount = mkOption {
691       type = types.int;
692       default = 100;
693       description = ''
694         Maximum number of concurrent TCP connections per server.
695       '';
696     };
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     };
707     tcpTimeout = mkOption {
708       type = types.int;
709       default = 120;
710       description = ''
711         TCP timeout in seconds.
712       '';
713     };
715     verbosity = mkOption {
716       type = types.int;
717       default = 0;
718       description = ''
719         Verbosity level.
720       '';
721     };
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     };
733     xfrdReloadTimeout = mkOption {
734       type = types.int;
735       default = 1;
736       description = ''
737         Number of seconds between reloads triggered by xfrd.
738       '';
739     };
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     };
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     };
760     keys = mkOption {
761       type = types.attrsOf (
762         types.submodule {
763           options = {
765             algorithm = mkOption {
766               type = types.str;
767               default = "hmac-sha256";
768               description = ''
769                 Authentication algorithm for this key.
770               '';
771             };
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             };
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     };
799     ratelimit = {
801       enable = mkEnableOption "ratelimit capabilities";
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       };
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       };
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       };
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       };
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       };
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       };
857     };
859     remoteControl = {
861       enable = mkEnableOption "remote control via nsd-control";
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       };
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       };
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       };
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       };
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       };
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       };
918     };
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           };
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   };
965   config = mkIf cfg.enable {
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     };
974     environment = {
975       systemPackages = [ nsdPkg ];
976       etc."nsd/nsd.conf".source = "${configFile}/nsd.conf";
977     };
979     users.groups.${username}.gid = config.ids.gids.nsd;
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     };
989     systemd.services.nsd = {
990       description = "NSD authoritative only domain name service";
992       after = [ "network.target" ];
993       wantedBy = [ "multi-user.target" ];
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       };
1005       preStart = ''
1006         rm -Rf "${stateDir}/private/"
1007         rm -Rf "${stateDir}/tmp/"
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"
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
1019         rm -rf "${stateDir}/zones"
1020         cp -rL "${nsdEnv}/zones" "${stateDir}/zones"
1022         ${copyKeys}
1023       '';
1024     };
1026     systemd.timers.nsd-dnssec = mkIf dnssec {
1027       description = "Automatic DNSSEC key rollover";
1029       wantedBy = [ "nsd.service" ];
1031       timerConfig = {
1032         OnActiveSec = cfg.dnssecInterval;
1033         OnUnitActiveSec = cfg.dnssecInterval;
1034       };
1035     };
1037     systemd.services.nsd-dnssec = mkIf dnssec {
1038       description = "DNSSEC key rollover";
1040       wantedBy = [ "nsd.service" ];
1041       before = [ "nsd.service" ];
1043       script = signZones;
1045       postStop = ''
1046         /run/current-system/systemd/bin/systemctl kill -s SIGHUP nsd.service
1047       '';
1048     };
1050   };
1052   meta.maintainers = with lib.maintainers; [ hrdinka ];