vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / yggdrasil.nix
blob0fb51400d666b49d9836d0e48dee8722c1f6266e
1 { config, lib, pkgs, ... }:
3 let
4   inherit (lib) mkIf mkOption;
5   inherit (lib.types) nullOr path bool listOf str;
6   keysPath = "/var/lib/yggdrasil/keys.json";
8   cfg = config.services.yggdrasil;
9   settingsProvided = cfg.settings != { };
10   configFileProvided = cfg.configFile != null;
12   format = pkgs.formats.json { };
15   imports = [
16     (lib.mkRenamedOptionModule
17       [ "services" "yggdrasil" "config" ]
18       [ "services" "yggdrasil" "settings" ])
19   ];
21   options = {
22     services.yggdrasil = {
23       enable = lib.mkEnableOption "the yggdrasil system service";
25       settings = mkOption {
26         type = format.type;
27         default = { };
28         example = {
29           Peers = [
30             "tcp://aa.bb.cc.dd:eeeee"
31             "tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff"
32           ];
33           Listen = [
34             "tcp://0.0.0.0:xxxxx"
35           ];
36         };
37         description = ''
38           Configuration for yggdrasil, as a Nix attribute set.
40           Warning: this is stored in the WORLD-READABLE Nix store!
41           Therefore, it is not appropriate for private keys. If you
42           wish to specify the keys, use {option}`configFile`.
44           If the {option}`persistentKeys` is enabled then the
45           keys that are generated during activation will override
46           those in {option}`settings` or
47           {option}`configFile`.
49           If no keys are specified then ephemeral keys are generated
50           and the Yggdrasil interface will have a random IPv6 address
51           each time the service is started. This is the default.
53           If both {option}`configFile` and {option}`settings`
54           are supplied, they will be combined, with values from
55           {option}`configFile` taking precedence.
57           You can use the command `nix-shell -p yggdrasil --run "yggdrasil -genconf"`
58           to generate default configuration values with documentation.
59         '';
60       };
62       configFile = mkOption {
63         type = nullOr path;
64         default = null;
65         example = "/run/keys/yggdrasil.conf";
66         description = ''
67           A file which contains JSON or HJSON configuration for yggdrasil. See
68           the {option}`settings` option for more information.
70           Note: This file must not be larger than 1 MB because it is passed to
71           the yggdrasil process via systemd‘s LoadCredential mechanism. For
72           details, see <https://systemd.io/CREDENTIALS/> and `man 5
73           systemd.exec`.
74         '';
75       };
77       group = mkOption {
78         type = nullOr str;
79         default = null;
80         example = "wheel";
81         description = "Group to grant access to the Yggdrasil control socket. If `null`, only root can access the socket.";
82       };
84       openMulticastPort = mkOption {
85         type = bool;
86         default = false;
87         description = ''
88           Whether to open the UDP port used for multicast peer discovery. The
89           NixOS firewall blocks link-local communication, so in order to make
90           incoming local peering work you will also need to configure
91           `MulticastInterfaces` in your Yggdrasil configuration
92           ({option}`settings` or {option}`configFile`). You will then have to
93           add the ports that you configure there to your firewall configuration
94           ({option}`networking.firewall.allowedTCPPorts` or
95           {option}`networking.firewall.interfaces.<name>.allowedTCPPorts`).
96         '';
97       };
99       denyDhcpcdInterfaces = mkOption {
100         type = listOf str;
101         default = [ ];
102         example = [ "tap*" ];
103         description = ''
104           Disable the DHCP client for any interface whose name matches
105           any of the shell glob patterns in this list.  Use this
106           option to prevent the DHCP client from broadcasting requests
107           on the yggdrasil network.  It is only necessary to do so
108           when yggdrasil is running in TAP mode, because TUN
109           interfaces do not support broadcasting.
110         '';
111       };
113       package = lib.mkPackageOption pkgs "yggdrasil" { };
115       persistentKeys = lib.mkEnableOption ''
116         persistent keys. If enabled then keys will be generated once and Yggdrasil
117         will retain the same IPv6 address when the service is
118         restarted. Keys are stored at ${keysPath}
119       '';
121       extraArgs = mkOption {
122         type = listOf str;
123         default = [ ];
124         example = [ "-loglevel" "info" ];
125         description = "Extra command line arguments.";
126       };
128     };
129   };
131   config = mkIf cfg.enable (
132     let
133       binYggdrasil = "${cfg.package}/bin/yggdrasil";
134       binHjson = "${pkgs.hjson-go}/bin/hjson-cli";
135     in
136     {
137       assertions = [{
138         assertion = config.networking.enableIPv6;
139         message = "networking.enableIPv6 must be true for yggdrasil to work";
140       }];
142       # This needs to be a separate service. The yggdrasil service fails if
143       # this is put into its preStart.
144       systemd.services.yggdrasil-persistent-keys = lib.mkIf cfg.persistentKeys {
145         wantedBy = [ "multi-user.target" ];
146         before = [ "yggdrasil.service" ];
147         serviceConfig.Type = "oneshot";
148         serviceConfig.RemainAfterExit = true;
149         script = ''
150           if [ ! -e ${keysPath} ]
151           then
152             mkdir --mode=700 -p ${builtins.dirOf keysPath}
153             ${binYggdrasil} -genconf -json \
154               | ${pkgs.jq}/bin/jq \
155                   'to_entries|map(select(.key|endswith("Key")))|from_entries' \
156               > ${keysPath}
157           fi
158         '';
159       };
161       systemd.services.yggdrasil = {
162         description = "Yggdrasil Network Service";
163         after = [ "network-pre.target" ];
164         wants = [ "network.target" ];
165         before = [ "network.target" ];
166         wantedBy = [ "multi-user.target" ];
168         # This script first prepares the config file, then it starts Yggdrasil.
169         # The preparation could also be done in ExecStartPre/preStart but only
170         # systemd versions >= v252 support reading credentials in ExecStartPre. As
171         # of February 2023, systemd v252 is not yet in the stable branch of NixOS.
172         #
173         # This could be changed in the future once systemd version v252 has
174         # reached NixOS but it does not have to be. Config file preparation is
175         # fast enough, it does not need elevated privileges, and `set -euo
176         # pipefail` should make sure that the service is not started if the
177         # preparation fails. Therefore, it is not necessary to move the
178         # preparation to ExecStartPre.
179         script = ''
180           set -euo pipefail
182           # prepare config file
183           ${(if settingsProvided || configFileProvided || cfg.persistentKeys then
184             "echo "
186             + (lib.optionalString settingsProvided
187               "'${builtins.toJSON cfg.settings}'")
188             + (lib.optionalString configFileProvided
189               "$(${binHjson} -c \"$CREDENTIALS_DIRECTORY/yggdrasil.conf\")")
190             + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
191             + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
192           else
193             "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
195           # start yggdrasil
196           ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf ${lib.strings.escapeShellArgs cfg.extraArgs}
197         '';
199         serviceConfig = {
200           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
201           Restart = "always";
203           DynamicUser = true;
204           StateDirectory = "yggdrasil";
205           RuntimeDirectory = "yggdrasil";
206           RuntimeDirectoryMode = "0750";
207           BindReadOnlyPaths = lib.optional cfg.persistentKeys keysPath;
208           LoadCredential =
209             mkIf configFileProvided "yggdrasil.conf:${cfg.configFile}";
211           AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
212           CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE";
213           MemoryDenyWriteExecute = true;
214           ProtectControlGroups = true;
215           ProtectHome = "tmpfs";
216           ProtectKernelModules = true;
217           ProtectKernelTunables = true;
218           RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
219           RestrictNamespaces = true;
220           RestrictRealtime = true;
221           SystemCallArchitectures = "native";
222           SystemCallFilter = [ "@system-service" "~@privileged @keyring" ];
223         } // (if (cfg.group != null) then {
224           Group = cfg.group;
225         } else { });
226       };
228       networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
229       networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
231       # Make yggdrasilctl available on the command line.
232       environment.systemPackages = [ cfg.package ];
233     }
234   );
235   meta = {
236     doc = ./yggdrasil.md;
237     maintainers = with lib.maintainers; [ gazally ehmry nagy ];
238   };