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}";
18 if netCfg.listen.port == null then
19 if (netCfg.isLighthouse || netCfg.isRelay) then 4242 else 0
28 networks = lib.mkOption {
29 description = "Nebula network definitions.";
31 type = lib.types.attrsOf (
34 enable = lib.mkOption {
35 type = lib.types.bool;
37 description = "Enable or disable this network.";
40 package = lib.mkPackageOption pkgs "nebula" { };
43 type = lib.types.path;
44 description = "Path to the certificate authority certificate.";
45 example = "/etc/nebula/ca.crt";
49 type = lib.types.path;
50 description = "Path to the host certificate.";
51 example = "/etc/nebula/host.crt";
55 type = lib.types.oneOf [
59 description = "Path or reference to the host key.";
60 example = "/etc/nebula/host.key";
63 staticHostMap = lib.mkOption {
64 type = lib.types.attrsOf (lib.types.listOf (lib.types.str));
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.
71 "192.168.100.1" = [ "100.64.22.11:4242" ];
75 isLighthouse = lib.mkOption {
76 type = lib.types.bool;
78 description = "Whether this node is a lighthouse.";
81 isRelay = lib.mkOption {
82 type = lib.types.bool;
84 description = "Whether this node is a relay.";
87 lighthouses = lib.mkOption {
88 type = lib.types.listOf lib.types.str;
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.
94 example = [ "192.168.100.1" ];
97 relays = lib.mkOption {
98 type = lib.types.listOf lib.types.str;
101 List of IPs of relays that this node should allow traffic from.
103 example = [ "192.168.100.1" ];
106 listen.host = lib.mkOption {
107 type = lib.types.str;
109 description = "IP address to listen on.";
112 listen.port = lib.mkOption {
113 type = lib.types.nullOr lib.types.port;
115 defaultText = lib.literalExpression ''
116 if (config.services.nebula.networks.''${name}.isLighthouse ||
117 config.services.nebula.networks.''${name}.isRelay) then
122 description = "Port number to listen on.";
125 tun.disable = lib.mkOption {
126 type = lib.types.bool;
129 When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root).
133 tun.device = lib.mkOption {
134 type = lib.types.nullOr lib.types.str;
136 description = "Name of the tun device. Defaults to nebula.\${networkName}.";
139 firewall.outbound = lib.mkOption {
140 type = lib.types.listOf lib.types.attrs;
142 description = "Firewall rules for outbound traffic.";
152 firewall.inbound = lib.mkOption {
153 type = lib.types.listOf lib.types.attrs;
155 description = "Firewall rules for inbound traffic.";
165 settings = lib.mkOption {
169 Nebula configuration. Refer to
170 <https://github.com/slackhq/nebula/blob/master/examples/config.yml>
171 for details on supported values.
173 example = lib.literalExpression ''
190 config = lib.mkIf (enabledNetworks != { }) {
191 systemd.services = lib.mkMerge (
195 networkId = nameToId netName;
196 settings = lib.recursiveUpdate {
202 static_host_map = netCfg.staticHostMap;
204 am_lighthouse = netCfg.isLighthouse;
205 hosts = netCfg.lighthouses;
208 am_relay = netCfg.isRelay;
209 relays = netCfg.relays;
213 host = netCfg.listen.host;
214 port = resolveFinalPort netCfg;
217 disabled = netCfg.tun.disable;
218 dev = if (netCfg.tun.device != null) then netCfg.tun.device else "nebula.${netName}";
221 inbound = netCfg.firewall.inbound;
222 outbound = netCfg.firewall.outbound;
225 configFile = format.generate "nebula-config-${netName}.yml" (
227 ((settings.lighthouse.am_lighthouse || settings.relay.am_relay) && settings.listen.port == 0)
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
236 # Create the systemd service for Nebula.
237 "nebula@${netName}" = {
238 description = "Nebula VPN service for ${netName}";
239 wants = [ "basic.target" ];
244 before = [ "sshd.service" ];
245 wantedBy = [ "multi-user.target" ];
249 ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
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";
259 PrivateUsers = false; # CapabilityBoundingSet needs to apply to the host namespace
261 ProtectControlGroups = 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;
274 unitConfig.StartLimitIntervalSec = 0; # ensure Restart=always is always honoured (networks can go down for arbitrarily long)
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
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}";
298 users.groups = lib.mkMerge (
299 lib.mapAttrsToList (netName: netCfg: {
300 ${nameToId netName} = { };
305 meta.maintainers = with lib.maintainers; [ numinit ];