nixos/netbird-server: init module
This commit is contained in:
parent
106b852a30
commit
6d91c95fce
8 changed files with 1041 additions and 0 deletions
|
@ -108,6 +108,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
|
||||||
|
|
||||||
- [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable).
|
- [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable).
|
||||||
|
|
||||||
|
- A self-hosted management server for the [Netbird](https://netbird.io). Available as [services.netbird.server](#opt-services.netbird.server.enable).
|
||||||
|
|
||||||
- [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable).
|
- [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable).
|
||||||
|
|
||||||
- [TigerBeetle](https://tigerbeetle.com/), a distributed financial accounting database designed for mission critical safety and performance. Available as [services.tigerbeetle](#opt-services.tigerbeetle.enable).
|
- [TigerBeetle](https://tigerbeetle.com/), a distributed financial accounting database designed for mission critical safety and performance. Available as [services.tigerbeetle](#opt-services.tigerbeetle.enable).
|
||||||
|
|
|
@ -1052,6 +1052,7 @@
|
||||||
./services/networking/ndppd.nix
|
./services/networking/ndppd.nix
|
||||||
./services/networking/nebula.nix
|
./services/networking/nebula.nix
|
||||||
./services/networking/netbird.nix
|
./services/networking/netbird.nix
|
||||||
|
./services/networking/netbird/server.nix
|
||||||
./services/networking/netclient.nix
|
./services/networking/netclient.nix
|
||||||
./services/networking/networkd-dispatcher.nix
|
./services/networking/networkd-dispatcher.nix
|
||||||
./services/networking/networkmanager.nix
|
./services/networking/networkmanager.nix
|
||||||
|
|
160
nixos/modules/services/networking/netbird/coturn.nix
Normal file
160
nixos/modules/services/networking/netbird/coturn.nix
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
getExe
|
||||||
|
literalExpression
|
||||||
|
mkAfter
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkMerge
|
||||||
|
mkOption
|
||||||
|
optionalAttrs
|
||||||
|
optionalString
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.types)
|
||||||
|
bool
|
||||||
|
listOf
|
||||||
|
nullOr
|
||||||
|
path
|
||||||
|
port
|
||||||
|
str
|
||||||
|
;
|
||||||
|
|
||||||
|
cfg = config.services.netbird.server.coturn;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.services.netbird.server.coturn = {
|
||||||
|
enable = mkEnableOption "a Coturn server for Netbird, will also open the firewall on the configured range";
|
||||||
|
|
||||||
|
useAcmeCertificates = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to use ACME certificates corresponding to the given domain for the server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The domain under which the coturn server runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "netbird";
|
||||||
|
description = ''
|
||||||
|
The username used by netbird to connect to the coturn server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
password = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The password of the user used by netbird to connect to the coturn server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = mkOption {
|
||||||
|
type = nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The path to a file containing the password of the user used by netbird to connect to the coturn server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
openPorts = mkOption {
|
||||||
|
type = listOf port;
|
||||||
|
default = with config.services.coturn; [
|
||||||
|
listening-port
|
||||||
|
alt-listening-port
|
||||||
|
tls-listening-port
|
||||||
|
alt-tls-listening-port
|
||||||
|
];
|
||||||
|
defaultText = literalExpression ''
|
||||||
|
with config.services.coturn; [
|
||||||
|
listening-port
|
||||||
|
alt-listening-port
|
||||||
|
tls-listening-port
|
||||||
|
alt-tls-listening-port
|
||||||
|
];
|
||||||
|
'';
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
The list of ports used by coturn for listening to open in the firewall.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
{
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = (cfg.password == null) != (cfg.passwordFile == null);
|
||||||
|
message = "Exactly one of `password` or `passwordFile` must be given for the coturn setup.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
services.coturn =
|
||||||
|
{
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
realm = cfg.domain;
|
||||||
|
lt-cred-mech = true;
|
||||||
|
no-cli = true;
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
fingerprint
|
||||||
|
user=${cfg.user}:${if cfg.password != null then cfg.password else "@password@"}
|
||||||
|
no-software-attribute
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
// (optionalAttrs cfg.useAcmeCertificates {
|
||||||
|
cert = "@cert@";
|
||||||
|
pkey = "@pkey@";
|
||||||
|
});
|
||||||
|
|
||||||
|
systemd.services.coturn =
|
||||||
|
let
|
||||||
|
dir = config.security.acme.certs.${cfg.domain}.directory;
|
||||||
|
preStart' =
|
||||||
|
(optionalString (cfg.passwordFile != null) ''
|
||||||
|
${getExe pkgs.replace-secret} @password@ ${cfg.passwordFile} /run/coturn/turnserver.cfg
|
||||||
|
'')
|
||||||
|
+ (optionalString cfg.useAcmeCertificates ''
|
||||||
|
${getExe pkgs.replace-secret} @cert@ "$CREDENTIALS_DIRECTORY/cert.pem" /run/coturn/turnserver.cfg
|
||||||
|
${getExe pkgs.replace-secret} @pkey@ "$CREDENTIALS_DIRECTORY/pkey.pem" /run/coturn/turnserver.cfg
|
||||||
|
'');
|
||||||
|
in
|
||||||
|
(optionalAttrs (preStart' != "") { preStart = mkAfter preStart'; })
|
||||||
|
// (optionalAttrs cfg.useAcmeCertificates {
|
||||||
|
serviceConfig.LoadCredential = [
|
||||||
|
"cert.pem:${dir}/fullchain.pem"
|
||||||
|
"pkey.pem:${dir}/key.pem"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
security.acme.certs.${cfg.domain}.postRun = optionalString cfg.useAcmeCertificates "systemctl restart coturn.service";
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedUDPPorts = cfg.openPorts;
|
||||||
|
allowedTCPPorts = cfg.openPorts;
|
||||||
|
|
||||||
|
allowedUDPPortRanges = [
|
||||||
|
{
|
||||||
|
from = cfg.minPort;
|
||||||
|
to = cfg.maxPort;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
186
nixos/modules/services/networking/netbird/dashboard.nix
Normal file
186
nixos/modules/services/networking/netbird/dashboard.nix
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
boolToString
|
||||||
|
concatStringsSep
|
||||||
|
hasAttr
|
||||||
|
isBool
|
||||||
|
mapAttrs
|
||||||
|
mkDefault
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkOption
|
||||||
|
mkPackageOption
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.types)
|
||||||
|
attrsOf
|
||||||
|
bool
|
||||||
|
either
|
||||||
|
package
|
||||||
|
str
|
||||||
|
submodule
|
||||||
|
;
|
||||||
|
|
||||||
|
toStringEnv = value: if isBool value then boolToString value else toString value;
|
||||||
|
|
||||||
|
cfg = config.services.netbird.server.dashboard;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.services.netbird.server.dashboard = {
|
||||||
|
enable = mkEnableOption "the static netbird dashboard frontend";
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "netbird-dashboard" { };
|
||||||
|
|
||||||
|
enableNginx = mkEnableOption "Nginx reverse-proxy to serve the dashboard.";
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "localhost";
|
||||||
|
description = "The domain under which the dashboard runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
managementServer = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The address of the management server, used for the API endpoints.";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
type = submodule { freeformType = attrsOf (either str bool); };
|
||||||
|
|
||||||
|
defaultText = ''
|
||||||
|
{
|
||||||
|
AUTH_AUDIENCE = "netbird";
|
||||||
|
AUTH_CLIENT_ID = "netbird";
|
||||||
|
AUTH_SUPPORTED_SCOPES = "openid profile email";
|
||||||
|
NETBIRD_TOKEN_SOURCE = "idToken";
|
||||||
|
USE_AUTH0 = false;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
An attribute set that will be used to substitute variables when building the dashboard.
|
||||||
|
Any values set here will be templated into the frontend and be public for anyone that can reach your website.
|
||||||
|
The exact values sadly aren't documented anywhere.
|
||||||
|
A starting point when searching for valid values is this [script](https://github.com/netbirdio/dashboard/blob/main/docker/init_react_envs.sh)
|
||||||
|
The only mandatory value is 'AUTH_AUTHORITY' as we cannot set a default value here.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
finalDrv = mkOption {
|
||||||
|
readOnly = true;
|
||||||
|
type = package;
|
||||||
|
description = ''
|
||||||
|
The derivation containing the final templated dashboard.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = hasAttr "AUTH_AUTHORITY" cfg.settings;
|
||||||
|
message = "The setting AUTH_AUTHORITY is required for the dasboard to function.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
services.netbird.server.dashboard = {
|
||||||
|
settings =
|
||||||
|
{
|
||||||
|
# Due to how the backend and frontend work this secret will be templated into the backend
|
||||||
|
# and then served statically from your website
|
||||||
|
# This enables you to login without the normally needed indirection through the backend
|
||||||
|
# but this also means anyone that can reach your website can
|
||||||
|
# fetch this secret, which is why there is no real need to put it into
|
||||||
|
# special options as its public anyway
|
||||||
|
# As far as I know leaking this secret is just
|
||||||
|
# an information leak as one can fetch some basic app
|
||||||
|
# informations from the IDP
|
||||||
|
# To actually do something one still needs to have login
|
||||||
|
# data and this secret so this being public will not
|
||||||
|
# suffice for anything just decreasing security
|
||||||
|
AUTH_CLIENT_SECRET = "";
|
||||||
|
|
||||||
|
NETBIRD_MGMT_API_ENDPOINT = cfg.managementServer;
|
||||||
|
NETBIRD_MGMT_GRPC_API_ENDPOINT = cfg.managementServer;
|
||||||
|
}
|
||||||
|
// (mapAttrs (_: mkDefault) {
|
||||||
|
# Those values have to be easily overridable
|
||||||
|
AUTH_AUDIENCE = "netbird"; # must be set for your devices to be able to log in
|
||||||
|
AUTH_CLIENT_ID = "netbird";
|
||||||
|
AUTH_SUPPORTED_SCOPES = "openid profile email";
|
||||||
|
NETBIRD_TOKEN_SOURCE = "idToken";
|
||||||
|
USE_AUTH0 = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
# The derivation containing the templated dashboard
|
||||||
|
finalDrv =
|
||||||
|
pkgs.runCommand "netbird-dashboard"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = [ pkgs.gettext ];
|
||||||
|
env = {
|
||||||
|
ENV_STR = concatStringsSep " " [
|
||||||
|
"$AUTH_AUDIENCE"
|
||||||
|
"$AUTH_AUTHORITY"
|
||||||
|
"$AUTH_CLIENT_ID"
|
||||||
|
"$AUTH_CLIENT_SECRET"
|
||||||
|
"$AUTH_REDIRECT_URI"
|
||||||
|
"$AUTH_SILENT_REDIRECT_URI"
|
||||||
|
"$AUTH_SUPPORTED_SCOPES"
|
||||||
|
"$NETBIRD_DRAG_QUERY_PARAMS"
|
||||||
|
"$NETBIRD_GOOGLE_ANALYTICS_ID"
|
||||||
|
"$NETBIRD_HOTJAR_TRACK_ID"
|
||||||
|
"$NETBIRD_MGMT_API_ENDPOINT"
|
||||||
|
"$NETBIRD_MGMT_GRPC_API_ENDPOINT"
|
||||||
|
"$NETBIRD_TOKEN_SOURCE"
|
||||||
|
"$USE_AUTH0"
|
||||||
|
];
|
||||||
|
} // (mapAttrs (_: toStringEnv) cfg.settings);
|
||||||
|
}
|
||||||
|
''
|
||||||
|
cp -R ${cfg.package} build
|
||||||
|
|
||||||
|
find build -type d -exec chmod 755 {} \;
|
||||||
|
OIDC_TRUSTED_DOMAINS="build/OidcTrustedDomains.js"
|
||||||
|
|
||||||
|
envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS.tmpl" > "$OIDC_TRUSTED_DOMAINS"
|
||||||
|
|
||||||
|
for f in $(grep -R -l AUTH_SUPPORTED_SCOPES build/); do
|
||||||
|
mv "$f" "$f.copy"
|
||||||
|
envsubst "$ENV_STR" < "$f.copy" > "$f"
|
||||||
|
rm "$f.copy"
|
||||||
|
done
|
||||||
|
|
||||||
|
cp -R build $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = mkIf cfg.enableNginx {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
locations = {
|
||||||
|
"/" = {
|
||||||
|
root = cfg.finalDrv;
|
||||||
|
tryFiles = "$uri $uri.html $uri/ =404";
|
||||||
|
};
|
||||||
|
|
||||||
|
"/404.html".extraConfig = ''
|
||||||
|
internal;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
error_page 404 /404.html;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
460
nixos/modules/services/networking/netbird/management.nix
Normal file
460
nixos/modules/services/networking/netbird/management.nix
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
any
|
||||||
|
concatMap
|
||||||
|
getExe'
|
||||||
|
literalExpression
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkOption
|
||||||
|
mkPackageOption
|
||||||
|
optional
|
||||||
|
recursiveUpdate
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.types)
|
||||||
|
bool
|
||||||
|
enum
|
||||||
|
listOf
|
||||||
|
port
|
||||||
|
str
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (utils) escapeSystemdExecArgs genJqSecretsReplacementSnippet;
|
||||||
|
|
||||||
|
stateDir = "/var/lib/netbird-mgmt";
|
||||||
|
|
||||||
|
settingsFormat = pkgs.formats.json { };
|
||||||
|
|
||||||
|
defaultSettings = {
|
||||||
|
Stuns = [
|
||||||
|
{
|
||||||
|
Proto = "udp";
|
||||||
|
URI = "stun:${cfg.turnDomain}:3478";
|
||||||
|
Username = "";
|
||||||
|
Password = null;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
TURNConfig = {
|
||||||
|
Turns = [
|
||||||
|
{
|
||||||
|
Proto = "udp";
|
||||||
|
URI = "turn:${cfg.turnDomain}:${builtins.toString cfg.turnPort}";
|
||||||
|
Username = "netbird";
|
||||||
|
Password = "netbird";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
CredentialsTTL = "12h";
|
||||||
|
Secret = "not-secure-secret";
|
||||||
|
TimeBasedCredentials = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Signal = {
|
||||||
|
Proto = "https";
|
||||||
|
URI = "${cfg.domain}:443";
|
||||||
|
Username = "";
|
||||||
|
Password = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ReverseProxy = {
|
||||||
|
TrustedHTTPProxies = [ ];
|
||||||
|
TrustedHTTPProxiesCount = 0;
|
||||||
|
TrustedPeers = [ "0.0.0.0/0" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Datadir = "${stateDir}/data";
|
||||||
|
DataStoreEncryptionKey = "very-insecure-key";
|
||||||
|
StoreConfig = {
|
||||||
|
Engine = "sqlite";
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpConfig = {
|
||||||
|
Address = "127.0.0.1:${builtins.toString cfg.port}";
|
||||||
|
IdpSignKeyRefreshEnabled = true;
|
||||||
|
OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
IdpManagerConfig = {
|
||||||
|
ManagerType = "none";
|
||||||
|
ClientConfig = {
|
||||||
|
Issuer = "";
|
||||||
|
TokenEndpoint = "";
|
||||||
|
ClientID = "netbird";
|
||||||
|
ClientSecret = "";
|
||||||
|
GrantType = "client_credentials";
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtraConfig = { };
|
||||||
|
Auth0ClientCredentials = null;
|
||||||
|
AzureClientCredentials = null;
|
||||||
|
KeycloakClientCredentials = null;
|
||||||
|
ZitadelClientCredentials = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceAuthorizationFlow = {
|
||||||
|
Provider = "none";
|
||||||
|
ProviderConfig = {
|
||||||
|
Audience = "netbird";
|
||||||
|
Domain = null;
|
||||||
|
ClientID = "netbird";
|
||||||
|
TokenEndpoint = null;
|
||||||
|
DeviceAuthEndpoint = "";
|
||||||
|
Scope = "openid profile email";
|
||||||
|
UseIDToken = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
PKCEAuthorizationFlow = {
|
||||||
|
ProviderConfig = {
|
||||||
|
Audience = "netbird";
|
||||||
|
ClientID = "netbird";
|
||||||
|
ClientSecret = "";
|
||||||
|
AuthorizationEndpoint = "";
|
||||||
|
TokenEndpoint = "";
|
||||||
|
Scope = "openid profile email";
|
||||||
|
RedirectURLs = [ "http://localhost:53000" ];
|
||||||
|
UseIDToken = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
managementConfig = recursiveUpdate defaultSettings cfg.settings;
|
||||||
|
|
||||||
|
managementFile = settingsFormat.generate "config.json" managementConfig;
|
||||||
|
|
||||||
|
cfg = config.services.netbird.server.management;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.services.netbird.server.management = {
|
||||||
|
enable = mkEnableOption "Netbird Management Service.";
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "netbird" { };
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The domain under which the management API runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
turnDomain = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The domain of the TURN server to use.";
|
||||||
|
};
|
||||||
|
|
||||||
|
turnPort = mkOption {
|
||||||
|
type = port;
|
||||||
|
default = 3478;
|
||||||
|
description = ''
|
||||||
|
The port of the TURN server to use.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dnsDomain = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "netbird.selfhosted";
|
||||||
|
description = "Domain used for peer resolution.";
|
||||||
|
};
|
||||||
|
|
||||||
|
singleAccountModeDomain = mkOption {
|
||||||
|
type = str;
|
||||||
|
default = "netbird.selfhosted";
|
||||||
|
description = ''
|
||||||
|
Enables single account mode.
|
||||||
|
This means that all the users will be under the same account grouped by the specified domain.
|
||||||
|
If the installation has more than one account, the property is ineffective.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
disableAnonymousMetrics = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = true;
|
||||||
|
description = "Disables push of anonymous usage metrics to NetBird.";
|
||||||
|
};
|
||||||
|
|
||||||
|
disableSingleAccountMode = mkOption {
|
||||||
|
type = bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
If set to true, disables single account mode.
|
||||||
|
The `singleAccountModeDomain` property will be ignored and every new user will have a separate NetBird account.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = port;
|
||||||
|
default = 8011;
|
||||||
|
description = "Internal port of the management server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraOptions = mkOption {
|
||||||
|
type = listOf str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Additional options given to netbird-mgmt as commandline arguments.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
oidcConfigEndpoint = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The oidc discovery endpoint.";
|
||||||
|
example = "https://example.eu.auth0.com/.well-known/openid-configuration";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
inherit (settingsFormat) type;
|
||||||
|
|
||||||
|
defaultText = literalExpression ''
|
||||||
|
defaultSettings = {
|
||||||
|
Stuns = [
|
||||||
|
{
|
||||||
|
Proto = "udp";
|
||||||
|
URI = "stun:''${cfg.turnDomain}:3478";
|
||||||
|
Username = "";
|
||||||
|
Password = null;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
TURNConfig = {
|
||||||
|
Turns = [
|
||||||
|
{
|
||||||
|
Proto = "udp";
|
||||||
|
URI = "turn:''${cfg.turnDomain}:3478";
|
||||||
|
Username = "netbird";
|
||||||
|
Password = "netbird";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
CredentialsTTL = "12h";
|
||||||
|
Secret = "not-secure-secret";
|
||||||
|
TimeBasedCredentials = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Signal = {
|
||||||
|
Proto = "https";
|
||||||
|
URI = "''${cfg.domain}:443";
|
||||||
|
Username = "";
|
||||||
|
Password = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
ReverseProxy = {
|
||||||
|
TrustedHTTPProxies = [ ];
|
||||||
|
TrustedHTTPProxiesCount = 0;
|
||||||
|
TrustedPeers = [ "0.0.0.0/0" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Datadir = "''${stateDir}/data";
|
||||||
|
DataStoreEncryptionKey = "genEVP6j/Yp2EeVujm0zgqXrRos29dQkpvX0hHdEUlQ=";
|
||||||
|
StoreConfig = { Engine = "sqlite"; };
|
||||||
|
|
||||||
|
HttpConfig = {
|
||||||
|
Address = "127.0.0.1:''${builtins.toString cfg.port}";
|
||||||
|
IdpSignKeyRefreshEnabled = true;
|
||||||
|
OIDCConfigEndpoint = cfg.oidcConfigEndpoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
IdpManagerConfig = {
|
||||||
|
ManagerType = "none";
|
||||||
|
ClientConfig = {
|
||||||
|
Issuer = "";
|
||||||
|
TokenEndpoint = "";
|
||||||
|
ClientID = "netbird";
|
||||||
|
ClientSecret = "";
|
||||||
|
GrantType = "client_credentials";
|
||||||
|
};
|
||||||
|
|
||||||
|
ExtraConfig = { };
|
||||||
|
Auth0ClientCredentials = null;
|
||||||
|
AzureClientCredentials = null;
|
||||||
|
KeycloakClientCredentials = null;
|
||||||
|
ZitadelClientCredentials = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceAuthorizationFlow = {
|
||||||
|
Provider = "none";
|
||||||
|
ProviderConfig = {
|
||||||
|
Audience = "netbird";
|
||||||
|
Domain = null;
|
||||||
|
ClientID = "netbird";
|
||||||
|
TokenEndpoint = null;
|
||||||
|
DeviceAuthEndpoint = "";
|
||||||
|
Scope = "openid profile email offline_access api";
|
||||||
|
UseIDToken = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
PKCEAuthorizationFlow = {
|
||||||
|
ProviderConfig = {
|
||||||
|
Audience = "netbird";
|
||||||
|
ClientID = "netbird";
|
||||||
|
ClientSecret = "";
|
||||||
|
AuthorizationEndpoint = "";
|
||||||
|
TokenEndpoint = "";
|
||||||
|
Scope = "openid profile email offline_access api";
|
||||||
|
RedirectURLs = "http://localhost:53000";
|
||||||
|
UseIDToken = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'';
|
||||||
|
|
||||||
|
default = { };
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
Configuration of the netbird management server.
|
||||||
|
Options containing secret data should be set to an attribute set containing the attribute _secret
|
||||||
|
- a string pointing to a file containing the value the option should be set to.
|
||||||
|
See the example to get a better picture of this: in the resulting management.json file,
|
||||||
|
the `DataStoreEncryptionKey` key will be set to the contents of the /run/agenix/netbird_mgmt-data_store_encryption_key file.
|
||||||
|
'';
|
||||||
|
|
||||||
|
example = {
|
||||||
|
DataStoreEncryptionKey = {
|
||||||
|
_secret = "/run/agenix/netbird_mgmt-data_store_encryption_key";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
logLevel = mkOption {
|
||||||
|
type = enum [
|
||||||
|
"ERROR"
|
||||||
|
"WARN"
|
||||||
|
"INFO"
|
||||||
|
"DEBUG"
|
||||||
|
];
|
||||||
|
default = "INFO";
|
||||||
|
description = "Log level of the netbird services.";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird management service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
warnings =
|
||||||
|
concatMap
|
||||||
|
(
|
||||||
|
{ check, name }:
|
||||||
|
optional check "${name} is world-readable in the Nix Store, you should provide it as a _secret."
|
||||||
|
)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
check = builtins.isString managementConfig.TURNConfig.Secret;
|
||||||
|
name = "The TURNConfig.secret";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
check = builtins.isString managementConfig.DataStoreEncryptionKey;
|
||||||
|
name = "The DataStoreEncryptionKey";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
check = any (T: (T ? Password) && builtins.isString T.Password) managementConfig.TURNConfig.Turns;
|
||||||
|
name = "A Turn configuration's password";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.netbird-management = {
|
||||||
|
description = "The management server for Netbird, a wireguard VPN";
|
||||||
|
documentation = [ "https://netbird.io/docs/" ];
|
||||||
|
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
restartTriggers = [ managementFile ];
|
||||||
|
|
||||||
|
preStart = genJqSecretsReplacementSnippet managementConfig "${stateDir}/management.json";
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = escapeSystemdExecArgs (
|
||||||
|
[
|
||||||
|
(getExe' cfg.package "netbird-mgmt")
|
||||||
|
"management"
|
||||||
|
# Config file
|
||||||
|
"--config"
|
||||||
|
"${stateDir}/management.json"
|
||||||
|
# Data directory
|
||||||
|
"--datadir"
|
||||||
|
"${stateDir}/data"
|
||||||
|
# DNS domain
|
||||||
|
"--dns-domain"
|
||||||
|
cfg.dnsDomain
|
||||||
|
# Port to listen on
|
||||||
|
"--port"
|
||||||
|
cfg.port
|
||||||
|
# Log to stdout
|
||||||
|
"--log-file"
|
||||||
|
"console"
|
||||||
|
# Log level
|
||||||
|
"--log-level"
|
||||||
|
cfg.logLevel
|
||||||
|
#
|
||||||
|
"--idp-sign-key-refresh-enabled"
|
||||||
|
# Domain for internal resolution
|
||||||
|
"--single-account-mode-domain"
|
||||||
|
cfg.singleAccountModeDomain
|
||||||
|
]
|
||||||
|
++ (optional cfg.disableAnonymousMetrics "--disable-anonymous-metrics")
|
||||||
|
++ (optional cfg.disableSingleAccountMode "--disable-single-account-mode")
|
||||||
|
++ cfg.extraOptions
|
||||||
|
);
|
||||||
|
Restart = "always";
|
||||||
|
RuntimeDirectory = "netbird-mgmt";
|
||||||
|
StateDirectory = [
|
||||||
|
"netbird-mgmt"
|
||||||
|
"netbird-mgmt/data"
|
||||||
|
];
|
||||||
|
WorkingDirectory = stateDir;
|
||||||
|
|
||||||
|
# hardening
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectSystem = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
stopIfChanged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = mkIf cfg.enableNginx {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
locations = {
|
||||||
|
"/api".proxyPass = "http://localhost:${builtins.toString cfg.port}";
|
||||||
|
|
||||||
|
"/management.ManagementService/".extraConfig = ''
|
||||||
|
# This is necessary so that grpc connections do not get closed early
|
||||||
|
# see https://stackoverflow.com/a/67805465
|
||||||
|
client_body_timeout 1d;
|
||||||
|
|
||||||
|
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
grpc_pass grpc://localhost:${builtins.toString cfg.port};
|
||||||
|
grpc_read_timeout 1d;
|
||||||
|
grpc_send_timeout 1d;
|
||||||
|
grpc_socket_keepalive on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
42
nixos/modules/services/networking/netbird/server.md
Normal file
42
nixos/modules/services/networking/netbird/server.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# Netbird server {#module-services-netbird-server}
|
||||||
|
|
||||||
|
NetBird is a VPN built on top of WireGuard® making it easy to create secure private networks for your organization or home.
|
||||||
|
|
||||||
|
## Quickstart {#module-services-netbird-server-quickstart}
|
||||||
|
|
||||||
|
To fully setup Netbird as a self-hosted server, we need both a Coturn server and an identity provider, the list of supported SSOs and their setup are available [on Netbird's documentation](https://docs.netbird.io/selfhosted/selfhosted-guide#step-3-configure-identity-provider-idp).
|
||||||
|
|
||||||
|
There are quite a few settings that need to be passed to Netbird for it to function, and a minimal config looks like :
|
||||||
|
|
||||||
|
```nix
|
||||||
|
services.netbird.server = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
domain = "netbird.example.selfhosted";
|
||||||
|
|
||||||
|
enableNginx = true;
|
||||||
|
|
||||||
|
coturn = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
passwordFile = "/path/to/a/secret/password";
|
||||||
|
};
|
||||||
|
|
||||||
|
management = {
|
||||||
|
oidcConfigEndpoint = "https://sso.example.selfhosted/oauth2/openid/netbird/.well-known/openid-configuration";
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
TURNConfig = {
|
||||||
|
Turns = [
|
||||||
|
{
|
||||||
|
Proto = "udp";
|
||||||
|
URI = "turn:netbird.example.selfhosted:3478";
|
||||||
|
Username = "netbird";
|
||||||
|
Password._secret = "/path/to/a/secret/password";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
67
nixos/modules/services/networking/netbird/server.nix
Normal file
67
nixos/modules/services/networking/netbird/server.nix
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkOption
|
||||||
|
optionalAttrs
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.types) str;
|
||||||
|
|
||||||
|
cfg = config.services.netbird.server;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
meta = {
|
||||||
|
maintainers = with lib.maintainers; [ thubrecht ];
|
||||||
|
doc = ./server.md;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Import the separate components
|
||||||
|
imports = [
|
||||||
|
./coturn.nix
|
||||||
|
./dashboard.nix
|
||||||
|
./management.nix
|
||||||
|
./signal.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
options.services.netbird.server = {
|
||||||
|
enable = mkEnableOption "Netbird Server stack, comprising the dashboard, management API and signal service";
|
||||||
|
|
||||||
|
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird server services.";
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The domain under which the netbird server runs.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.netbird.server = {
|
||||||
|
dashboard = {
|
||||||
|
inherit (cfg) enable domain enableNginx;
|
||||||
|
|
||||||
|
managementServer = "https://${cfg.domain}";
|
||||||
|
};
|
||||||
|
|
||||||
|
management =
|
||||||
|
{
|
||||||
|
inherit (cfg) enable domain enableNginx;
|
||||||
|
}
|
||||||
|
// (optionalAttrs cfg.coturn.enable {
|
||||||
|
turnDomain = cfg.domain;
|
||||||
|
turnPort = config.services.coturn.tls-listening-port;
|
||||||
|
});
|
||||||
|
|
||||||
|
signal = {
|
||||||
|
inherit (cfg) enable domain enableNginx;
|
||||||
|
};
|
||||||
|
|
||||||
|
coturn = {
|
||||||
|
inherit (cfg) domain;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
123
nixos/modules/services/networking/netbird/signal.nix
Normal file
123
nixos/modules/services/networking/netbird/signal.nix
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
utils,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
getExe'
|
||||||
|
mkEnableOption
|
||||||
|
mkIf
|
||||||
|
mkPackageOption
|
||||||
|
mkOption
|
||||||
|
;
|
||||||
|
|
||||||
|
inherit (lib.types) enum port str;
|
||||||
|
|
||||||
|
inherit (utils) escapeSystemdExecArgs;
|
||||||
|
|
||||||
|
cfg = config.services.netbird.server.signal;
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
options.services.netbird.server.signal = {
|
||||||
|
enable = mkEnableOption "Netbird's Signal Service";
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "netbird" { };
|
||||||
|
|
||||||
|
enableNginx = mkEnableOption "Nginx reverse-proxy for the netbird signal service.";
|
||||||
|
|
||||||
|
domain = mkOption {
|
||||||
|
type = str;
|
||||||
|
description = "The domain name for the signal service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = port;
|
||||||
|
default = 8012;
|
||||||
|
description = "Internal port of the signal server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
logLevel = mkOption {
|
||||||
|
type = enum [
|
||||||
|
"ERROR"
|
||||||
|
"WARN"
|
||||||
|
"INFO"
|
||||||
|
"DEBUG"
|
||||||
|
];
|
||||||
|
default = "INFO";
|
||||||
|
description = "Log level of the netbird signal service.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
systemd.services.netbird-signal = {
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = escapeSystemdExecArgs [
|
||||||
|
(getExe' cfg.package "netbird-signal")
|
||||||
|
"run"
|
||||||
|
# Port to listen on
|
||||||
|
"--port"
|
||||||
|
cfg.port
|
||||||
|
# Log to stdout
|
||||||
|
"--log-file"
|
||||||
|
"console"
|
||||||
|
# Log level
|
||||||
|
"--log-level"
|
||||||
|
cfg.logLevel
|
||||||
|
];
|
||||||
|
|
||||||
|
Restart = "always";
|
||||||
|
RuntimeDirectory = "netbird-mgmt";
|
||||||
|
StateDirectory = "netbird-mgmt";
|
||||||
|
WorkingDirectory = "/var/lib/netbird-mgmt";
|
||||||
|
|
||||||
|
# hardening
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateMounts = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectSystem = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
stopIfChanged = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = mkIf cfg.enableNginx {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
locations."/signalexchange.SignalExchange/".extraConfig = ''
|
||||||
|
# This is necessary so that grpc connections do not get closed early
|
||||||
|
# see https://stackoverflow.com/a/67805465
|
||||||
|
client_body_timeout 1d;
|
||||||
|
|
||||||
|
grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
grpc_pass grpc://localhost:${builtins.toString cfg.port};
|
||||||
|
grpc_read_timeout 1d;
|
||||||
|
grpc_send_timeout 1d;
|
||||||
|
grpc_socket_keepalive on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue