1 { config, lib, pkgs, utils, ... }:
34 cfg = config.services.knot;
37 result = assert secsCheck; nix2yaml cfg.settings;
39 secAllow = n: hasPrefix "mod-" n || elem n [
41 "server" "xdp" "control"
43 "statistics" "database"
44 "keystore" "key" "remote" "remotes" "acl" "submission" "policy"
50 secsBad = filter (n: !secAllow n) (attrNames cfg.settings);
51 in if secsBad == [] then true else throw
52 ("services.knot.settings contains unknown sections: " + toString secsBad);
54 nix2yaml = nix_def: concatStrings (
55 # We output the config section in the upstream-mandated order.
56 # Ordering is important due to forward-references not being allowed.
57 # See definition of conf_export and 'const yp_item_t conf_schema'
58 # upstream for reference. Last updated for 3.3.
59 # When changing the set of sections, also update secAllow above.
60 [ (sec_list_fa "id" nix_def "module") ]
61 ++ map (sec_plain nix_def)
62 [ "server" "xdp" "control" ]
63 ++ [ (sec_list_fa "target" nix_def "log") ]
64 ++ map (sec_plain nix_def)
65 [ "statistics" "database" ]
66 ++ map (sec_list_fa "id" nix_def)
67 [ "keystore" "key" "remote" "remotes" "acl" "submission" "policy" ]
69 # Export module sections before the template section.
70 ++ map (sec_list_fa "id" nix_def) (filter (hasPrefix "mod-") (attrNames nix_def))
72 ++ [ (sec_list_fa "id" nix_def "template") ]
73 ++ [ (sec_list_fa "domain" nix_def "zone") ]
74 ++ [ (sec_plain nix_def "include") ]
75 ++ [ (sec_plain nix_def "clear") ]
78 # A plain section contains directly attributes (we don't really check that ATM).
79 sec_plain = nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
80 n2y "" { ${sec_name} = nix_def.${sec_name}; };
82 # This section contains a list of attribute sets. In each of the sets
83 # there's an attribute (`fa_name`, typically "id") that must exist and come first.
84 # Alternatively we support using attribute sets instead of lists; example diff:
85 # -template = [ { id = "default"; /* other attributes */ } { id = "foo"; } ]
86 # +template = { default = { /* those attributes */ }; foo = { }; }
87 sec_list_fa = fa_name: nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
89 elem2yaml = fa_val: other_attrs:
90 " - " + n2y "" { ${fa_name} = fa_val; }
91 + " " + n2y " " other_attrs
93 sec = nix_def.${sec_name};
97 then flip concatMapStrings sec
98 (elem: elem2yaml elem.${fa_name} (removeAttrs elem [ fa_name ]))
99 else concatStrings (mapAttrsToList elem2yaml sec)
102 # This convertor doesn't care about ordering of attributes.
103 # TODO: it could probably be simplified even more, now that it's not
104 # to be used directly, but we might want some other tweaks, too.
106 if doRecurse val then concatStringsSep "\n${indent}"
108 # This is a bit wacky - set directly under a set would start on bad indent,
109 # so we start those on a new line, but not other types of attribute values.
110 (aname: aval: "${aname}:${if doRecurse aval then "\n${indent} " else " "}"
111 + n2y (indent + " ") aval)
117 if isList val && stringLength indent < 4 then concatMapStrings
118 (elem: "\n${indent}- " + n2y (indent + " ") elem)
122 if isList val /* and long indent */ then
123 "[ " + concatMapStringsSep ", " quoteString val + " ]" else
124 if isBool val then (if val then "on" else "off") else
127 # We don't want paths like ./my-zone.txt be converted to plain strings.
128 quoteString = s: ''"${if builtins.typeOf s == "path" then s else toString s}"'';
129 # We don't want to walk the insides of derivation attributes.
130 doRecurse = val: isAttrs val && !isDerivation val;
134 configFile = if cfg.settingsFile != null then
135 # Note: with extraConfig, the 23.05 compat code did include keyFiles from settingsFile.
136 assert cfg.settings == {} && (cfg.keyFiles == [] || cfg.extraConfig != null);
139 mkConfigFile yamlConfig;
141 mkConfigFile = configString: pkgs.writeTextFile {
143 text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + configString;
144 checkPhase = lib.optionalString cfg.checkConfig ''
145 ${cfg.package}/bin/knotc --config=$out conf-check
149 socketFile = "/run/knot/knot.sock";
151 knot-cli-wrappers = pkgs.stdenv.mkDerivation {
152 name = "knot-cli-wrappers";
153 nativeBuildInputs = [ pkgs.makeWrapper ];
156 makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
157 --add-flags "--config=${configFile}" \
158 --add-flags "--socket=${socketFile}"
159 makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \
160 --add-flags "--config=${configFile}"
161 for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck
163 ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable"
165 mkdir -p "$out/share"
166 ln -s '${cfg.package}/share/man' "$out/share/"
172 enable = mkEnableOption "Knot authoritative-only DNS server";
174 enableXDP = mkOption {
176 default = lib.hasAttrByPath [ "xdp" "listen" ] cfg.settings;
178 Enabled when the `xdp.listen` setting is configured through `settings`.
182 Extends the systemd unit with permissions to allow for the use of
183 the eXpress Data Path (XDP).
186 Make sure to read up on functional [limitations](https://www.knot-dns.cz/docs/latest/singlehtml/index.html#mode-xdp-limitations)
187 when running in XDP mode.
192 checkConfig = mkOption {
194 # TODO: maybe we could do some checks even when private keys complicate this?
195 # conf-check fails hard on missing IPs/devices with XDP
196 default = cfg.keyFiles == [] && !cfg.enableXDP;
198 Disabled when the config uses `keyFiles` or `enableXDP`.
202 Toggles the configuration test at build time. It runs in a
203 sandbox, and therefore cannot be used in all scenarios.
207 extraArgs = mkOption {
208 type = types.listOf types.str;
211 List of additional command line parameters for knotd
215 keyFiles = mkOption {
216 type = types.listOf types.path;
219 A list of files containing additional configuration
220 to be included using the include directive. This option
221 allows to include configuration like TSIG keys without
222 exposing them to the nix store readable to any process.
223 Note that using this option will also disable configuration
224 checks at build time.
228 settings = mkOption {
229 type = (pkgs.formats.yaml {}).type;
232 Extra configuration as nix values.
236 settingsFile = mkOption {
237 type = types.nullOr types.path;
240 As alternative to ``settings``, you can provide whole configuration
241 directly in the almost-YAML format of Knot DNS.
242 You might want to utilize ``pkgs.writeText "knot.conf" "longConfigString"`` for this.
246 package = mkPackageOption pkgs "knot-dns" { };
250 # Compatibility with NixOS 23.05.
251 (mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ]
252 (config: mkConfigFile config.services.knot.extraConfig)
256 config = mkIf config.services.knot.enable {
257 users.groups.knot = {};
261 description = "Knot daemon user";
264 environment.etc."knot/knot.conf".source = configFile; # just for user's convenience
266 systemd.services.knot = {
267 unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
268 description = cfg.package.meta.description;
269 wantedBy = [ "multi-user.target" ];
270 wants = [ "network.target" ];
271 after = ["network.target" ];
274 # https://www.knot-dns.cz/docs/3.3/singlehtml/index.html#pre-requisites
275 xdpCapabilities = lib.optionals (cfg.enableXDP) [
280 ] ++ lib.optionals (lib.versionOlder config.boot.kernelPackages.kernel.version "5.11") [
285 ExecStart = escapeSystemdExecArgs ([
286 (lib.getExe cfg.package)
287 "--config=${configFile}"
288 "--socket=${socketFile}"
290 ExecReload = escapeSystemdExecArgs [
291 "${knot-cli-wrappers}/bin/knotc" "reload"
296 AmbientCapabilities = [
297 "CAP_NET_BIND_SERVICE"
298 ] ++ xdpCapabilities;
299 CapabilityBoundingSet = [
300 "CAP_NET_BIND_SERVICE"
301 ] ++ xdpCapabilities;
303 DevicePolicy = "closed";
304 LockPersonality = true;
305 MemoryDenyWriteExecute = true;
306 NoNewPrivileges = true;
307 PrivateDevices = true;
309 PrivateUsers = false; # breaks capability passing
312 ProtectControlGroups = true;
314 ProtectHostname = true;
315 ProtectKernelLogs = true;
316 ProtectKernelModules = true;
317 ProtectKernelTunables = true;
318 ProtectProc = "invisible";
319 ProtectSystem = "strict";
321 Restart = "on-abort";
322 RestrictAddressFamilies = [
326 ] ++ optionals (cfg.enableXDP) [
330 RestrictNamespaces = true;
331 RestrictRealtime =true;
332 RestrictSUIDSGID = true;
333 RuntimeDirectory = "knot";
334 StateDirectory = "knot";
335 StateDirectoryMode = "0700";
336 SystemCallArchitectures = "native";
340 ] ++ optionals (cfg.enableXDP) [
347 environment.systemPackages = [ knot-cli-wrappers ];