1{ config, lib, pkgs, ... }:
2with lib;
3let
4 keysPath = "/var/lib/yggdrasil/keys.json";
5
6 cfg = config.services.yggdrasil;
7 configProvided = cfg.config != { };
8 configFileProvided = cfg.configFile != null;
9
10in {
11 options = with types; {
12 services.yggdrasil = {
13 enable = mkEnableOption "the yggdrasil system service";
14
15 config = mkOption {
16 type = attrs;
17 default = {};
18 example = {
19 Peers = [
20 "tcp://aa.bb.cc.dd:eeeee"
21 "tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff"
22 ];
23 Listen = [
24 "tcp://0.0.0.0:xxxxx"
25 ];
26 };
27 description = ''
28 Configuration for yggdrasil, as a Nix attribute set.
29
30 Warning: this is stored in the WORLD-READABLE Nix store!
31 Therefore, it is not appropriate for private keys. If you
32 wish to specify the keys, use <option>configFile</option>.
33
34 If the <option>persistentKeys</option> is enabled then the
35 keys that are generated during activation will override
36 those in <option>config</option> or
37 <option>configFile</option>.
38
39 If no keys are specified then ephemeral keys are generated
40 and the Yggdrasil interface will have a random IPv6 address
41 each time the service is started, this is the default.
42
43 If both <option>configFile</option> and <option>config</option>
44 are supplied, they will be combined, with values from
45 <option>configFile</option> taking precedence.
46
47 You can use the command <code>nix-shell -p yggdrasil --run
48 "yggdrasil -genconf"</code> to generate default
49 configuration values with documentation.
50 '';
51 };
52
53 configFile = mkOption {
54 type = nullOr path;
55 default = null;
56 example = "/run/keys/yggdrasil.conf";
57 description = ''
58 A file which contains JSON configuration for yggdrasil.
59 See the <option>config</option> option for more information.
60 '';
61 };
62
63 group = mkOption {
64 type = types.str;
65 default = "root";
66 example = "wheel";
67 description = "Group to grant access to the Yggdrasil control socket.";
68 };
69
70 openMulticastPort = mkOption {
71 type = bool;
72 default = false;
73 description = ''
74 Whether to open the UDP port used for multicast peer
75 discovery. The NixOS firewall blocks link-local
76 communication, so in order to make local peering work you
77 will also need to set <code>LinkLocalTCPPort</code> in your
78 yggdrasil configuration (<option>config</option> or
79 <option>configFile</option>) to a port number other than 0,
80 and then add that port to
81 <option>networking.firewall.allowedTCPPorts</option>.
82 '';
83 };
84
85 denyDhcpcdInterfaces = mkOption {
86 type = listOf str;
87 default = [];
88 example = [ "tap*" ];
89 description = ''
90 Disable the DHCP client for any interface whose name matches
91 any of the shell glob patterns in this list. Use this
92 option to prevent the DHCP client from broadcasting requests
93 on the yggdrasil network. It is only necessary to do so
94 when yggdrasil is running in TAP mode, because TUN
95 interfaces do not support broadcasting.
96 '';
97 };
98
99 package = mkOption {
100 type = package;
101 default = pkgs.yggdrasil;
102 defaultText = "pkgs.yggdrasil";
103 description = "Yggdrasil package to use.";
104 };
105
106 persistentKeys = mkEnableOption ''
107 If enabled then keys will be generated once and Yggdrasil
108 will retain the same IPv6 address when the service is
109 restarted. Keys are stored at ${keysPath}.
110 '';
111
112 };
113 };
114
115 config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil";
116 in {
117 assertions = [{
118 assertion = config.networking.enableIPv6;
119 message = "networking.enableIPv6 must be true for yggdrasil to work";
120 }];
121
122 system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
123 if [ ! -e ${keysPath} ]
124 then
125 mkdir --mode=700 -p ${builtins.dirOf keysPath}
126 ${binYggdrasil} -genconf -json \
127 | ${pkgs.jq}/bin/jq \
128 'to_entries|map(select(.key|endswith("Key")))|from_entries' \
129 > ${keysPath}
130 fi
131 '';
132
133 systemd.services.yggdrasil = {
134 description = "Yggdrasil Network Service";
135 bindsTo = [ "network-online.target" ];
136 after = [ "network-online.target" ];
137 wantedBy = [ "multi-user.target" ];
138
139 preStart =
140 (if configProvided || configFileProvided || cfg.persistentKeys then
141 "echo "
142
143 + (lib.optionalString configProvided
144 "'${builtins.toJSON cfg.config}'")
145 + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})")
146 + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
147 + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
148 else
149 "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf";
150
151 serviceConfig = {
152 ExecStart =
153 "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf";
154 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
155 Restart = "always";
156
157 Group = cfg.group;
158 RuntimeDirectory = "yggdrasil";
159 RuntimeDirectoryMode = "0750";
160 BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
161 ++ lib.optional cfg.persistentKeys keysPath;
162
163 # TODO: as of yggdrasil 0.3.8 and systemd 243, yggdrasil fails
164 # to set up the network adapter when DynamicUser is set. See
165 # github.com/yggdrasil-network/yggdrasil-go/issues/557. The
166 # following options are implied by DynamicUser according to
167 # the systemd.exec documentation, and can be removed if the
168 # upstream issue is fixed and DynamicUser is set to true:
169 PrivateTmp = true;
170 RemoveIPC = true;
171 NoNewPrivileges = true;
172 ProtectSystem = "strict";
173 RestrictSUIDSGID = true;
174 # End of list of options implied by DynamicUser.
175
176 AmbientCapabilities = "CAP_NET_ADMIN";
177 CapabilityBoundingSet = "CAP_NET_ADMIN";
178 MemoryDenyWriteExecute = true;
179 ProtectControlGroups = true;
180 ProtectHome = "tmpfs";
181 ProtectKernelModules = true;
182 ProtectKernelTunables = true;
183 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
184 RestrictNamespaces = true;
185 RestrictRealtime = true;
186 SystemCallArchitectures = "native";
187 SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
188 };
189 };
190
191 networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
192 networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
193
194 # Make yggdrasilctl available on the command line.
195 environment.systemPackages = [ cfg.package ];
196 });
197 meta = {
198 doc = ./yggdrasil.xml;
199 maintainers = with lib.maintainers; [ gazally ehmry ];
200 };
201}