From 9f1ebd0c104a6bf945989b675a06f673b81eff58 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Sun, 28 Feb 2021 18:31:42 -0800 Subject: nixos/nebula: Refactor module to allow for multiple nebula services on the same machine --- nixos/modules/services/networking/nebula.nix | 387 ++++++++++++++------------- 1 file changed, 202 insertions(+), 185 deletions(-) (limited to 'nixos/modules/services/networking/nebula.nix') 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; - - 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"; } ]''; - }; - - 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; + 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."; + }; + + 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.nullOr types.str; + default = null; + description = "Name of the tun device. Defaults to nebula.\${networkName}."; + }; + + 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"; } ]''; + }; + + 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"; - }) - ]; - }; - - # Open the chosen port for UDP. - networking.firewall.allowedUDPPorts = [ cfg.listen.port ]; - - # 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 ]; - }; - - groups.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 ports for UDP. + networking.firewall.allowedUDPPorts = + lib.unique (lib.mapAttrsToList (netName: netCfg: netCfg.listen.port) cfg.networks); + + # 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); + + users.groups = mkMerge (lib.mapAttrsToList (netName: netCfg: + mkIf netCfg.tun.disable { + ${nameToId netName} = {}; + }) cfg.networks); }; -} +} \ No newline at end of file -- cgit 1.4.1