From 35d79e894c7f580b5fb985baa05d547fb1ebc40f Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Wed, 27 Jan 2021 21:49:10 -0800 Subject: [PATCH 01/13] nixos/nebula: add basic module --- nixos/modules/services/networking/nebula.nix | 187 +++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 nixos/modules/services/networking/nebula.nix diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix new file mode 100644 index 000000000000..84eda281c854 --- /dev/null +++ b/nixos/modules/services/networking/nebula.nix @@ -0,0 +1,187 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.nebula; + nebulaDesc = "Nebula VPN service"; + +in +{ + # Interface + + options.services.nebula = { + enable = mkEnableOption nebulaDesc; + + 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"; + }; + + 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"; + }; + + 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."; + }; + + 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.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. + ''; + }; + + 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.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.inbound = mkOption { + type = types.listOf types.attrs; + default = []; + description = "Firewall rules for inbound traffic."; + example = ''[ { port = "any"; proto = "any"; host = "any"; } ]''; + }; + + extraConfig = mkOption { + type = types.attrs; + default = {}; + description = "Extra configuration added to the Nebula config file."; + example = literalExample '' + { + lighthouse.dns = { + host = "0.0.0.0"; + port = 53; + }; + } + ''; + }; + }; + + # Implementation + + config = + let + # Convert the options to an attrset. + optionConfigSet = { + 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; }; + }; + + # Merge in the extraconfig attrset. + mergedConfigSet = lib.recursiveUpdate optionConfigSet cfg.extraConfig; + + # Dump the config to JSON. Because Nebula reads YAML and YAML is a superset of JSON, this works. + nebulaConfig = pkgs.writeTextFile { name = "nebula-config.yml"; text = (builtins.toJSON mergedConfigSet); }; + + # The service needs to launch as root to access the tun device, if it's enabled. + serviceUser = if cfg.tun.disable then "nebula" else "root"; + serviceGroup = if cfg.tun.disable then "nebula" else "root"; + + in mkIf cfg.enable { + # Create systemd service for Nebula. + systemd.services.nebula = { + description = nebulaDesc; + after = [ "network.target" ]; + before = [ "sshd.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + User = serviceUser; + Group = serviceGroup; + ExecStart = "${cfg.package}/bin/nebula -config ${nebulaConfig}"; + }; + }; + + # Open the chosen port for UDP. + networking.firewall.allowedUDPPorts = [ cfg.listen.port ]; + + # Create the service user and its group. + users.users."nebula" = { + name = "nebula"; + group = "nebula"; + description = "Nebula service user"; + isSystemUser = true; + packages = [ cfg.package ]; + }; + users.groups."nebula" = {}; + }; +} From e8eaea9627ce92ae35b0696154f68e00cd14fa7a Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Tue, 9 Feb 2021 20:42:33 -0500 Subject: [PATCH 02/13] nixos/nebula: replace extraConfig option with a settings option --- nixos/modules/services/networking/nebula.nix | 61 +++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 84eda281c854..888f9f96fbe6 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -7,6 +7,9 @@ let cfg = config.services.nebula; nebulaDesc = "Nebula VPN service"; + format = pkgs.formats.yaml {}; + configFile = format.generate "nebula-config.yml" cfg.settings; + in { # Interface @@ -115,10 +118,14 @@ in example = ''[ { port = "any"; proto = "any"; host = "any"; } ]''; }; - extraConfig = mkOption { - type = types.attrs; + settings = { + type = format.type; default = {}; - description = "Extra configuration added to the Nebula config file."; + description = '' + Nebula configuration. Refer to + + for details on supported values. + ''; example = literalExample '' { lighthouse.dns = { @@ -134,28 +141,38 @@ in config = let - # Convert the options to an attrset. - optionConfigSet = { - 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; }; - }; - - # Merge in the extraconfig attrset. - mergedConfigSet = lib.recursiveUpdate optionConfigSet cfg.extraConfig; - - # Dump the config to JSON. Because Nebula reads YAML and YAML is a superset of JSON, this works. - nebulaConfig = pkgs.writeTextFile { name = "nebula-config.yml"; text = (builtins.toJSON mergedConfigSet); }; - # The service needs to launch as root to access the tun device, if it's enabled. serviceUser = if cfg.tun.disable then "nebula" else "root"; serviceGroup = if cfg.tun.disable then "nebula" else "root"; - in 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; @@ -167,7 +184,7 @@ in Restart = "always"; User = serviceUser; Group = serviceGroup; - ExecStart = "${cfg.package}/bin/nebula -config ${nebulaConfig}"; + ExecStart = "${cfg.package}/bin/nebula -config ${configFile}"; }; }; From b52a8f67dd0256fb3352121db544328dee84143c Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Tue, 9 Feb 2021 20:45:17 -0500 Subject: [PATCH 03/13] nixos/nebula: simply service user logic --- nixos/modules/services/networking/nebula.nix | 112 +++++++++---------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 888f9f96fbe6..28504cded44c 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -139,66 +139,66 @@ in # Implementation - config = - let - # The service needs to launch as root to access the tun device, if it's enabled. - serviceUser = if cfg.tun.disable then "nebula" else "root"; - serviceGroup = if cfg.tun.disable then "nebula" else "root"; - in 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; - }; + 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 = { + # Create systemd service for Nebula. + systemd.services.nebula = { + description = nebulaDesc; + after = [ "network.target" ]; + before = [ "sshd.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = mkMerge [ + { Type = "simple"; Restart = "always"; - User = serviceUser; - Group = serviceGroup; ExecStart = "${cfg.package}/bin/nebula -config ${configFile}"; - }; - }; - - # Open the chosen port for UDP. - networking.firewall.allowedUDPPorts = [ cfg.listen.port ]; - - # Create the service user and its group. - users.users."nebula" = { - name = "nebula"; - group = "nebula"; - description = "Nebula service user"; - isSystemUser = true; - packages = [ cfg.package ]; - }; - users.groups."nebula" = {}; + } + # The service needs to launch as root to access the tun device, if it's enabled. + (mkIf cfg.tun.disable { + User = "nebula"; + Group = "nebula"; + }) + ]; }; + + # Open the chosen port for UDP. + networking.firewall.allowedUDPPorts = [ cfg.listen.port ]; + + # Create the service user and its group. + users.users."nebula" = { + name = "nebula"; + group = "nebula"; + description = "Nebula service user"; + isSystemUser = true; + packages = [ cfg.package ]; + }; + users.groups."nebula" = {}; + }; } From 9f9e7c181c4aa3247b8b47febdd7f354ca492a0c Mon Sep 17 00:00:00 2001 From: Aaron Andersen Date: Tue, 9 Feb 2021 20:48:23 -0500 Subject: [PATCH 04/13] nixos/nebula: conditionally provision the nebula user --- nixos/modules/services/networking/nebula.nix | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 28504cded44c..663accc7464a 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -192,13 +192,15 @@ in networking.firewall.allowedUDPPorts = [ cfg.listen.port ]; # Create the service user and its group. - users.users."nebula" = { - name = "nebula"; - group = "nebula"; - description = "Nebula service user"; - isSystemUser = true; - packages = [ cfg.package ]; + users = mkIf cfg.tun.disable { + users.nebula = { + group = "nebula"; + description = "Nebula service user"; + isSystemUser = true; + packages = [ cfg.package ]; + }; + + groups.nebula = {}; }; - users.groups."nebula" = {}; }; } From 9f1ebd0c104a6bf945989b675a06f673b81eff58 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Sun, 28 Feb 2021 18:31:42 -0800 Subject: [PATCH 05/13] nixos/nebula: Refactor module to allow for multiple nebula services on the same machine --- nixos/modules/services/networking/nebula.nix | 353 ++++++++++--------- 1 file changed, 185 insertions(+), 168 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 663accc7464a..31c3da024372 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 From 511465ade01d1a677d2ddc32b2fa62d1bc651271 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Sun, 28 Feb 2021 18:35:16 -0800 Subject: [PATCH 06/13] nixos/nebula: Remove unnecessary package from service user --- nixos/modules/services/networking/nebula.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 31c3da024372..b936b2a1cf38 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -211,7 +211,6 @@ in group = nameToId netName; description = "Nebula service user for network ${netName}"; isSystemUser = true; - packages = [ netCfg.package ]; }; }) cfg.networks); From 17430ea40a12de82bb846edd23901425c9b96c1a Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 1 Mar 2021 20:21:27 -0800 Subject: [PATCH 07/13] nixos/nebula: Remove default punch option in favor of setting it through the settings option --- nixos/modules/services/networking/nebula.nix | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index b936b2a1cf38..6402262b59cb 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -85,14 +85,6 @@ in 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. - ''; - }; - tun.disable = mkOption { type = types.bool; default = false; @@ -164,9 +156,6 @@ in 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}"; From 9858cba4dcef4ef58edb8ab4e53c1e99b26b1295 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 4 Mar 2021 21:03:17 -0800 Subject: [PATCH 08/13] nixos/nebula: Add nebula unit test --- nixos/tests/nebula.nix | 132 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 nixos/tests/nebula.nix diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix new file mode 100644 index 000000000000..829c23524992 --- /dev/null +++ b/nixos/tests/nebula.nix @@ -0,0 +1,132 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: let + + # We'll need to be able to trade cert files between nodes via scp. + inherit (import ./ssh-keys.nix pkgs) + snakeOilPrivateKey snakeOilPublicKey; + + makeNebulaNode = { config, ... }: name: extraConfig: lib.mkMerge [ + { + # Expose nebula for doing cert signing. + environment.systemPackages = [ pkgs.nebula ]; + users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; + services.openssh.enable = true; + + services.nebula.networks.smoke = { + # Note that these paths won't exist when the machine is first booted. + ca = "/etc/nebula/ca.crt"; + cert = "/etc/nebula/${name}.crt"; + key = "/etc/nebula/${name}.key"; + listen = { host = "0.0.0.0"; port = 4242; }; + }; + } + extraConfig + ]; + +in +{ + name = "nebula"; + + nodes = { + + lighthouse = { ... } @ args: + makeNebulaNode args "lighthouse" { + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.1"; + prefixLength = 24; + }]; + + services.nebula.networks.smoke = { + isLighthouse = true; + firewall = { + outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + }; + }; + }; + + node2 = { ... } @ args: + makeNebulaNode args "node2" { + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.2"; + prefixLength = 24; + }]; + + services.nebula.networks.smoke = { + staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; + isLighthouse = false; + lighthouses = [ "10.0.100.1" ]; + firewall = { + outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + }; + }; + }; + + }; + + testScript = let + + setUpPrivateKey = name: '' + ${name}.succeed( + "mkdir -p /root/.ssh", + "chown 700 /root/.ssh", + "cat '${snakeOilPrivateKey}' > /root/.ssh/id_snakeoil", + "chown 600 /root/.ssh/id_snakeoil", + ) + ''; + + # From what I can tell, StrictHostKeyChecking=no is necessary for ssh to work between machines. + sshOpts = "-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oIdentityFile=/root/.ssh/id_snakeoil"; + + restartAndCheckNebula = name: ip: '' + ${name}.systemctl("restart nebula@smoke.service") + ${name}.succeed("ping -c5 ${ip}") + ''; + + # Create a keypair on the client node, then use the public key to sign a cert on the lighthouse. + signKeysFor = name: ip: '' + lighthouse.wait_for_unit("sshd.service") + ${name}.wait_for_unit("sshd.service") + ${name}.succeed( + "mkdir -p /etc/nebula", + "nebula-cert keygen -out-key /etc/nebula/${name}.key -out-pub /etc/nebula/${name}.pub", + "scp ${sshOpts} /etc/nebula/${name}.pub 192.168.1.1:/tmp/${name}.pub", + ) + lighthouse.succeed( + 'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "${name}" -groups "${name}" -ip "${ip}" -in-pub /tmp/${name}.pub -out-crt /tmp/${name}.crt', + ) + ${name}.succeed( + "scp ${sshOpts} 192.168.1.1:/tmp/${name}.crt /etc/nebula/${name}.crt", + "scp ${sshOpts} 192.168.1.1:/etc/nebula/ca.crt /etc/nebula/ca.crt", + ) + ''; + + in '' + start_all() + + # Create the certificate and sign the lighthouse's keys. + ${setUpPrivateKey "lighthouse"} + lighthouse.succeed( + "mkdir -p /etc/nebula", + 'nebula-cert ca -name "Smoke Test" -out-crt /etc/nebula/ca.crt -out-key /etc/nebula/ca.key', + 'nebula-cert sign -ca-crt /etc/nebula/ca.crt -ca-key /etc/nebula/ca.key -name "lighthouse" -groups "lighthouse" -ip "10.0.100.1/24" -out-crt /etc/nebula/lighthouse.crt -out-key /etc/nebula/lighthouse.key', + ) + + # Reboot the lighthouse and verify that the nebula service comes up on boot. + # Since rebooting takes a while, we'll just restart the service on the other nodes. + lighthouse.shutdown() + lighthouse.start() + lighthouse.wait_for_unit("nebula@smoke.service") + lighthouse.succeed("ping -c5 10.0.100.1") + + # Create keys on node2 and have the lighthouse sign them. + ${setUpPrivateKey "node2"} + ${signKeysFor "node2" "10.0.100.2/24"} + + # Reboot node2 and test that the nebula service comes up. + ${restartAndCheckNebula "node2" "10.0.100.2"} + + # Test that the node is now connected to the lighthouse. + node2.succeed("ping -c5 10.0.100.1") + ''; +}) \ No newline at end of file From e3f113abc2ea4108ce1d39f5332d16341e773c83 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 4 Mar 2021 21:04:18 -0800 Subject: [PATCH 09/13] nixos/nebula: Update systemd service to be more like the source repo's --- nixos/modules/services/networking/nebula.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 6402262b59cb..b4ae91e19d07 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -171,7 +171,8 @@ in # Create systemd service for Nebula. "nebula@${netName}" = { description = "Nebula VPN service for ${netName}"; - after = [ "network.target" ]; + wants = [ "basic.target" ]; + after = [ "basic.target" "network.target" ]; before = [ "sshd.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = mkMerge [ From c8dcf63b4ea6f9ffc3f79cbd823bbb1c0956efbb Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 4 Mar 2021 21:28:25 -0800 Subject: [PATCH 10/13] nixos/nebula: Expand unit test to match source repo's smoke test --- nixos/tests/nebula.nix | 74 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix index 829c23524992..c7d71c00f811 100644 --- a/nixos/tests/nebula.nix +++ b/nixos/tests/nebula.nix @@ -62,6 +62,42 @@ in }; }; + node3 = { ... } @ args: + makeNebulaNode args "node3" { + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.3"; + prefixLength = 24; + }]; + + services.nebula.networks.smoke = { + staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; + isLighthouse = false; + lighthouses = [ "10.0.100.1" ]; + firewall = { + outbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + inbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; + }; + }; + }; + + node4 = { ... } @ args: + makeNebulaNode args "node4" { + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.4"; + prefixLength = 24; + }]; + + services.nebula.networks.smoke = { + staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; + isLighthouse = false; + lighthouses = [ "10.0.100.1" ]; + firewall = { + outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; + inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + }; + }; + }; + }; testScript = let @@ -119,14 +155,42 @@ in lighthouse.wait_for_unit("nebula@smoke.service") lighthouse.succeed("ping -c5 10.0.100.1") - # Create keys on node2 and have the lighthouse sign them. + # Create keys for node2's nebula service and test that it comes up. ${setUpPrivateKey "node2"} ${signKeysFor "node2" "10.0.100.2/24"} - - # Reboot node2 and test that the nebula service comes up. ${restartAndCheckNebula "node2" "10.0.100.2"} - # Test that the node is now connected to the lighthouse. - node2.succeed("ping -c5 10.0.100.1") + # Create keys for node3's nebula service and test that it comes up. + ${setUpPrivateKey "node3"} + ${signKeysFor "node3" "10.0.100.3/24"} + ${restartAndCheckNebula "node3" "10.0.100.3"} + + # Create keys for node4's nebula service and test that it comes up. + ${setUpPrivateKey "node4"} + ${signKeysFor "node4" "10.0.100.4/24"} + ${restartAndCheckNebula "node4" "10.0.100.4"} + + # The lighthouse can ping node2 and node3 + lighthouse.succeed("ping -c3 10.0.100.2") + lighthouse.succeed("ping -c3 10.0.100.3") + + # node2 can ping the lighthouse, but not node3 because of its inbound firewall + node2.succeed("ping -c3 10.0.100.1") + node2.fail("ping -c3 10.0.100.3") + + # node3 can ping the lighthouse and node2 + node3.succeed("ping -c3 10.0.100.1") + node3.succeed("ping -c3 10.0.100.2") + + # node4 can ping the lighthouse but not node2 or node3 + node4.succeed("ping -c3 10.0.100.1") + node4.fail("ping -c3 10.0.100.2") + node4.fail("ping -c3 10.0.100.3") + + # node2 can ping node3 now that node3 pinged it first + node2.succeed("ping -c3 10.0.100.3") + # node4 can ping node2 if node2 pings it first + node2.succeed("ping -c3 10.0.100.4") + node4.succeed("ping -c3 10.0.100.2") ''; }) \ No newline at end of file From 10a6af2d610ce12e81f28e342a0dc7e104e8d28c Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 4 Mar 2021 21:28:58 -0800 Subject: [PATCH 11/13] nixos/nebula: Add nebula module and unit test to lists --- nixos/modules/module-list.nix | 1 + nixos/tests/all-tests.nix | 1 + 2 files changed, 2 insertions(+) diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c7a8f6b2f7c3..cbceef1a5449 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -699,6 +699,7 @@ ./services/networking/nar-serve.nix ./services/networking/nat.nix ./services/networking/ndppd.nix + ./services/networking/nebula.nix ./services/networking/networkmanager.nix ./services/networking/nextdns.nix ./services/networking/nftables.nix diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 05fd5c4822a7..1227611a54d0 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -250,6 +250,7 @@ in nat.standalone = handleTest ./nat.nix { withFirewall = false; }; ncdns = handleTest ./ncdns.nix {}; ndppd = handleTest ./ndppd.nix {}; + nebula = handleTest ./nebula.nix {}; neo4j = handleTest ./neo4j.nix {}; netdata = handleTest ./netdata.nix {}; networking.networkd = handleTest ./networking.nix { networkd = true; }; From 002fe4f19dcf14993dd850be100865b63bc97b80 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Thu, 4 Mar 2021 21:39:04 -0800 Subject: [PATCH 12/13] nixos/nebula: Add final newline to module and test --- nixos/modules/services/networking/nebula.nix | 2 +- nixos/tests/nebula.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index b4ae91e19d07..db6c42868d5c 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -209,4 +209,4 @@ in ${nameToId netName} = {}; }) cfg.networks); }; -} \ No newline at end of file +} diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix index c7d71c00f811..b341017295ee 100644 --- a/nixos/tests/nebula.nix +++ b/nixos/tests/nebula.nix @@ -193,4 +193,4 @@ in node2.succeed("ping -c3 10.0.100.4") node4.succeed("ping -c3 10.0.100.2") ''; -}) \ No newline at end of file +}) From 064e0af80b574bfe96540f17ddd35d6e0b1d5c71 Mon Sep 17 00:00:00 2001 From: Morgan Jones Date: Sat, 10 Apr 2021 16:38:44 -0600 Subject: [PATCH 13/13] nixos/nebula: Add enable option defaulting to true to Nebula networks --- nixos/modules/services/networking/nebula.nix | 25 +++++++++++------ nixos/tests/nebula.nix | 29 +++++++++++++++++++- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index db6c42868d5c..e7ebfe1b4db7 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -5,6 +5,7 @@ with lib; let cfg = config.services.nebula; + enabledNetworks = filterAttrs (n: v: v.enable) cfg.networks; format = pkgs.formats.yaml {}; @@ -20,6 +21,12 @@ in default = {}; type = types.attrsOf (types.submodule { options = { + enable = mkOption { + type = types.bool; + default = true; + description = "Enable or disable this network."; + }; + package = mkOption { type = types.package; default = pkgs.nebula; @@ -137,11 +144,11 @@ in }; # Implementation - config = mkIf (cfg.networks != {}) { - systemd.services = mkMerge (lib.mapAttrsToList (netName: netCfg: + config = mkIf (enabledNetworks != {}) { + systemd.services = mkMerge (mapAttrsToList (netName: netCfg: let networkId = nameToId netName; - settings = lib.recursiveUpdate { + settings = recursiveUpdate { pki = { ca = netCfg.ca; cert = netCfg.cert; @@ -188,25 +195,25 @@ in }) ]; }; - }) cfg.networks); + }) enabledNetworks); # Open the chosen ports for UDP. networking.firewall.allowedUDPPorts = - lib.unique (lib.mapAttrsToList (netName: netCfg: netCfg.listen.port) cfg.networks); + unique (mapAttrsToList (netName: netCfg: netCfg.listen.port) enabledNetworks); # Create the service users and groups. - users.users = mkMerge (lib.mapAttrsToList (netName: netCfg: + users.users = mkMerge (mapAttrsToList (netName: netCfg: mkIf netCfg.tun.disable { ${nameToId netName} = { group = nameToId netName; description = "Nebula service user for network ${netName}"; isSystemUser = true; }; - }) cfg.networks); + }) enabledNetworks); - users.groups = mkMerge (lib.mapAttrsToList (netName: netCfg: + users.groups = mkMerge (mapAttrsToList (netName: netCfg: mkIf netCfg.tun.disable { ${nameToId netName} = {}; - }) cfg.networks); + }) enabledNetworks); }; } diff --git a/nixos/tests/nebula.nix b/nixos/tests/nebula.nix index b341017295ee..372cfebdf801 100644 --- a/nixos/tests/nebula.nix +++ b/nixos/tests/nebula.nix @@ -88,6 +88,26 @@ in }]; services.nebula.networks.smoke = { + enable = true; + staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; + isLighthouse = false; + lighthouses = [ "10.0.100.1" ]; + firewall = { + outbound = [ { port = "any"; proto = "any"; host = "lighthouse"; } ]; + inbound = [ { port = "any"; proto = "any"; host = "any"; } ]; + }; + }; + }; + + node5 = { ... } @ args: + makeNebulaNode args "node5" { + networking.interfaces.eth1.ipv4.addresses = [{ + address = "192.168.1.5"; + prefixLength = 24; + }]; + + services.nebula.networks.smoke = { + enable = false; staticHostMap = { "10.0.100.1" = [ "192.168.1.1:4242" ]; }; isLighthouse = false; lighthouses = [ "10.0.100.1" ]; @@ -170,9 +190,16 @@ in ${signKeysFor "node4" "10.0.100.4/24"} ${restartAndCheckNebula "node4" "10.0.100.4"} - # The lighthouse can ping node2 and node3 + # Create keys for node4's nebula service and test that it does not come up. + ${setUpPrivateKey "node5"} + ${signKeysFor "node5" "10.0.100.5/24"} + node5.fail("systemctl status nebula@smoke.service") + node5.fail("ping -c5 10.0.100.5") + + # The lighthouse can ping node2 and node3 but not node5 lighthouse.succeed("ping -c3 10.0.100.2") lighthouse.succeed("ping -c3 10.0.100.3") + lighthouse.fail("ping -c3 10.0.100.5") # node2 can ping the lighthouse, but not node3 because of its inbound firewall node2.succeed("ping -c3 10.0.100.1")