vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / tinc.nix
blob5f625c10840be5bc18751bd630d2630b2c58c8a4
1 { config, lib, pkgs, ... }:
3 with lib;
4 let
5   cfg = config.services.tinc;
7   mkValueString = value:
8     if value == true then "yes"
9     else if value == false then "no"
10     else generators.mkValueStringDefault { } value;
12   toTincConf = generators.toKeyValue {
13     listsAsDuplicateKeys = true;
14     mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } "=";
15   };
17   tincConfType = with types;
18     let
19       valueType = oneOf [ bool str int ];
20     in
21     attrsOf (either valueType (listOf valueType));
23   addressSubmodule = {
24     options = {
25       address = mkOption {
26         type = types.str;
27         description = "The external IP address or hostname where the host can be reached.";
28       };
30       port = mkOption {
31         type = types.nullOr types.port;
32         default = null;
33         description = ''
34           The port where the host can be reached.
36           If no port is specified, the default Port is used.
37         '';
38       };
39     };
40   };
42   subnetSubmodule = {
43     options = {
44       address = mkOption {
45         type = types.str;
46         description = ''
47           The subnet of this host.
49           Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case
50           a subnet consisting of only that single address is assumed, or they can
51           be a IPv4 or IPv6 network address with a prefix length.
53           IPv4 subnets are notated like 192.168.1.0/24, IPv6 subnets are notated
54           like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e.
56           Note that subnets like 192.168.1.1/24 are invalid.
57         '';
58       };
60       prefixLength = mkOption {
61         type = with types; nullOr (addCheck int (n: n >= 0 && n <= 128));
62         default = null;
63         description = ''
64           The prefix length of the subnet.
66           If null, a subnet consisting of only that single address is assumed.
68           This conforms to standard CIDR notation as described in RFC1519.
69         '';
70       };
72       weight = mkOption {
73         type = types.ints.unsigned;
74         default = 10;
75         description = ''
76           Indicates the priority over identical Subnets owned by different nodes.
78           Lower values indicate higher priority. Packets will be sent to the
79           node with the highest priority, unless that node is not reachable, in
80           which case the node with the next highest priority will be tried, and
81           so on.
82         '';
83       };
84     };
85   };
87   hostSubmodule = { config, ... }: {
88     options = {
89       addresses = mkOption {
90         type = types.listOf (types.submodule addressSubmodule);
91         default = [ ];
92         description = ''
93           The external address where the host can be reached. This will set this
94           host's {option}`settings.Address` option.
96           This variable is only required if you want to connect to this host.
97         '';
98       };
100       subnets = mkOption {
101         type = types.listOf (types.submodule subnetSubmodule);
102         default = [ ];
103         description = ''
104           The subnets which this tinc daemon will serve. This will set this
105           host's {option}`settings.Subnet` option.
107           Tinc tries to look up which other daemon it should send a packet to by
108           searching the appropriate subnet. If the packet matches a subnet, it
109           will be sent to the daemon who has this subnet in his host
110           configuration file.
111         '';
112       };
114       rsaPublicKey = mkOption {
115         type = types.str;
116         default = "";
117         description = ''
118           Legacy RSA public key of the host in PEM format, including start and
119           end markers.
121           This will be appended as-is in the host's configuration file.
123           The ed25519 public key can be specified using the
124           {option}`settings.Ed25519PublicKey` option instead.
125         '';
126       };
128       settings = mkOption {
129         default = { };
130         type = types.submodule { freeformType = tincConfType; };
131         description = ''
132           Configuration for this host.
134           See <https://tinc-vpn.org/documentation-1.1/Host-configuration-variables.html>
135           for supported values.
136         '';
137       };
138     };
140     config.settings = {
141       Address = mkDefault (map
142         (address: "${address.address} ${toString address.port}")
143         config.addresses);
145       Subnet = mkDefault (map
146         (subnet:
147           if subnet.prefixLength == null then "${subnet.address}#${toString subnet.weight}"
148           else "${subnet.address}/${toString subnet.prefixLength}#${toString subnet.weight}")
149         config.subnets);
150     };
151   };
156   ###### interface
158   options = {
160     services.tinc = {
162       networks = mkOption {
163         default = { };
164         type = with types; attrsOf (submodule ({ config, ... }: {
165           options = {
167             extraConfig = mkOption {
168               default = "";
169               type = types.lines;
170               description = ''
171                 Extra lines to add to the tinc service configuration file.
173                 Note that using the declarative {option}`service.tinc.networks.<name>.settings`
174                 option is preferred.
175               '';
176             };
178             name = mkOption {
179               default = null;
180               type = types.nullOr types.str;
181               description = ''
182                 The name of the node which is used as an identifier when communicating
183                 with the remote nodes in the mesh. If null then the hostname of the system
184                 is used to derive a name (note that tinc may replace non-alphanumeric characters in
185                 hostnames by underscores).
186               '';
187             };
189             ed25519PrivateKeyFile = mkOption {
190               default = null;
191               type = types.nullOr types.path;
192               description = ''
193                 Path of the private ed25519 keyfile.
194               '';
195             };
197             rsaPrivateKeyFile = mkOption {
198               default = null;
199               type = types.nullOr types.path;
200               description = ''
201                 Path of the private RSA keyfile.
202               '';
203             };
205             debugLevel = mkOption {
206               default = 0;
207               type = types.addCheck types.int (l: l >= 0 && l <= 5);
208               description = ''
209                 The amount of debugging information to add to the log. 0 means little
210                 logging while 5 is the most logging. {command}`man tincd` for
211                 more details.
212               '';
213             };
215             hosts = mkOption {
216               default = { };
217               type = types.attrsOf types.lines;
218               description = ''
219                 The name of the host in the network as well as the configuration for that host.
220                 This name should only contain alphanumerics and underscores.
222                 Note that using the declarative {option}`service.tinc.networks.<name>.hostSettings`
223                 option is preferred.
224               '';
225             };
227             hostSettings = mkOption {
228               default = { };
229               example = literalExpression ''
230                 {
231                   host1 = {
232                     addresses = [
233                       { address = "192.168.1.42"; }
234                       { address = "192.168.1.42"; port = 1655; }
235                     ];
236                     subnets = [ { address = "10.0.0.42"; } ];
237                     rsaPublicKey = "...";
238                     settings = {
239                       Ed25519PublicKey = "...";
240                     };
241                   };
242                   host2 = {
243                     subnets = [ { address = "10.0.1.0"; prefixLength = 24; weight = 2; } ];
244                     rsaPublicKey = "...";
245                     settings = {
246                       Compression = 10;
247                     };
248                   };
249                 }
250               '';
251               type = types.attrsOf (types.submodule hostSubmodule);
252               description = ''
253                 The name of the host in the network as well as the configuration for that host.
254                 This name should only contain alphanumerics and underscores.
255               '';
256             };
258             interfaceType = mkOption {
259               default = "tun";
260               type = types.enum [ "tun" "tap" ];
261               description = ''
262                 The type of virtual interface used for the network connection.
263               '';
264             };
266             listenAddress = mkOption {
267               default = null;
268               type = types.nullOr types.str;
269               description = ''
270                 The ip address to listen on for incoming connections.
271               '';
272             };
274             bindToAddress = mkOption {
275               default = null;
276               type = types.nullOr types.str;
277               description = ''
278                 The ip address to bind to (both listen on and send packets from).
279               '';
280             };
282             package = mkPackageOption pkgs "tinc_pre" { };
284             chroot = mkOption {
285               default = false;
286               type = types.bool;
287               description = ''
288                 Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
289                 The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
291                 Note that this currently breaks dns resolution and tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
292               '';
293             };
295             settings = mkOption {
296               default = { };
297               type = types.submodule { freeformType = tincConfType; };
298               example = literalExpression ''
299                 {
300                   Interface = "custom.interface";
301                   DirectOnly = true;
302                   Mode = "switch";
303                 }
304               '';
305               description = ''
306                 Configuration of the Tinc daemon for this network.
308                 See <https://tinc-vpn.org/documentation-1.1/Main-configuration-variables.html>
309                 for supported values.
310               '';
311             };
312           };
314           config = {
315             hosts = mapAttrs
316               (hostname: host: ''
317                 ${toTincConf host.settings}
318                 ${host.rsaPublicKey}
319               '')
320               config.hostSettings;
322             settings = {
323               DeviceType = mkDefault config.interfaceType;
324               Name = mkDefault (if config.name == null then "$HOST" else config.name);
325               Ed25519PrivateKeyFile = mkIf (config.ed25519PrivateKeyFile != null) (mkDefault config.ed25519PrivateKeyFile);
326               PrivateKeyFile = mkIf (config.rsaPrivateKeyFile != null) (mkDefault config.rsaPrivateKeyFile);
327               ListenAddress = mkIf (config.listenAddress != null) (mkDefault config.listenAddress);
328               BindToAddress = mkIf (config.bindToAddress != null) (mkDefault config.bindToAddress);
329             };
330           };
331         }));
333         description = ''
334           Defines the tinc networks which will be started.
335           Each network invokes a different daemon.
336         '';
337       };
338     };
340   };
343   ###### implementation
345   config = mkIf (cfg.networks != { }) (
346     let
347       etcConfig = foldr (a: b: a // b) { }
348         (flip mapAttrsToList cfg.networks (network: data:
349           flip mapAttrs' data.hosts (host: text: nameValuePair
350             ("tinc/${network}/hosts/${host}")
351             ({ mode = "0644"; user = "tinc-${network}"; inherit text; })
352           ) // {
353             "tinc/${network}/tinc.conf" = {
354               mode = "0444";
355               text = ''
356                 ${toTincConf ({ Interface = "tinc.${network}"; } // data.settings)}
357                 ${data.extraConfig}
358               '';
359             };
360           }
361         ));
362     in {
363       environment.etc = etcConfig;
365       systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
366         ("tinc.${network}")
367         (let version = getVersion data.package; in {
368           description = "Tinc Daemon - ${network}";
369           wantedBy = [ "multi-user.target" ];
370           path = [ data.package ];
371           reloadTriggers = mkIf (versionAtLeast version "1.1pre") [ (builtins.toJSON etcConfig) ];
372           restartTriggers = mkIf (versionOlder version "1.1pre") [ (builtins.toJSON etcConfig) ];
373           serviceConfig = {
374             Type = "simple";
375             Restart = "always";
376             RestartSec = "3";
377             ExecReload = mkIf (versionAtLeast version "1.1pre") "${data.package}/bin/tinc -n ${network} reload";
378             ExecStart = "${data.package}/bin/tincd -D -U tinc-${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
379           };
380           preStart = ''
381             mkdir -p /etc/tinc/${network}/hosts
382             chown tinc-${network} /etc/tinc/${network}/hosts
383             mkdir -p /etc/tinc/${network}/invitations
384             chown tinc-${network} /etc/tinc/${network}/invitations
386             # Determine how we should generate our keys
387             if type tinc >/dev/null 2>&1; then
388               # Tinc 1.1+ uses the tinc helper application for key generation
389             ${if data.ed25519PrivateKeyFile != null then "  # ed25519 Keyfile managed by nix" else ''
390               # Prefer ED25519 keys (only in 1.1+)
391               [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
392             ''}
393             ${if data.rsaPrivateKeyFile != null then "  # RSA Keyfile managed by nix" else ''
394               [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
395             ''}
396               # In case there isn't anything to do
397               true
398             else
399               # Tinc 1.0 uses the tincd application
400               [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
401             fi
402           '';
403         })
404       );
406       environment.systemPackages = let
407         cli-wrappers = pkgs.stdenv.mkDerivation {
408           name = "tinc-cli-wrappers";
409           nativeBuildInputs = [ pkgs.makeWrapper ];
410           buildCommand = ''
411             mkdir -p $out/bin
412             ${concatStringsSep "\n" (mapAttrsToList (network: data:
413               optionalString (versionAtLeast data.package.version "1.1pre") ''
414                 makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
415                   --add-flags "--pidfile=/run/tinc.${network}.pid" \
416                   --add-flags "--config=/etc/tinc/${network}"
417               '') cfg.networks)}
418           '';
419         };
420       in [ cli-wrappers ];
422       users.users = flip mapAttrs' cfg.networks (network: _:
423         nameValuePair ("tinc-${network}") ({
424           description = "Tinc daemon user for ${network}";
425           isSystemUser = true;
426           group = "tinc-${network}";
427         })
428       );
429       users.groups = flip mapAttrs' cfg.networks (network: _:
430         nameValuePair "tinc-${network}" {}
431       );
432     });
434   meta.maintainers = with maintainers; [ minijackson mic92 ];