39 cfg = config.services.knot;
45 nix2yaml cfg.settings;
71 secsBad = filter (n: !secAllow n) (attrNames cfg.settings);
73 if secsBad == [ ] then
76 throw ("services.knot.settings contains unknown sections: " + toString secsBad);
81 # We output the config section in the upstream-mandated order.
82 # Ordering is important due to forward-references not being allowed.
83 # See definition of conf_export and 'const yp_item_t conf_schema'
84 # upstream for reference. Last updated for 3.3.
85 # When changing the set of sections, also update secAllow above.
86 [ (sec_list_fa "id" nix_def "module") ]
87 ++ map (sec_plain nix_def) [
92 ++ [ (sec_list_fa "target" nix_def "log") ]
93 ++ map (sec_plain nix_def) [
97 ++ map (sec_list_fa "id" nix_def) [
107 # Export module sections before the template section.
108 ++ map (sec_list_fa "id" nix_def) (filter (hasPrefix "mod-") (attrNames nix_def))
110 ++ [ (sec_list_fa "id" nix_def "template") ]
111 ++ [ (sec_list_fa "domain" nix_def "zone") ]
112 ++ [ (sec_plain nix_def "include") ]
113 ++ [ (sec_plain nix_def "clear") ]
116 # A plain section contains directly attributes (we don't really check that ATM).
119 if !hasAttr sec_name nix_def then "" else n2y "" { ${sec_name} = nix_def.${sec_name}; };
121 # This section contains a list of attribute sets. In each of the sets
122 # there's an attribute (`fa_name`, typically "id") that must exist and come first.
123 # Alternatively we support using attribute sets instead of lists; example diff:
124 # -template = [ { id = "default"; /* other attributes */ } { id = "foo"; } ]
125 # +template = { default = { /* those attributes */ }; foo = { }; }
127 fa_name: nix_def: sec_name:
128 if !hasAttr sec_name nix_def then
134 " - " + n2y "" { ${fa_name} = fa_val; } + " " + n2y " " other_attrs + "\n";
135 sec = nix_def.${sec_name};
141 flip concatMapStrings sec (elem: elem2yaml elem.${fa_name} (removeAttrs elem [ fa_name ]))
143 concatStrings (mapAttrsToList elem2yaml sec)
146 # This convertor doesn't care about ordering of attributes.
147 # TODO: it could probably be simplified even more, now that it's not
148 # to be used directly, but we might want some other tweaks, too.
151 if doRecurse val then
152 concatStringsSep "\n${indent}" (
154 # This is a bit wacky - set directly under a set would start on bad indent,
155 # so we start those on a new line, but not other types of attribute values.
158 "${aname}:${if doRecurse aval then "\n${indent} " else " "}" + n2y (indent + " ") aval
165 if isList val && stringLength indent < 4 then concatMapStrings
166 (elem: "\n${indent}- " + n2y (indent + " ") elem)
171 isList val # and long indent
173 "[ " + concatMapStringsSep ", " quoteString val + " ]"
174 else if isBool val then
175 (if val then "on" else "off")
179 # We don't want paths like ./my-zone.txt be converted to plain strings.
180 quoteString = s: ''"${if builtins.typeOf s == "path" then s else toString s}"'';
181 # We don't want to walk the insides of derivation attributes.
182 doRecurse = val: isAttrs val && !isDerivation val;
188 if cfg.settingsFile != null then
189 # Note: with extraConfig, the 23.05 compat code did include keyFiles from settingsFile.
190 assert cfg.settings == { } && (cfg.keyFiles == [ ] || cfg.extraConfig != null);
193 mkConfigFile yamlConfig;
199 text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + configString;
200 checkPhase = lib.optionalString cfg.checkConfig ''
201 ${cfg.package}/bin/knotc --config=$out conf-check
205 socketFile = "/run/knot/knot.sock";
207 knot-cli-wrappers = pkgs.stdenv.mkDerivation {
208 name = "knot-cli-wrappers";
209 nativeBuildInputs = [ pkgs.makeWrapper ];
212 makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
213 --add-flags "--config=${configFile}" \
214 --add-flags "--socket=${socketFile}"
215 makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \
216 --add-flags "--config=${configFile}"
217 makeWrapper ${cfg.package}/bin/kzonesign "$out/bin/kzonesign" \
218 --add-flags "--config=${configFile}"
219 makeWrapper ${cfg.package}/bin/kcatalogprint "$out/bin/kcatalogprint" \
220 --add-flags "--config=${configFile}"
221 for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck kxdpgun
223 ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable"
225 mkdir -p "$out/share"
226 ln -s '${cfg.package}/share/man' "$out/share/"
233 enable = mkEnableOption "Knot authoritative-only DNS server";
235 enableXDP = mkOption {
237 default = lib.hasAttrByPath [ "xdp" "listen" ] cfg.settings;
239 Enabled when the `xdp.listen` setting is configured through `settings`.
243 Extends the systemd unit with permissions to allow for the use of
244 the eXpress Data Path (XDP).
247 Make sure to read up on functional [limitations](https://www.knot-dns.cz/docs/latest/singlehtml/index.html#mode-xdp-limitations)
248 when running in XDP mode.
253 checkConfig = mkOption {
255 # TODO: maybe we could do some checks even when private keys complicate this?
256 # conf-check fails hard on missing IPs/devices with XDP
257 default = cfg.keyFiles == [ ] && !cfg.enableXDP;
259 Disabled when the config uses `keyFiles` or `enableXDP`.
263 Toggles the configuration test at build time. It runs in a
264 sandbox, and therefore cannot be used in all scenarios.
268 extraArgs = mkOption {
269 type = types.listOf types.str;
272 List of additional command line parameters for knotd
276 keyFiles = mkOption {
277 type = types.listOf types.path;
280 A list of files containing additional configuration
281 to be included using the include directive. This option
282 allows to include configuration like TSIG keys without
283 exposing them to the nix store readable to any process.
284 Note that using this option will also disable configuration
285 checks at build time.
289 settings = mkOption {
290 type = (pkgs.formats.yaml { }).type;
293 Extra configuration as nix values.
297 settingsFile = mkOption {
298 type = types.nullOr types.path;
301 As alternative to ``settings``, you can provide whole configuration
302 directly in the almost-YAML format of Knot DNS.
303 You might want to utilize ``pkgs.writeText "knot.conf" "longConfigString"`` for this.
307 package = mkPackageOption pkgs "knot-dns" { };
311 # Compatibility with NixOS 23.05.
312 (mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ] (
313 config: mkConfigFile config.services.knot.extraConfig
317 config = mkIf config.services.knot.enable {
318 users.groups.knot = { };
322 description = "Knot daemon user";
325 environment.etc."knot/knot.conf".source = configFile; # just for user's convenience
327 systemd.services.knot = {
328 unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
329 description = cfg.package.meta.description;
330 wantedBy = [ "multi-user.target" ];
331 wants = [ "network.target" ];
332 after = [ "network.target" ];
336 # https://www.knot-dns.cz/docs/3.3/singlehtml/index.html#pre-requisites
338 lib.optionals (cfg.enableXDP) [
344 ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "5.11") [
350 ExecStart = escapeSystemdExecArgs (
352 (lib.getExe cfg.package)
353 "--config=${configFile}"
354 "--socket=${socketFile}"
358 ExecReload = escapeSystemdExecArgs [
359 "${knot-cli-wrappers}/bin/knotc"
365 AmbientCapabilities = [
366 "CAP_NET_BIND_SERVICE"
367 ] ++ xdpCapabilities;
368 CapabilityBoundingSet = [
369 "CAP_NET_BIND_SERVICE"
370 ] ++ xdpCapabilities;
372 DevicePolicy = "closed";
373 LockPersonality = true;
374 MemoryDenyWriteExecute = true;
375 NoNewPrivileges = true;
376 PrivateDevices = true;
378 PrivateUsers = false; # breaks capability passing
381 ProtectControlGroups = true;
383 ProtectHostname = true;
384 ProtectKernelLogs = true;
385 ProtectKernelModules = true;
386 ProtectKernelTunables = true;
387 ProtectProc = "invisible";
388 ProtectSystem = "strict";
390 Restart = "on-abort";
391 RestrictAddressFamilies =
397 ++ optionals (cfg.enableXDP) [
401 RestrictNamespaces = true;
402 RestrictRealtime = true;
403 RestrictSUIDSGID = true;
404 RuntimeDirectory = "knot";
405 StateDirectory = "knot";
406 StateDirectoryMode = "0700";
407 SystemCallArchitectures = "native";
414 ++ optionals (cfg.enableXDP) [
421 environment.systemPackages = [ knot-cli-wrappers ];