nixos/README.md: relax the requirement of providing option defaults (#334509)
[NixPkgs.git] / nixos / modules / services / networking / nebula.nix
blob35d7fafb43b57621585cb5416869a242501fda55
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
9   cfg = config.services.nebula;
10   enabledNetworks = lib.filterAttrs (n: v: v.enable) cfg.networks;
12   format = pkgs.formats.yaml { };
14   nameToId = netName: "nebula-${netName}";
16   resolveFinalPort =
17     netCfg:
18     if netCfg.listen.port == null then
19       if (netCfg.isLighthouse || netCfg.isRelay) then 4242 else 0
20     else
21       netCfg.listen.port;
24   # Interface
26   options = {
27     services.nebula = {
28       networks = lib.mkOption {
29         description = "Nebula network definitions.";
30         default = { };
31         type = lib.types.attrsOf (
32           lib.types.submodule {
33             options = {
34               enable = lib.mkOption {
35                 type = lib.types.bool;
36                 default = true;
37                 description = "Enable or disable this network.";
38               };
40               package = lib.mkPackageOption pkgs "nebula" { };
42               ca = lib.mkOption {
43                 type = lib.types.path;
44                 description = "Path to the certificate authority certificate.";
45                 example = "/etc/nebula/ca.crt";
46               };
48               cert = lib.mkOption {
49                 type = lib.types.path;
50                 description = "Path to the host certificate.";
51                 example = "/etc/nebula/host.crt";
52               };
54               key = lib.mkOption {
55                 type = lib.types.oneOf [
56                   lib.types.nonEmptyStr
57                   lib.types.path
58                 ];
59                 description = "Path or reference to the host key.";
60                 example = "/etc/nebula/host.key";
61               };
63               staticHostMap = lib.mkOption {
64                 type = lib.types.attrsOf (lib.types.listOf (lib.types.str));
65                 default = { };
66                 description = ''
67                   The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
68                   A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
69                 '';
70                 example = {
71                   "192.168.100.1" = [ "100.64.22.11:4242" ];
72                 };
73               };
75               isLighthouse = lib.mkOption {
76                 type = lib.types.bool;
77                 default = false;
78                 description = "Whether this node is a lighthouse.";
79               };
81               isRelay = lib.mkOption {
82                 type = lib.types.bool;
83                 default = false;
84                 description = "Whether this node is a relay.";
85               };
87               lighthouses = lib.mkOption {
88                 type = lib.types.listOf lib.types.str;
89                 default = [ ];
90                 description = ''
91                   List of IPs of lighthouse hosts this node should report to and query from. This should be empty on lighthouse
92                   nodes. The IPs should be the lighthouse's Nebula IPs, not their external IPs.
93                 '';
94                 example = [ "192.168.100.1" ];
95               };
97               relays = lib.mkOption {
98                 type = lib.types.listOf lib.types.str;
99                 default = [ ];
100                 description = ''
101                   List of IPs of relays that this node should allow traffic from.
102                 '';
103                 example = [ "192.168.100.1" ];
104               };
106               listen.host = lib.mkOption {
107                 type = lib.types.str;
108                 default = "0.0.0.0";
109                 description = "IP address to listen on.";
110               };
112               listen.port = lib.mkOption {
113                 type = lib.types.nullOr lib.types.port;
114                 default = null;
115                 defaultText = lib.literalExpression ''
116                   if (config.services.nebula.networks.''${name}.isLighthouse ||
117                       config.services.nebula.networks.''${name}.isRelay) then
118                     4242
119                   else
120                     0;
121                 '';
122                 description = "Port number to listen on.";
123               };
125               tun.disable = lib.mkOption {
126                 type = lib.types.bool;
127                 default = false;
128                 description = ''
129                   When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root).
130                 '';
131               };
133               tun.device = lib.mkOption {
134                 type = lib.types.nullOr lib.types.str;
135                 default = null;
136                 description = "Name of the tun device. Defaults to nebula.\${networkName}.";
137               };
139               firewall.outbound = lib.mkOption {
140                 type = lib.types.listOf lib.types.attrs;
141                 default = [ ];
142                 description = "Firewall rules for outbound traffic.";
143                 example = [
144                   {
145                     port = "any";
146                     proto = "any";
147                     host = "any";
148                   }
149                 ];
150               };
152               firewall.inbound = lib.mkOption {
153                 type = lib.types.listOf lib.types.attrs;
154                 default = [ ];
155                 description = "Firewall rules for inbound traffic.";
156                 example = [
157                   {
158                     port = "any";
159                     proto = "any";
160                     host = "any";
161                   }
162                 ];
163               };
165               settings = lib.mkOption {
166                 type = format.type;
167                 default = { };
168                 description = ''
169                   Nebula configuration. Refer to
170                   <https://github.com/slackhq/nebula/blob/master/examples/config.yml>
171                   for details on supported values.
172                 '';
173                 example = lib.literalExpression ''
174                   {
175                     lighthouse.dns = {
176                       host = "0.0.0.0";
177                       port = 53;
178                     };
179                   }
180                 '';
181               };
182             };
183           }
184         );
185       };
186     };
187   };
189   # Implementation
190   config = lib.mkIf (enabledNetworks != { }) {
191     systemd.services = lib.mkMerge (
192       lib.mapAttrsToList (
193         netName: netCfg:
194         let
195           networkId = nameToId netName;
196           settings = lib.recursiveUpdate {
197             pki = {
198               ca = netCfg.ca;
199               cert = netCfg.cert;
200               key = netCfg.key;
201             };
202             static_host_map = netCfg.staticHostMap;
203             lighthouse = {
204               am_lighthouse = netCfg.isLighthouse;
205               hosts = netCfg.lighthouses;
206             };
207             relay = {
208               am_relay = netCfg.isRelay;
209               relays = netCfg.relays;
210               use_relays = true;
211             };
212             listen = {
213               host = netCfg.listen.host;
214               port = resolveFinalPort netCfg;
215             };
216             tun = {
217               disabled = netCfg.tun.disable;
218               dev = if (netCfg.tun.device != null) then netCfg.tun.device else "nebula.${netName}";
219             };
220             firewall = {
221               inbound = netCfg.firewall.inbound;
222               outbound = netCfg.firewall.outbound;
223             };
224           } netCfg.settings;
225           configFile = format.generate "nebula-config-${netName}.yml" (
226             lib.warnIf
227               ((settings.lighthouse.am_lighthouse || settings.relay.am_relay) && settings.listen.port == 0)
228               ''
229                 Nebula network '${netName}' is configured as a lighthouse or relay, and its port is ${builtins.toString settings.listen.port}.
230                 You will likely experience connectivity issues: https://nebula.defined.net/docs/config/listen/#listenport
231               ''
232               settings
233           );
234         in
235         {
236           # Create the systemd service for Nebula.
237           "nebula@${netName}" = {
238             description = "Nebula VPN service for ${netName}";
239             wants = [ "basic.target" ];
240             after = [
241               "basic.target"
242               "network.target"
243             ];
244             before = [ "sshd.service" ];
245             wantedBy = [ "multi-user.target" ];
246             serviceConfig = {
247               Type = "notify";
248               Restart = "always";
249               ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
250               UMask = "0027";
251               CapabilityBoundingSet = "CAP_NET_ADMIN";
252               AmbientCapabilities = "CAP_NET_ADMIN";
253               LockPersonality = true;
254               NoNewPrivileges = true;
255               PrivateDevices = false; # needs access to /dev/net/tun (below)
256               DeviceAllow = "/dev/net/tun rw";
257               DevicePolicy = "closed";
258               PrivateTmp = true;
259               PrivateUsers = false; # CapabilityBoundingSet needs to apply to the host namespace
260               ProtectClock = true;
261               ProtectControlGroups = true;
262               ProtectHome = true;
263               ProtectHostname = true;
264               ProtectKernelLogs = true;
265               ProtectKernelModules = true;
266               ProtectKernelTunables = true;
267               ProtectProc = "invisible";
268               ProtectSystem = true;
269               RestrictNamespaces = true;
270               RestrictSUIDSGID = true;
271               User = networkId;
272               Group = networkId;
273             };
274             unitConfig.StartLimitIntervalSec = 0; # ensure Restart=always is always honoured (networks can go down for arbitrarily long)
275           };
276         }
277       ) enabledNetworks
278     );
280     # Open the chosen ports for UDP.
281     networking.firewall.allowedUDPPorts = lib.unique (
282       lib.filter (port: port > 0) (
283         lib.mapAttrsToList (netName: netCfg: resolveFinalPort netCfg) enabledNetworks
284       )
285     );
287     # Create the service users and groups.
288     users.users = lib.mkMerge (
289       lib.mapAttrsToList (netName: netCfg: {
290         ${nameToId netName} = {
291           group = nameToId netName;
292           description = "Nebula service user for network ${netName}";
293           isSystemUser = true;
294         };
295       }) enabledNetworks
296     );
298     users.groups = lib.mkMerge (
299       lib.mapAttrsToList (netName: netCfg: {
300         ${nameToId netName} = { };
301       }) enabledNetworks
302     );
303   };
305   meta.maintainers = with lib.maintainers; [ numinit ];