python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / networking / unbound.nix
blobfa24c70e63de3ab8c5220e326278c4ba1214063d
1 { config, lib, pkgs, ... }:
3 with lib;
4 let
5   cfg = config.services.unbound;
7   yesOrNo = v: if v then "yes" else "no";
9   toOption = indent: n: v: "${indent}${toString n}: ${v}";
11   toConf = indent: n: v:
12     if builtins.isFloat v then (toOption indent n (builtins.toJSON v))
13     else if isInt v       then (toOption indent n (toString v))
14     else if isBool v      then (toOption indent n (yesOrNo v))
15     else if isString v    then (toOption indent n v)
16     else if isList v      then (concatMapStringsSep "\n" (toConf indent n) v)
17     else if isAttrs v     then (concatStringsSep "\n" (
18                                   ["${indent}${n}:"] ++ (
19                                     mapAttrsToList (toConf "${indent}  ") v
20                                   )
21                                 ))
22     else throw (traceSeq v "services.unbound.settings: unexpected type");
24   confNoServer = concatStringsSep "\n" ((mapAttrsToList (toConf "") (builtins.removeAttrs cfg.settings [ "server" ])) ++ [""]);
25   confServer = concatStringsSep "\n" (mapAttrsToList (toConf "  ") (builtins.removeAttrs cfg.settings.server [ "define-tag" ]));
27   confFile = pkgs.writeText "unbound.conf" ''
28     server:
29     ${optionalString (cfg.settings.server.define-tag != "") (toOption "  " "define-tag" cfg.settings.server.define-tag)}
30     ${confServer}
31     ${confNoServer}
32   '';
34   rootTrustAnchorFile = "${cfg.stateDir}/root.key";
36 in {
38   ###### interface
40   options = {
41     services.unbound = {
43       enable = mkEnableOption (lib.mdDoc "Unbound domain name server");
45       package = mkOption {
46         type = types.package;
47         default = pkgs.unbound-with-systemd;
48         defaultText = literalExpression "pkgs.unbound-with-systemd";
49         description = lib.mdDoc "The unbound package to use";
50       };
52       user = mkOption {
53         type = types.str;
54         default = "unbound";
55         description = lib.mdDoc "User account under which unbound runs.";
56       };
58       group = mkOption {
59         type = types.str;
60         default = "unbound";
61         description = lib.mdDoc "Group under which unbound runs.";
62       };
64       stateDir = mkOption {
65         type = types.path;
66         default = "/var/lib/unbound";
67         description = lib.mdDoc "Directory holding all state for unbound to run.";
68       };
70       resolveLocalQueries = mkOption {
71         type = types.bool;
72         default = true;
73         description = lib.mdDoc ''
74           Whether unbound should resolve local queries (i.e. add 127.0.0.1 to
75           /etc/resolv.conf).
76         '';
77       };
79       enableRootTrustAnchor = mkOption {
80         default = true;
81         type = types.bool;
82         description = lib.mdDoc "Use and update root trust anchor for DNSSEC validation.";
83       };
85       localControlSocketPath = mkOption {
86         default = null;
87         # FIXME: What is the proper type here so users can specify strings,
88         # paths and null?
89         # My guess would be `types.nullOr (types.either types.str types.path)`
90         # but I haven't verified yet.
91         type = types.nullOr types.str;
92         example = "/run/unbound/unbound.ctl";
93         description = lib.mdDoc ''
94           When not set to `null` this option defines the path
95           at which the unbound remote control socket should be created at. The
96           socket will be owned by the unbound user (`unbound`)
97           and group will be `nogroup`.
99           Users that should be permitted to access the socket must be in the
100           `config.services.unbound.group` group.
102           If this option is `null` remote control will not be
103           enabled. Unbounds default values apply.
104         '';
105       };
107       settings = mkOption {
108         default = {};
109         type = with types; submodule {
111           freeformType = let
112             validSettingsPrimitiveTypes = oneOf [ int str bool float ];
113             validSettingsTypes = oneOf [ validSettingsPrimitiveTypes (listOf validSettingsPrimitiveTypes) ];
114             settingsType = oneOf [ str (attrsOf validSettingsTypes) ];
115           in attrsOf (oneOf [ settingsType (listOf settingsType) ])
116               // { description = ''
117                 unbound.conf configuration type. The format consist of an attribute
118                 set of settings. Each settings can be either one value, a list of
119                 values or an attribute set. The allowed values are integers,
120                 strings, booleans or floats.
121               '';
122             };
124           options = {
125             remote-control.control-enable = mkOption {
126               type = bool;
127               default = false;
128               internal = true;
129             };
130           };
131         };
132         example = literalExpression ''
133           {
134             server = {
135               interface = [ "127.0.0.1" ];
136             };
137             forward-zone = [
138               {
139                 name = ".";
140                 forward-addr = "1.1.1.1@853#cloudflare-dns.com";
141               }
142               {
143                 name = "example.org.";
144                 forward-addr = [
145                   "1.1.1.1@853#cloudflare-dns.com"
146                   "1.0.0.1@853#cloudflare-dns.com"
147                 ];
148               }
149             ];
150             remote-control.control-enable = true;
151           };
152         '';
153         description = lib.mdDoc ''
154           Declarative Unbound configuration
155           See the {manpage}`unbound.conf(5)` manpage for a list of
156           available options.
157         '';
158       };
159     };
160   };
162   ###### implementation
164   config = mkIf cfg.enable {
166     services.unbound.settings = {
167       server = {
168         directory = mkDefault cfg.stateDir;
169         username = cfg.user;
170         chroot = ''""'';
171         pidfile = ''""'';
172         # when running under systemd there is no need to daemonize
173         do-daemonize = false;
174         interface = mkDefault ([ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1"));
175         access-control = mkDefault ([ "127.0.0.0/8 allow" ] ++ (optional config.networking.enableIPv6 "::1/128 allow"));
176         auto-trust-anchor-file = mkIf cfg.enableRootTrustAnchor rootTrustAnchorFile;
177         tls-cert-bundle = mkDefault "/etc/ssl/certs/ca-certificates.crt";
178         # prevent race conditions on system startup when interfaces are not yet
179         # configured
180         ip-freebind = mkDefault true;
181         define-tag = mkDefault "";
182       };
183       remote-control = {
184         control-enable = mkDefault false;
185         control-interface = mkDefault ([ "127.0.0.1" ] ++ (optional config.networking.enableIPv6 "::1"));
186         server-key-file = mkDefault "${cfg.stateDir}/unbound_server.key";
187         server-cert-file = mkDefault "${cfg.stateDir}/unbound_server.pem";
188         control-key-file = mkDefault "${cfg.stateDir}/unbound_control.key";
189         control-cert-file = mkDefault "${cfg.stateDir}/unbound_control.pem";
190       } // optionalAttrs (cfg.localControlSocketPath != null) {
191         control-enable = true;
192         control-interface = cfg.localControlSocketPath;
193       };
194     };
196     environment.systemPackages = [ cfg.package ];
198     users.users = mkIf (cfg.user == "unbound") {
199       unbound = {
200         description = "unbound daemon user";
201         isSystemUser = true;
202         group = cfg.group;
203       };
204     };
206     users.groups = mkIf (cfg.group == "unbound") {
207       unbound = {};
208     };
210     networking = mkIf cfg.resolveLocalQueries {
211       resolvconf = {
212         useLocalResolver = mkDefault true;
213       };
215       networkmanager.dns = "unbound";
216     };
218     environment.etc."unbound/unbound.conf".source = confFile;
220     systemd.services.unbound = {
221       description = "Unbound recursive Domain Name Server";
222       after = [ "network.target" ];
223       before = [ "nss-lookup.target" ];
224       wantedBy = [ "multi-user.target" "nss-lookup.target" ];
226       path = mkIf cfg.settings.remote-control.control-enable [ pkgs.openssl ];
228       preStart = ''
229         ${optionalString cfg.enableRootTrustAnchor ''
230           ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!"
231         ''}
232         ${optionalString cfg.settings.remote-control.control-enable ''
233           ${cfg.package}/bin/unbound-control-setup -d ${cfg.stateDir}
234         ''}
235       '';
237       restartTriggers = [
238         confFile
239       ];
241       serviceConfig = {
242         ExecStart = "${cfg.package}/bin/unbound -p -d -c /etc/unbound/unbound.conf";
243         ExecReload = "+/run/current-system/sw/bin/kill -HUP $MAINPID";
245         NotifyAccess = "main";
246         Type = "notify";
248         # FIXME: Which of these do we actualy need, can we drop the chroot flag?
249         AmbientCapabilities = [
250           "CAP_NET_BIND_SERVICE"
251           "CAP_NET_RAW"
252           "CAP_SETGID"
253           "CAP_SETUID"
254           "CAP_SYS_CHROOT"
255           "CAP_SYS_RESOURCE"
256         ];
258         User = cfg.user;
259         Group = cfg.group;
261         MemoryDenyWriteExecute = true;
262         NoNewPrivileges = true;
263         PrivateDevices = true;
264         PrivateTmp = true;
265         ProtectHome = true;
266         ProtectControlGroups = true;
267         ProtectKernelModules = true;
268         ProtectSystem = "strict";
269         RuntimeDirectory = "unbound";
270         ConfigurationDirectory = "unbound";
271         StateDirectory = "unbound";
272         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
273         RestrictRealtime = true;
274         SystemCallArchitectures = "native";
275         SystemCallFilter = [
276           "~@clock"
277           "@cpu-emulation"
278           "@debug"
279           "@keyring"
280           "@module"
281           "mount"
282           "@obsolete"
283           "@resources"
284         ];
285         RestrictNamespaces = true;
286         LockPersonality = true;
287         RestrictSUIDSGID = true;
289         Restart = "on-failure";
290         RestartSec = "5s";
291       };
292     };
293   };
295   imports = [
296     (mkRenamedOptionModule [ "services" "unbound" "interfaces" ] [ "services" "unbound" "settings" "server" "interface" ])
297     (mkChangedOptionModule [ "services" "unbound" "allowedAccess" ] [ "services" "unbound" "settings" "server" "access-control" ] (
298       config: map (value: "${value} allow") (getAttrFromPath [ "services" "unbound" "allowedAccess" ] config)
299     ))
300     (mkRemovedOptionModule [ "services" "unbound" "forwardAddresses" ] ''
301       Add a new setting:
302       services.unbound.settings.forward-zone = [{
303         name = ".";
304         forward-addr = [ # Your current services.unbound.forwardAddresses ];
305       }];
306       If any of those addresses are local addresses (127.0.0.1 or ::1), you must
307       also set services.unbound.settings.server.do-not-query-localhost to false.
308     '')
309     (mkRemovedOptionModule [ "services" "unbound" "extraConfig" ] ''
310       You can use services.unbound.settings to add any configuration you want.
311     '')
312   ];