at 25.11-pre 13 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8 9let 10 inherit (lib) 11 any 12 concatMap 13 getExe' 14 literalExpression 15 mkEnableOption 16 mkIf 17 mkOption 18 mkPackageOption 19 optional 20 recursiveUpdate 21 ; 22 23 inherit (lib.types) 24 bool 25 enum 26 listOf 27 port 28 str 29 ; 30 31 inherit (utils) escapeSystemdExecArgs genJqSecretsReplacementSnippet; 32 33 stateDir = "/var/lib/netbird-mgmt"; 34 35 settingsFormat = pkgs.formats.json { }; 36 37 defaultSettings = { 38 Stuns = [ 39 { 40 Proto = "udp"; 41 URI = "stun:${cfg.turnDomain}:3478"; 42 Username = ""; 43 Password = null; 44 } 45 ]; 46 47 TURNConfig = { 48 Turns = [ 49 { 50 Proto = "udp"; 51 URI = "turn:${cfg.turnDomain}:${builtins.toString cfg.turnPort}"; 52 Username = "netbird"; 53 Password = "netbird"; 54 } 55 ]; 56 57 CredentialsTTL = "12h"; 58 Secret = "not-secure-secret"; 59 TimeBasedCredentials = false; 60 }; 61 62 Signal = { 63 Proto = "https"; 64 URI = "${cfg.domain}:443"; 65 Username = ""; 66 Password = null; 67 }; 68 69 ReverseProxy = { 70 TrustedHTTPProxies = [ ]; 71 TrustedHTTPProxiesCount = 0; 72 TrustedPeers = [ "0.0.0.0/0" ]; 73 }; 74 75 Datadir = "${stateDir}/data"; 76 DataStoreEncryptionKey = "very-insecure-key"; 77 StoreConfig = { 78 Engine = "sqlite"; 79 }; 80 81 HttpConfig = { 82 Address = "127.0.0.1:${builtins.toString cfg.port}"; 83 IdpSignKeyRefreshEnabled = true; 84 OIDCConfigEndpoint = cfg.oidcConfigEndpoint; 85 }; 86 87 IdpManagerConfig = { 88 ManagerType = "none"; 89 ClientConfig = { 90 Issuer = ""; 91 TokenEndpoint = ""; 92 ClientID = "netbird"; 93 ClientSecret = ""; 94 GrantType = "client_credentials"; 95 }; 96 97 ExtraConfig = { }; 98 Auth0ClientCredentials = null; 99 AzureClientCredentials = null; 100 KeycloakClientCredentials = null; 101 ZitadelClientCredentials = null; 102 }; 103 104 DeviceAuthorizationFlow = { 105 Provider = "none"; 106 ProviderConfig = { 107 Audience = "netbird"; 108 Domain = null; 109 ClientID = "netbird"; 110 TokenEndpoint = null; 111 DeviceAuthEndpoint = ""; 112 Scope = "openid profile email"; 113 UseIDToken = false; 114 }; 115 }; 116 117 PKCEAuthorizationFlow = { 118 ProviderConfig = { 119 Audience = "netbird"; 120 ClientID = "netbird"; 121 ClientSecret = ""; 122 AuthorizationEndpoint = ""; 123 TokenEndpoint = ""; 124 Scope = "openid profile email"; 125 RedirectURLs = [ "http://localhost:53000" ]; 126 UseIDToken = false; 127 }; 128 }; 129 }; 130 131 managementConfig = recursiveUpdate defaultSettings cfg.settings; 132 133 managementFile = settingsFormat.generate "config.json" managementConfig; 134 135 cfg = config.services.netbird.server.management; 136in 137 138{ 139 options.services.netbird.server.management = { 140 enable = mkEnableOption "Netbird Management Service"; 141 142 package = mkPackageOption pkgs "netbird" { }; 143 144 domain = mkOption { 145 type = str; 146 description = "The domain under which the management API runs."; 147 }; 148 149 turnDomain = mkOption { 150 type = str; 151 description = "The domain of the TURN server to use."; 152 }; 153 154 turnPort = mkOption { 155 type = port; 156 default = 3478; 157 description = '' 158 The port of the TURN server to use. 159 ''; 160 }; 161 162 dnsDomain = mkOption { 163 type = str; 164 default = "netbird.selfhosted"; 165 description = "Domain used for peer resolution."; 166 }; 167 168 singleAccountModeDomain = mkOption { 169 type = str; 170 default = "netbird.selfhosted"; 171 description = '' 172 Enables single account mode. 173 This means that all the users will be under the same account grouped by the specified domain. 174 If the installation has more than one account, the property is ineffective. 175 ''; 176 }; 177 178 disableAnonymousMetrics = mkOption { 179 type = bool; 180 default = true; 181 description = "Disables push of anonymous usage metrics to NetBird."; 182 }; 183 184 disableSingleAccountMode = mkOption { 185 type = bool; 186 default = false; 187 description = '' 188 If set to true, disables single account mode. 189 The `singleAccountModeDomain` property will be ignored and every new user will have a separate NetBird account. 190 ''; 191 }; 192 193 port = mkOption { 194 type = port; 195 default = 8011; 196 description = "Internal port of the management server."; 197 }; 198 199 metricsPort = mkOption { 200 type = port; 201 default = 9090; 202 description = "Internal port of the metrics server."; 203 }; 204 205 extraOptions = mkOption { 206 type = listOf str; 207 default = [ ]; 208 description = '' 209 Additional options given to netbird-mgmt as commandline arguments. 210 ''; 211 }; 212 213 oidcConfigEndpoint = mkOption { 214 type = str; 215 description = "The oidc discovery endpoint."; 216 example = "https://example.eu.auth0.com/.well-known/openid-configuration"; 217 }; 218 219 settings = mkOption { 220 inherit (settingsFormat) type; 221 222 defaultText = literalExpression '' 223 defaultSettings = { 224 Stuns = [ 225 { 226 Proto = "udp"; 227 URI = "stun:''${cfg.turnDomain}:3478"; 228 Username = ""; 229 Password = null; 230 } 231 ]; 232 233 TURNConfig = { 234 Turns = [ 235 { 236 Proto = "udp"; 237 URI = "turn:''${cfg.turnDomain}:3478"; 238 Username = "netbird"; 239 Password = "netbird"; 240 } 241 ]; 242 243 CredentialsTTL = "12h"; 244 Secret = "not-secure-secret"; 245 TimeBasedCredentials = false; 246 }; 247 248 Signal = { 249 Proto = "https"; 250 URI = "''${cfg.domain}:443"; 251 Username = ""; 252 Password = null; 253 }; 254 255 ReverseProxy = { 256 TrustedHTTPProxies = [ ]; 257 TrustedHTTPProxiesCount = 0; 258 TrustedPeers = [ "0.0.0.0/0" ]; 259 }; 260 261 Datadir = "''${stateDir}/data"; 262 DataStoreEncryptionKey = "genEVP6j/Yp2EeVujm0zgqXrRos29dQkpvX0hHdEUlQ="; 263 StoreConfig = { Engine = "sqlite"; }; 264 265 HttpConfig = { 266 Address = "127.0.0.1:''${builtins.toString cfg.port}"; 267 IdpSignKeyRefreshEnabled = true; 268 OIDCConfigEndpoint = cfg.oidcConfigEndpoint; 269 }; 270 271 IdpManagerConfig = { 272 ManagerType = "none"; 273 ClientConfig = { 274 Issuer = ""; 275 TokenEndpoint = ""; 276 ClientID = "netbird"; 277 ClientSecret = ""; 278 GrantType = "client_credentials"; 279 }; 280 281 ExtraConfig = { }; 282 Auth0ClientCredentials = null; 283 AzureClientCredentials = null; 284 KeycloakClientCredentials = null; 285 ZitadelClientCredentials = null; 286 }; 287 288 DeviceAuthorizationFlow = { 289 Provider = "none"; 290 ProviderConfig = { 291 Audience = "netbird"; 292 Domain = null; 293 ClientID = "netbird"; 294 TokenEndpoint = null; 295 DeviceAuthEndpoint = ""; 296 Scope = "openid profile email offline_access api"; 297 UseIDToken = false; 298 }; 299 }; 300 301 PKCEAuthorizationFlow = { 302 ProviderConfig = { 303 Audience = "netbird"; 304 ClientID = "netbird"; 305 ClientSecret = ""; 306 AuthorizationEndpoint = ""; 307 TokenEndpoint = ""; 308 Scope = "openid profile email offline_access api"; 309 RedirectURLs = "http://localhost:53000"; 310 UseIDToken = false; 311 }; 312 }; 313 }; 314 ''; 315 316 default = { }; 317 318 description = '' 319 Configuration of the netbird management server. 320 Options containing secret data should be set to an attribute set containing the attribute _secret 321 - a string pointing to a file containing the value the option should be set to. 322 See the example to get a better picture of this: in the resulting management.json file, 323 the `DataStoreEncryptionKey` key will be set to the contents of the /run/agenix/netbird_mgmt-data_store_encryption_key file. 324 ''; 325 326 example = { 327 DataStoreEncryptionKey = { 328 _secret = "/run/agenix/netbird_mgmt-data_store_encryption_key"; 329 }; 330 }; 331 }; 332 333 logLevel = mkOption { 334 type = enum [ 335 "ERROR" 336 "WARN" 337 "INFO" 338 "DEBUG" 339 ]; 340 default = "INFO"; 341 description = "Log level of the netbird services."; 342 }; 343 344 enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird management service"; 345 }; 346 347 config = mkIf cfg.enable { 348 warnings = 349 concatMap 350 ( 351 { check, name }: 352 optional check "${name} is world-readable in the Nix Store, you should provide it as a _secret." 353 ) 354 [ 355 { 356 check = builtins.isString managementConfig.TURNConfig.Secret; 357 name = "The TURNConfig.secret"; 358 } 359 { 360 check = builtins.isString managementConfig.DataStoreEncryptionKey; 361 name = "The DataStoreEncryptionKey"; 362 } 363 { 364 check = any (T: (T ? Password) && builtins.isString T.Password) managementConfig.TURNConfig.Turns; 365 name = "A Turn configuration's password"; 366 } 367 ]; 368 369 assertions = [ 370 { 371 assertion = cfg.port != cfg.metricsPort; 372 message = "The primary listen port cannot be the same as the listen port for the metrics endpoint"; 373 } 374 ]; 375 376 systemd.services.netbird-management = { 377 description = "The management server for Netbird, a wireguard VPN"; 378 documentation = [ "https://netbird.io/docs/" ]; 379 380 after = [ "network.target" ]; 381 wantedBy = [ "multi-user.target" ]; 382 restartTriggers = [ managementFile ]; 383 384 preStart = genJqSecretsReplacementSnippet managementConfig "${stateDir}/management.json"; 385 386 serviceConfig = { 387 ExecStart = escapeSystemdExecArgs ( 388 [ 389 (getExe' cfg.package "netbird-mgmt") 390 "management" 391 # Config file 392 "--config" 393 "${stateDir}/management.json" 394 # Data directory 395 "--datadir" 396 "${stateDir}/data" 397 # DNS domain 398 "--dns-domain" 399 cfg.dnsDomain 400 # Port to listen on 401 "--port" 402 cfg.port 403 # Port the internal prometheus server listens on 404 "--metrics-port" 405 cfg.metricsPort 406 # Log to stdout 407 "--log-file" 408 "console" 409 # Log level 410 "--log-level" 411 cfg.logLevel 412 # 413 "--idp-sign-key-refresh-enabled" 414 # Domain for internal resolution 415 "--single-account-mode-domain" 416 cfg.singleAccountModeDomain 417 ] 418 ++ (optional cfg.disableAnonymousMetrics "--disable-anonymous-metrics") 419 ++ (optional cfg.disableSingleAccountMode "--disable-single-account-mode") 420 ++ cfg.extraOptions 421 ); 422 Restart = "always"; 423 RuntimeDirectory = "netbird-mgmt"; 424 StateDirectory = [ 425 "netbird-mgmt" 426 "netbird-mgmt/data" 427 ]; 428 StateDirectoryMode = "0750"; 429 RuntimeDirectoryMode = "0750"; 430 WorkingDirectory = stateDir; 431 432 # hardening 433 LockPersonality = true; 434 MemoryDenyWriteExecute = true; 435 NoNewPrivileges = true; 436 PrivateMounts = true; 437 PrivateTmp = true; 438 ProtectClock = true; 439 ProtectControlGroups = true; 440 ProtectHome = true; 441 ProtectHostname = true; 442 ProtectKernelLogs = true; 443 ProtectKernelModules = true; 444 ProtectKernelTunables = true; 445 ProtectSystem = true; 446 RemoveIPC = true; 447 RestrictNamespaces = true; 448 RestrictRealtime = true; 449 RestrictSUIDSGID = true; 450 }; 451 452 stopIfChanged = false; 453 }; 454 455 services.nginx = mkIf cfg.enableNginx { 456 enable = true; 457 458 virtualHosts.${cfg.domain} = { 459 locations = { 460 "/api".proxyPass = "http://localhost:${builtins.toString cfg.port}"; 461 462 "/management.ManagementService/".extraConfig = '' 463 # This is necessary so that grpc connections do not get closed early 464 # see https://stackoverflow.com/a/67805465 465 client_body_timeout 1d; 466 467 grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 468 469 grpc_pass grpc://localhost:${builtins.toString cfg.port}; 470 grpc_read_timeout 1d; 471 grpc_send_timeout 1d; 472 grpc_socket_keepalive on; 473 ''; 474 }; 475 }; 476 }; 477 }; 478}