Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions nixos/doc/manual/redirects.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"module-services-netbird-relay": [
"index.html#module-services-netbird-relay"
],
"module-services-netbird-relay-quickstart": [
"index.html#module-services-netbird-relay-quickstart"
],
"module-services-netbird-relay-tls": [
"index.html#module-services-netbird-relay-tls"
],
"module-services-portmaster": [
"index.html#module-services-portmaster"
],
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2611.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@

- [qbit-manage](https://github.com/StuffAnThings/qbit_manage), a tool to help manage tedious tasks in qBittorrent and automate them. Available as [services.qbit-manage](#opt-services.qbit-manage.enable).

- [Netbird Relay](https://netbird.io/), a module to relay traffic when a point-to-point connection is not possible.

## Backward Incompatibilities {#sec-release-26.11-incompatibilities}

<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1349,6 +1349,7 @@
./services/networking/nebula-lighthouse-service.nix
./services/networking/nebula.nix
./services/networking/netbird.nix
./services/networking/netbird/netbird-relay.nix
./services/networking/netbird/server.nix
./services/networking/netclient.nix
./services/networking/netfoil.nix
Expand Down
2 changes: 1 addition & 1 deletion nixos/modules/services/networking/netbird.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Netbird {#module-services-netbird}
# Netbird Client {#module-services-netbird}

## Quickstart {#module-services-netbird-quickstart}

Expand Down
92 changes: 92 additions & 0 deletions nixos/modules/services/networking/netbird/netbird-relay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Netbird Relay {#module-services-netbird-relay}

The Relay service forwards encrypted WireGuard traffic between peers that cannot establish a direct P2P connection

For more information, check [external relays](https://docs.netbird.io/selfhosted/maintenance/scaling/set-up-external-relays) documentation.

## Quickstart {#module-services-netbird-relay-quickstart}

You can run the relay directly, or behind a reverse proxy, like `traefik`.

To run it directly, you'll have to run it on HTTPS port (443) and it will need the TLS certificates. Which you can configure using the `acme` module.

In the following example, we can see the `traefik` route and the relay configuration.
The operator should configure the TLS for the domain used by the relay, whether it's via `traefik` itself or using the `acme` module.

```nix
let
relayDomain = "relay.example.com";
in
{
services.netbird.relay = {
enable = true;
settings = {
listen-address = ":33080";
exposed-address = "rels://${relayDomain}:443";
log-level = "info";
enable-stun = true;
stun-ports = [ 3479 ];
};
openFirewall = true;
authSecretFile = "/run/auth_secret";
};

services.traefik = {
enable = true;
dynamicConfigOptions = {
http = {
routers = {
vpn-relay = {
rule = "Host(`${relayDomain}`)";
entryPoints = [ "websecure" ];
service = "vpn-relay-svc";
tls = { };
};
};
services = {
vpn-relay-svc = {
loadBalancer.servers = [ { url = "http://[::1]:33080"; } ];
};
};
};
};
};
}
```

Finally, you must let `netbird.server` know about the new relay:

```nix
{ }:
{
services.netbird.server = {
# ...other config...
management = {
# ...other config...
settings = {
# ...other config...
Relay = {
Addresses = [ "rels://${relayDomain}:443" ];
CredentialsTTL = "24h0m0s";
Secret = {
_secret = "/run/auth_secret";
};
};
};
};
};
}
```
Done!

## TLS with Let's Encrypt {#module-services-netbird-relay-tls}

You can expose the netbird relay directly to the internet with TLS. Then you won't need a reverse proxy.

To use TLS, set `settings."letsencrypt-domains"`.
The relay gets and renews the certificates only when `settings."letsencrypt-domains"` is set.

Certificates are stored on disk, and the services runs as a systemd dynamic user.
The only writable location is therefore a `StateDirectory`, which can only be under `/var/lib`.

Open the ports `tcp/443` and `tcp/80` manually for the HTTP challenge.
248 changes: 248 additions & 0 deletions nixos/modules/services/networking/netbird/netbird-relay.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
{
config,
lib,
pkgs,
...
}:

let
inherit (lib)
getExe'
types
;

cfg = config.services.netbird.relay;
cmd = getExe' cfg.package "netbird-relay";
derivedLEDataDir = lib.optionalAttrs (
cfg.settings ? "letsencrypt-domain" && !cfg.settings ? "letsencrypt-data-dir"
) { "letsencrypt-data-dir" = "/var/lib/netbird-relay"; };

args = lib.cli.toCommandLineShellGNU { } (cfg.settings // derivedLEDataDir);

listenPortMatch = builtins.match ".*:([0-9]+)$" cfg.settings.listen-address;
listenPort = if listenPortMatch == null then null else lib.toInt (builtins.head listenPortMatch);

# Checks
authSecretSet = cfg.authSecretFile != null;
needsPrivPort = listenPort != null && listenPort < 1024;
letsencryptEnabled = cfg.settings ? "letsencrypt-domain";
in
{
options.services.netbird.relay = {
enable = lib.mkEnableOption "Netbird's Relay Service";
package = lib.mkPackageOption pkgs "netbird-relay" { };

# as per RFC 0042:
settings = lib.mkOption {
type = types.submodule {
freeformType =
with lib.types;
nullOr (oneOf [
bool
str
# For --letsencrypt-domains strings
(listOf (oneOf [ str ]))
]);
options.listen-address = lib.mkOption {
type = types.str;
description = ''
The host address and port separated by colon where the relay will listen
'';
default = ":33080";
};
options.exposed-address = lib.mkOption {
type = types.str;
description = ''
Exposed address for peers. Address told to the peers to connect to
'';
example = "rels://relay.example.com:443";
};
options.enable-stun = lib.mkOption {
type = types.bool;
default = false;
description = "Enable embedded STUN server";
};
options.stun-ports = lib.mkOption {
type = types.listOf types.port;
default = [ 3478 ];
description = "STUN server UDP ports";
};
options.log-level = lib.mkOption {
type = types.enum [
Comment thread
woile marked this conversation as resolved.
"panic"
"fatal"
"error"
"warn"
"info"
"debug"
"trace"
];
default = "info";
description = "Log level of the netbird relay service";
};
options.metrics-port = lib.mkOption {
type = types.port;
default = 9092;
description = "Metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics";
};
options.health-listen-address = lib.mkOption {
type = types.str;
description = ''
Listen address of healthcheck server
'';
default = ":9000";
};
};
default = null;
description = ''
Settings to configure the netbird relay.
Converted automatically into cli args, check all the options with `netbird-relay --help`
'';
};

environmentFile = lib.mkOption {
type = types.nullOr types.externalPath;
default = null;
description = ''
Path (as string) to an environmentFile with the netbird relay environment variables.
You can find all the variables under [Relay runtime env variables](https://docs.netbird.io/selfhosted/environment-variables#runtime-variables-2).

WARNING: You must manually open any port configured via environmentFile.
If you use an Internet domain privileged port (e.g 443), add CAP_NET_BIND_SERVICE to systemd's CapabilityBoundingSet and AmbientCapabilities
'';
example = "/run/netbird-relay.env";
};

openFirewall = lib.mkOption {
type = types.bool;
default = false;
description = ''
Open STUN ports in the firewall for the netbird relay.

WARNING: You must manually open listen-address port/tcp and port 80/tcp,
if you are exposing the relay directly to the internet.
Review [set-up-external-relays](https://docs.netbird.io/selfhosted/maintenance/scaling/set-up-external-relays)
'';
};

authSecretFile = lib.mkOption {
Comment thread
woile marked this conversation as resolved.
type = types.nullOr types.externalPath;
default = null;
description = ''
Path (as string) to a file containing the auth-secret used by netbird to connect to the relay server.
It will populate `NB_AUTH_SECRET`
'';
example = "/run/auth_secret";
};

};

config = lib.mkIf cfg.enable {
assertions = [
{
assertion = authSecretSet || cfg.environmentFile != null;
message = "services.netbird.relay requires NB_AUTH_SECRET, either set authSecretFile or configure it in environmentFile";
}
{
assertion = !(cfg.settings.auth-secret or null != null);
message = "settings.auth-secret puts the secret into the nix store and the cli args. Use relay.authSecretFile instead";
}
{
assertion =
!letsencryptEnabled || lib.hasPrefix "/var/lib/" (cfg.settings."letsencrypt-data-dir" or "");
message = "settings.letsencrypt-data-dir must be under /var/lib, the only writable location when using DynamidcUser in systemd";
}
{
assertion = !(cfg.settings.enable-stun && cfg.settings.stun-ports == [ ]);
message = "Missing stun ports while enable-stun = true";
}
];

systemd.services.netbird-relay = {
description = "Relay for Netbird, a wireguard VPN";
documentation = [ "https://netbird.io/docs/" ];
after = [
"network.target"
];
wantedBy = [ "multi-user.target" ];

script = ''
${lib.optionalString authSecretSet ''
export NB_AUTH_SECRET="$(< "$CREDENTIALS_DIRECTORY/auth_secret")"
''}
exec ${cmd} ${args}
'';
unitConfig = {
# Spread the restart
StartLimitIntervalSec = 60;
StartLimitBurst = 10;
};
serviceConfig = {
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];

# Let systemd ingest the secret safely
LoadCredential = lib.mkIf authSecretSet "auth_secret:${cfg.authSecretFile}";

Restart = "always";
RestartSec = "5s";

StateDirectory = lib.mkIf letsencryptEnabled (
lib.removeSuffix "/" (lib.removePrefix "/var/lib/" cfg.settings."letsencrypt-data-dir")
);

# Hardening::Start
# Dynamic user automatically sets:
# NoNewPrivileges = true;
# RemoveIPC = true;
# RestrictSUIDSGID = true;
# ProtectSystem = "strict";
# ProtectHome = "read-only";
# see https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=
DynamicUser = true;

LockPersonality = true;
MemoryDenyWriteExecute = true;
# Relay is mostly stateless, no need to give access to any of this:
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProcSubset = "pid";
ProtectProc = "invisible";
RestrictNamespaces = true;
RestrictRealtime = true;
SystemCallFilter = [ "@system-service" ];
SystemCallArchitectures = "native";

# If a user wants to bind netbird to 443, we must allow binding to
# a socket to Internet domain privileged ports
CapabilityBoundingSet = lib.optionals needsPrivPort [ "CAP_NET_BIND_SERVICE" ];
AmbientCapabilities = lib.optionals needsPrivPort [ "CAP_NET_BIND_SERVICE" ];

# Use only IP, no socket needed
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
];
UMask = "0077";
# Hardening::End
};
};
networking.firewall.allowedUDPPorts = lib.mkIf (
cfg.openFirewall && cfg.settings.enable-stun
) cfg.settings.stun-ports;
};

meta = {
doc = ./netbird-relay.md;
maintainers = [
lib.maintainers.woile
];
};
}
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,7 @@ in
nebula.reload = runTest ./nebula/reload.nix;
neo4j = runTest ./neo4j.nix;
netbird = runTest ./netbird.nix;
netbird-relay = runTest ./netbird-relay.nix;
netbox = runTest ./web-apps/netbox/default.nix;
netdata = runTest ./netdata.nix;
netfoil = runTest ./netfoil.nix;
Expand Down
Loading
Loading