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