diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index 663accc7464..31c3da02437 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -5,202 +5,219 @@ with lib;
let
cfg = config.services.nebula;
- nebulaDesc = "Nebula VPN service";
format = pkgs.formats.yaml {};
- configFile = format.generate "nebula-config.yml" cfg.settings;
+ nameToId = netName: "nebula-${netName}";
in
{
# Interface
- options.services.nebula = {
- enable = mkEnableOption nebulaDesc;
+ options = {
+ services.nebula = {
+ networks = mkOption {
+ description = "Nebula network definitions.";
+ default = {};
+ type = types.attrsOf (types.submodule {
+ options = {
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nebula;
+ defaultText = "pkgs.nebula";
+ description = "Nebula derivation to use.";
+ };
- package = mkOption {
- type = types.package;
- default = pkgs.nebula;
- defaultText = "pkgs.nebula";
- description = "Nebula derivation to use.";
- };
+ ca = mkOption {
+ type = types.path;
+ description = "Path to the certificate authority certificate.";
+ example = "/etc/nebula/ca.crt";
+ };
- ca = mkOption {
- type = types.path;
- description = "Path to the certificate authority certificate.";
- example = "/etc/nebula/ca.crt";
- };
+ cert = mkOption {
+ type = types.path;
+ description = "Path to the host certificate.";
+ example = "/etc/nebula/host.crt";
+ };
- cert = mkOption {
- type = types.path;
- description = "Path to the host certificate.";
- example = "/etc/nebula/host.crt";
- };
+ key = mkOption {
+ type = types.path;
+ description = "Path to the host key.";
+ example = "/etc/nebula/host.key";
+ };
- key = mkOption {
- type = types.path;
- description = "Path to the host key.";
- example = "/etc/nebula/host.key";
- };
+ staticHostMap = mkOption {
+ type = types.attrsOf (types.listOf (types.str));
+ default = {};
+ description = ''
+ The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
+ A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
+ '';
+ example = literalExample ''
+ { "192.168.100.1" = [ "100.64.22.11:4242" ]; }
+ '';
+ };
- staticHostMap = mkOption {
- type = types.attrsOf (types.listOf (types.str));
- default = {};
- description = ''
- The static host map defines a set of hosts with fixed IP addresses on the internet (or any network).
- A host can have multiple fixed IP addresses defined here, and nebula will try each when establishing a tunnel.
- '';
- example = literalExample ''
- { "192.168.100.1" = [ "100.64.22.11:4242" ]; }
- '';
- };
+ isLighthouse = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether this node is a lighthouse.";
+ };
- isLighthouse = mkOption {
- type = types.bool;
- default = false;
- description = "Whether this node is a lighthouse.";
- };
+ lighthouses = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ List of IPs of lighthouse hosts this node should report to and query from. This should be empty on lighthouse
+ nodes. The IPs should be the lighthouse's Nebula IPs, not their external IPs.
+ '';
+ example = ''[ "192.168.100.1" ]'';
+ };
- lighthouses = mkOption {
- type = types.listOf types.str;
- default = [];
- description = ''
- List of IPs of lighthouse hosts this node should report to and query from. This should be empty on lighthouse
- nodes. The IPs should be the lighthouse's Nebula IPs, not their external IPs.
- '';
- example = ''[ "192.168.100.1" ]'';
- };
+ listen.host = mkOption {
+ type = types.str;
+ default = "0.0.0.0";
+ description = "IP address to listen on.";
+ };
- listen.host = mkOption {
- type = types.str;
- default = "0.0.0.0";
- description = "IP address to listen on.";
- };
+ listen.port = mkOption {
+ type = types.port;
+ default = 4242;
+ description = "Port number to listen on.";
+ };
- listen.port = mkOption {
- type = types.port;
- default = 4242;
- description = "Port number to listen on.";
- };
+ punch = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Continues to punch inbound/outbound at a regular interval to avoid expiration of firewall nat mappings.
+ '';
+ };
- punch = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Continues to punch inbound/outbound at a regular interval to avoid expiration of firewall nat mappings.
- '';
- };
+ tun.disable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root).
+ '';
+ };
- tun.disable = mkOption {
- type = types.bool;
- default = false;
- description = ''
- When tun is disabled, a lighthouse can be started without a local tun interface (and therefore without root).
- '';
- };
+ tun.device = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Name of the tun device. Defaults to nebula.\${networkName}.";
+ };
- tun.device = mkOption {
- type = types.str;
- default = "nebula1";
- description = "Name of the tun device.";
- };
+ firewall.outbound = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ description = "Firewall rules for outbound traffic.";
+ example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
+ };
- firewall.outbound = mkOption {
- type = types.listOf types.attrs;
- default = [];
- description = "Firewall rules for outbound traffic.";
- example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
- };
+ firewall.inbound = mkOption {
+ type = types.listOf types.attrs;
+ default = [];
+ description = "Firewall rules for inbound traffic.";
+ example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
+ };
- firewall.inbound = mkOption {
- type = types.listOf types.attrs;
- default = [];
- description = "Firewall rules for inbound traffic.";
- example = ''[ { port = "any"; proto = "any"; host = "any"; } ]'';
- };
-
- settings = {
- type = format.type;
- default = {};
- description = ''
- Nebula configuration. Refer to
-
- for details on supported values.
- '';
- example = literalExample ''
- {
- lighthouse.dns = {
- host = "0.0.0.0";
- port = 53;
+ settings = mkOption {
+ type = format.type;
+ default = {};
+ description = ''
+ Nebula configuration. Refer to
+
+ for details on supported values.
+ '';
+ example = literalExample ''
+ {
+ lighthouse.dns = {
+ host = "0.0.0.0";
+ port = 53;
+ };
+ }
+ '';
+ };
};
- }
- '';
+ });
+ };
};
};
# Implementation
-
- config = mkIf cfg.enable {
- services.nebula.settings = {
- pki = {
- ca = cfg.ca;
- cert = cfg.cert;
- key = cfg.key;
- };
- static_host_map = cfg.staticHostMap;
- lighthouse = {
- am_lighthouse = cfg.isLighthouse;
- hosts = cfg.lighthouses;
- };
- listen = {
- host = cfg.listen.host;
- port = cfg.listen.port;
- };
- punchy = {
- punch = cfg.punch;
- };
- tun = {
- disabled = cfg.tun.disable;
- dev = cfg.tun.device;
- };
- firewall = {
- inbound = cfg.firewall.inbound;
- outbound = cfg.firewall.outbound;
- };
- };
-
- # Create systemd service for Nebula.
- systemd.services.nebula = {
- description = nebulaDesc;
- after = [ "network.target" ];
- before = [ "sshd.service" ];
- wantedBy = [ "multi-user.target" ];
- serviceConfig = mkMerge [
+ config = mkIf (cfg.networks != {}) {
+ systemd.services = mkMerge (lib.mapAttrsToList (netName: netCfg:
+ let
+ networkId = nameToId netName;
+ settings = lib.recursiveUpdate {
+ pki = {
+ ca = netCfg.ca;
+ cert = netCfg.cert;
+ key = netCfg.key;
+ };
+ static_host_map = netCfg.staticHostMap;
+ lighthouse = {
+ am_lighthouse = netCfg.isLighthouse;
+ hosts = netCfg.lighthouses;
+ };
+ listen = {
+ host = netCfg.listen.host;
+ port = netCfg.listen.port;
+ };
+ punchy = {
+ punch = netCfg.punch;
+ };
+ tun = {
+ disabled = netCfg.tun.disable;
+ dev = if (netCfg.tun.device != null) then netCfg.tun.device else "nebula.${netName}";
+ };
+ firewall = {
+ inbound = netCfg.firewall.inbound;
+ outbound = netCfg.firewall.outbound;
+ };
+ } netCfg.settings;
+ configFile = format.generate "nebula-config-${netName}.yml" settings;
+ in
{
- Type = "simple";
- Restart = "always";
- ExecStart = "${cfg.package}/bin/nebula -config ${configFile}";
- }
- # The service needs to launch as root to access the tun device, if it's enabled.
- (mkIf cfg.tun.disable {
- User = "nebula";
- Group = "nebula";
- })
- ];
- };
+ # Create systemd service for Nebula.
+ "nebula@${netName}" = {
+ description = "Nebula VPN service for ${netName}";
+ after = [ "network.target" ];
+ before = [ "sshd.service" ];
+ wantedBy = [ "multi-user.target" ];
+ serviceConfig = mkMerge [
+ {
+ Type = "simple";
+ Restart = "always";
+ ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
+ }
+ # The service needs to launch as root to access the tun device, if it's enabled.
+ (mkIf netCfg.tun.disable {
+ User = networkId;
+ Group = networkId;
+ })
+ ];
+ };
+ }) cfg.networks);
- # Open the chosen port for UDP.
- networking.firewall.allowedUDPPorts = [ cfg.listen.port ];
+ # Open the chosen ports for UDP.
+ networking.firewall.allowedUDPPorts =
+ lib.unique (lib.mapAttrsToList (netName: netCfg: netCfg.listen.port) cfg.networks);
- # Create the service user and its group.
- users = mkIf cfg.tun.disable {
- users.nebula = {
- group = "nebula";
- description = "Nebula service user";
- isSystemUser = true;
- packages = [ cfg.package ];
- };
+ # Create the service users and groups.
+ users.users = mkMerge (lib.mapAttrsToList (netName: netCfg:
+ mkIf netCfg.tun.disable {
+ ${nameToId netName} = {
+ group = nameToId netName;
+ description = "Nebula service user for network ${netName}";
+ isSystemUser = true;
+ packages = [ netCfg.package ];
+ };
+ }) cfg.networks);
- groups.nebula = {};
- };
+ users.groups = mkMerge (lib.mapAttrsToList (netName: netCfg:
+ mkIf netCfg.tun.disable {
+ ${nameToId netName} = {};
+ }) cfg.networks);
};
-}
+}
\ No newline at end of file