1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 23:00:39 +02:00

feat: add netbird (and coturn)

This commit is contained in:
oddlama 2024-05-15 22:17:21 +02:00
parent 4f3a379b3f
commit 9daa744334
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
32 changed files with 372 additions and 5 deletions

81
hosts/sentinel/coturn.nix Normal file
View file

@ -0,0 +1,81 @@
{
config,
lib,
pkgs,
...
}: let
inherit
(lib)
getExe
mkAfter
mkForce
;
hostDomain = config.repo.secrets.global.domains.me;
coturnDomain = "coturn.${hostDomain}";
in {
age.secrets.coturn-password-netbird = {
generator.script = "alnum";
group = "turnserver";
mode = "440";
};
networking.firewall.allowedUDPPorts = [
config.services.coturn.listening-port
config.services.coturn.alt-listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-tls-listening-port
];
networking.firewall.allowedTCPPorts = [
config.services.coturn.listening-port
config.services.coturn.alt-listening-port
config.services.coturn.tls-listening-port
config.services.coturn.alt-tls-listening-port
];
networking.firewall.allowedUDPPortRanges = [
{
from = config.services.coturn.min-port;
to = config.services.coturn.max-port;
}
];
networking.providedDomains.coturn = coturnDomain;
services.coturn = {
enable = true;
realm = coturnDomain;
lt-cred-mech = true;
no-cli = true;
extraConfig = ''
fingerprint
user=netbird:@password@
no-software-attribute
'';
cert = "@cert@";
pkey = "@pkey@";
};
systemd.services.coturn = let
certsDir = config.security.acme.certs.${hostDomain}.directory;
in {
preStart = mkAfter ''
${getExe pkgs.replace-secret} @password@ ${config.age.secrets.coturn-password-netbird.path} /run/coturn/turnserver.cfg
${getExe pkgs.replace-secret} @cert@ <(echo "$CREDENTIALS_DIRECTORY/cert.pem") /run/coturn/turnserver.cfg
${getExe pkgs.replace-secret} @pkey@ <(echo "$CREDENTIALS_DIRECTORY/pkey.pem") /run/coturn/turnserver.cfg
'';
serviceConfig = {
LoadCredential = [
"cert.pem:${certsDir}/fullchain.pem"
"pkey.pem:${certsDir}/key.pem"
];
Restart = mkForce "always";
RestartSec = "60"; # Retry every minute
};
};
security.acme.certs.${hostDomain}.postRun = ''
systemctl restart coturn.service
'';
}

View file

@ -11,6 +11,7 @@
../../modules/optional/zfs.nix ../../modules/optional/zfs.nix
./acme.nix ./acme.nix
./coturn.nix
./fs.nix ./fs.nix
./net.nix ./net.nix
./oauth2.nix ./oauth2.nix

View file

@ -107,6 +107,7 @@
// mkMicrovm "adguardhome" // mkMicrovm "adguardhome"
// mkMicrovm "forgejo" // mkMicrovm "forgejo"
// mkMicrovm "kanidm" // mkMicrovm "kanidm"
// mkMicrovm "netbird"
// mkMicrovm "radicale" // mkMicrovm "radicale"
// mkMicrovm "vaultwarden" // mkMicrovm "vaultwarden"
); );

View file

@ -106,12 +106,24 @@ in {
basicSecretFile = config.age.secrets.kanidm-oauth2-immich.path; basicSecretFile = config.age.secrets.kanidm-oauth2-immich.path;
preferShortUsername = true; preferShortUsername = true;
# XXX: PKCE is currently not supported by immich # XXX: PKCE is currently not supported by immich
# XXX: Also RS256 is used instead of ES256 so additionally needed:
# kanidm system oauth2 warning-enable-legacy-crypto immich
allowInsecureClientDisablePkce = true; allowInsecureClientDisablePkce = true;
# XXX: RS256 is used instead of ES256 so additionally we need legacy crypto
enableLegacyCrypto = true;
scopeMaps."immich.access" = ["openid" "email" "profile"]; scopeMaps."immich.access" = ["openid" "email" "profile"];
}; };
# Netbird
groups."netbird.access" = {};
systems.oauth2.netbird = {
public = true;
displayName = "Netbird";
originUrl = "https://${sentinelCfg.networking.providedDomains.netbird}/";
preferShortUsername = true;
enableLocalhostRedirects = true;
enableLegacyCrypto = true;
scopeMaps."netbird.access" = ["openid" "email" "profile"];
};
# Paperless # Paperless
groups."paperless.access" = {}; groups."paperless.access" = {};
systems.oauth2.paperless = { systems.oauth2.paperless = {

View file

@ -0,0 +1,134 @@
{
config,
lib,
nodes,
...
}: let
sentinelCfg = nodes.sentinel.config;
netbirdDomain = "netbird.${config.repo.secrets.global.domains.me}";
in {
wireguard.proxy-sentinel = {
client.via = "sentinel";
firewallRuleForNode.sentinel.allowedTCPPorts = [3000 3001];
};
# Mirror the original coturn password
age.secrets.coturn-password-netbird = {
inherit (sentinelCfg.age.secrets.coturn-password-netbird) rekeyFile;
};
age.secrets.coturn-secret = {
generator.script = "alnum";
};
age.secrets.netbird-data-store-encryption-key = {
generator.script = {pkgs, ...}: ''
${lib.getExe pkgs.openssl} rand -base64 32
'';
};
environment.persistence."/persist".directories = [
{
directory = "/var/lib/netbird-mgmt";
mode = "640";
user = "netbird";
group = "netbird";
}
];
services.netbird = {
server = {
enable = true;
domain = netbirdDomain;
dashboard.settings.AUTH_AUTHORITY = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/openid/netbird";
management = {
port = 3000;
dnsDomain = "internal.${config.repo.secrets.global.domains.me}";
singleAccountModeDomain = "home.lan";
oidcConfigEndpoint = "https://${sentinelCfg.networking.providedDomains.kanidm}/oauth2/openid/netbird/.well-known/openid-configuration";
turnDomain = sentinelCfg.networking.providedDomains.coturn;
turnPort = sentinelCfg.services.coturn.tls-listening-port;
settings = {
TURNConfig = {
Secret._secret = config.age.secrets.coturn-secret.path;
Turns = [
{
Proto = "udp";
URI = "turn:${config.services.netbird.server.management.turnDomain}:${builtins.toString config.services.netbird.server.management.turnPort}";
Username = "netbird";
Password._secret = config.age.secrets.coturn-password-netbird.path;
}
];
};
DataStoreEncryptionKey._secret = config.age.secrets.netbird-data-store-encryption-key.path;
};
};
};
};
nodes.sentinel = {
networking.providedDomains.netbird = netbirdDomain;
services.nginx = {
upstreams.netbird = {
servers."${config.wireguard.proxy-sentinel.ipv4}:80" = {};
extraConfig = ''
zone netbird 64k;
keepalive 5;
'';
};
upstreams.netbird-mgmt = {
servers."${config.wireguard.proxy-sentinel.ipv4}:3000" = {};
extraConfig = ''
zone netbird 64k;
keepalive 5;
'';
};
upstreams.netbird-signal = {
servers."${config.wireguard.proxy-sentinel.ipv4}:3001" = {};
extraConfig = ''
zone netbird 64k;
keepalive 5;
'';
};
virtualHosts.${netbirdDomain} = {
forceSSL = true;
useACMEWildcardHost = true;
locations = {
"/" = {
root = config.services.netbird.server.dashboard.finalDrv;
tryFiles = "$uri $uri.html $uri/ =404";
X-Frame-Options = "SAMEORIGIN";
};
"/signalexchange.SignalExchange/".extraConfig = ''
grpc_pass grpc://netbird-signal;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
'';
"/api".proxyPass = "http://netbird-mgmt";
"/management.ManagementService/".extraConfig = ''
grpc_pass grpc://netbird-mgmt;
grpc_read_timeout 1d;
grpc_send_timeout 1d;
grpc_socket_keepalive on;
'';
};
extraConfig = ''
client_max_body_size 500M ;
client_header_timeout 1d;
client_body_timeout 1d;
'';
};
};
};
systemd.services.netbird-signal.serviceConfig.RestartSec = "60"; # Retry every minute
systemd.services.netbird-management.serviceConfig.RestartSec = "60"; # Retry every minute
}

View file

@ -110,9 +110,29 @@ in {
late = true; # Only accept after any rejects have been processed late = true; # Only accept after any rejects have been processed
verdict = "accept"; verdict = "accept";
}; };
#masquerade-vpn = {
# from = ["wg-home"];
# to = ["lan"];
# masquerade = true;
#};
#outbound-vpn = {
# from = ["wg-home"];
# to = ["lan"];
# late = true; # Only accept after any rejects have been processed
# verdict = "accept";
#};
}; };
}; };
# Allow accessing influx # Allow accessing influx
wireguard.proxy-sentinel.client.via = "sentinel"; wireguard.proxy-sentinel.client.via = "sentinel";
#wireguard.home.server = {
# host = todo # config.networking.fqdn;
# port = 51192;
# reservedAddresses = ["10.10.0.1/24" "fd00:10::/120"];
# openFirewall = true;
#};
} }

View file

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJt2DE0HJjmePYjuZVRcsb0/SfoHSmm06T4ayzIgxUOp

View file

@ -48,7 +48,9 @@
inherit inherit
(config.networking.firewall) (config.networking.firewall)
allowedTCPPorts allowedTCPPorts
allowedTCPPortRanges
allowedUDPPorts allowedUDPPorts
allowedUDPPortRanges
; ;
}; };
}; };

View file

@ -450,6 +450,12 @@ in {
options = { options = {
present = mkPresentOption "oauth2 resource server"; present = mkPresentOption "oauth2 resource server";
public = mkOption {
description = "Whether this is a public client (enforces PKCE, doesn't use a basic secret)";
type = types.bool;
default = false;
};
displayName = mkOption { displayName = mkOption {
description = "Display name"; description = "Display name";
type = types.str; type = types.str;
@ -479,10 +485,23 @@ in {
default = null; default = null;
}; };
enableLocalhostRedirects = mkOption {
description = "Allow localhost redirects. Only for public clients.";
type = types.bool;
default = false;
};
enableLegacyCrypto = mkOption {
description = "Enable legacy crypto on this client. Allows JWT signing algorthms like RS256.";
type = types.bool;
default = false;
};
allowInsecureClientDisablePkce = mkOption { allowInsecureClientDisablePkce = mkOption {
description = '' description = ''
Disable PKCE on this oauth2 resource server to work around insecure clients Disable PKCE on this oauth2 resource server to work around insecure clients
that may not support it. You should request the client to enable PKCE! that may not support it. You should request the client to enable PKCE!
Only for non-public clients.
''; '';
type = types.bool; type = types.bool;
default = false; default = false;
@ -681,6 +700,21 @@ in {
assertion = (cfg.provision.enable && cfg.enableServer) -> any (xs: xs != []) (attrValues claimCfg.valuesByGroup); assertion = (cfg.provision.enable && cfg.enableServer) -> any (xs: xs != []) (attrValues claimCfg.valuesByGroup);
message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group"; message = "services.kanidm.provision.systems.oauth2.${oauth2}.claimMaps.${claim} does not specify any values for any group";
} }
# Public clients cannot define a basic secret
{
assertion = (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> oauth2Cfg.basicSecretFile == null;
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot specify a basic secret";
}
# Public clients cannot disable PKCE
{
assertion = (cfg.provision.enable && cfg.enableServer && oauth2Cfg.public) -> !oauth2Cfg.allowInsecureClientDisablePkce;
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a public client and thus cannot disable PKCE";
}
# Non-public clients cannot enable localhost redirects
{
assertion = (cfg.provision.enable && cfg.enableServer && !oauth2Cfg.public) -> !oauth2Cfg.enableLocalhostRedirects;
message = "services.kanidm.provision.systems.oauth2.${oauth2} is a non-public client and thus cannot enable localhost redirects";
}
])) ]))
)); ));

View file

@ -5,16 +5,16 @@
}: }:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
pname = "kanidm-provision"; pname = "kanidm-provision";
version = "1.0.1"; version = "1.1.0";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "oddlama"; owner = "oddlama";
repo = "kanidm-provision"; repo = "kanidm-provision";
rev = "v${version}"; rev = "v${version}";
hash = "sha256-tSr2I7bGEwJoC5C7BOmru2oh9ta04WVTz449KePYSK4="; hash = "sha256-pFOFFKh3la/sZGXj+pAM8x4SMeffvvbOvTjPeHS1XPU=";
}; };
cargoHash = "sha256-LRPpAIH+pXThS+HJ63kVbxMMoBgsky1nf99RWarX7/0="; cargoHash = "sha256-oiKlKIL23xH67tCDbny9Gj97JQQm4mYt0IHXB5hzJ/A=";
meta = with lib; { meta = with lib; {
description = "A small utility to help with kanidm provisioning"; description = "A small utility to help with kanidm provisioning";

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> X25519 BX1TzWJvYYuXIc5jazmoefCDOrWYCc6vtQHqiidFK0k
KguZPOuk4LKDPogJ40mXA8okdLgG9PAx5fqYW2gkqwQ
-> piv-p256 xqSe8Q A58MztEJBOwOK0pPa7WngTGynn0I+VUFrCtibSKSwOep
sVyAneNoMlRnIPR502xrnFeQyI36GpzxqTRhjOpfU7w
-> YS-grease
WMxsZrN//DXWbO+03CQwRqPKXdeV844codU
--- BrgOOiY9Crg771rp77VQ0i3tM770D6CjGknWYRgoIfk
zîXNò,¹Ž1 ª?v(£oü¬›Õ®
ØÏÛ|ÄvF9àÞ™Þ»åm"dÑ�úâù?ƒ9?
ÒáòJn7Q-¦g‚�Q‘ïÄ^\f«Q

View file

@ -0,0 +1,9 @@
age-encryption.org/v1
-> X25519 pc+s+uniKbP/sMiud4xtJ9x6UMBaIdBO0iHBeznb6VI
/baQ9J0Qcpr9sZD6LWguy7lcAcgFHq0fPSsCBkkDoKY
-> piv-p256 xqSe8Q Ay2WPFU0XrukDvFIe0+ZiGm+5m5oJTzktnZ+7L3l4/G5
5/CGoggDIARr02H0sUX3/HJ6PEoQMLJuhACF2MEdRts
-> T/-grease =Cr4 B ,R1u(?
is0bg8583EfFjiM8b+737Wm6+J4
--- +R5bojMEBENAYEEy++5iMdhEyKCr8rCPOIOiHRa4Wls
´•¹„7ß9 Žp¬Á¶.’Ãd¹"g½²oÉ:9”ì4äÂá÷~²©ôú“(Ù¸Q*¤ˆj“ž«ãúCD8 > eœÁÁÁ­ìvS×ùLE#+

View file

@ -0,0 +1,11 @@
age-encryption.org/v1
-> X25519 qeT9jfLCM7cn3uu9z44fa54bAQgjdK+l/uvIG5al924
Py1wATeO86kvUJzY6MFzl2vTXSvyM93ZrTBjxrMrxBg
-> piv-p256 xqSe8Q A8ojuCdRG5nViw0SS133NIk7/h0hWbjDYeJiO5LtxkS8
jQUqyqhIbeUGqyrLOBJBYwCg9ucyumzjT4c/BsrVjLU
-> {Z-grease jeRAOL 7:2"CZ u={2< 2|0e$Sqx
Z9ZtNsDOP2sj5nmBgAfDGSEJVQ6jO/ikZuyZXOklhsWa3o1hvgXWL43S3ThRN3+t
dsQyP7yVZ1J54/mMURTJc9pPyTqSsvoQ9/MP9VkfIhp6ZBoeQGa34UCppzX1xfo
--- JFa18D2SE3VwuLEMvNpiiR9YY5NZBUVTxzNZcTSfFHI
xó{·Â!Î�•ü�D™(U¡Á¹u‹næM‡¥[P7;Óçò.Kç#—àûúãȾ`Ú�¹x $<˜”sü™*GH,ì-¹¿
ÎyždåýAîTs

Binary file not shown.

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 yV7lcA YulN0/x6Gm6SCXABynVcm2EmCKtvS8Qd9mfvJdWBHhY
clZ+rznQkfQGnPpM2H2FaPCjg+Tou6wkvXo0SEO0QUk
-> lSt!-grease n/_ lnzq>4 WHRbZ78. C
C3jB9dLLqtVQOaL7tusyOvrAAxsfjHbYvUtz1XgSQYfLySmfyhVxNz2TYG2biWkE
xnhyT65BjhSlKRCGN5ABKmUtjYQ
--- i9Wkqqpnbnye7ndRxxXbZaVhC5x8qTldieLbzx9h5vE
âÂ"©ñA<—œºˆ(Œ„�È!z ï)_"Ncn5«èi§J/“öŒôÄãÀ’ˆ�¤Y�Ï†Š‘Ⱦ¼ìwøèK;y$I€võä€ôR<

View file

@ -0,0 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 yV7lcA rInHh7EJiht3x4bSdusTvPZeFc6GAUkUZQGaISa0HDI
p3HcoOg+PcPMdLVpHvn5R28GJ6n/d00EVF1KKMZOsqI
-> :`epM2-grease 0%~=37:k @~ +-.u1/~=
d8uTZyL9nN5Q+tS1YQzoyDxS14GT7+EtISr2LSS+/41aWiaUNsvn1/0PKR4lNBce
vGmoEWERf9yKd6a1h9dlbPaf9jOgEaDjgNyYboYyf0EKQSM
--- sERxxQVPG2gykXJPnD/BHzDZ2m6XqkywufcNLcinwVg
”gUçcœ=
¥ÏÇÚ©Ø[×íÂÞqqç»LäXV‹J¤oÍû…ß=»›0øšÏÐ�!C3ƒ¥gôtY„BjùŠ|ô¾>g×ܸ!ï/¿

View file

@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 1tdZKQ UEC1qtqm46KIsl/aWeFtVQxPzY3Rb/iVnNYFaNpyShc
le4O/7aTZ1wvwqaZ+TciQQcnFavIqele5FCyNjoPUg0
-> @-grease
15HI1iKmv/+MRpeT
--- GbgkpPSiqzyooOKtrsj0HGl0gfMMT7d46CrKpwpkVTI
#Îô/‹®¤yü„ú°MNj° 5×É¥B‡èÁÅÖ‡„]HóÏ¿ú£(ýÎ ë!=s·7¦Úq¦ñxž/¸�ï<³O²Ì".ƒ¸¦h^ÎŽ\ º¬É

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 2PpNmg HR+7l6qXcAXmDXTDI6J2sLwYdEo7/7eOvtbsJELHfQQ
VpCZPyJT4syKsoby/di70g63EUZsGg36mla1jKk5fyw
-> 1-grease T9.Y
2qDDSDI0Yoh83qTVXki1WYPsqdjuR9e2qrNdl6H3mWYAlF5ggjLu+3MbQ2P6ouIP
cFvso0vS56O/SOpPpj5P9El6auY
--- sWPsDspbccjrl+UmGBwI9e959ZoMSkb6kvGcbB+NE4Q
sH1Á�ÈbL‹ì£/Ïî¶ÌØCœ¶o3j™aÈ�²Â701Úñj沃` ^çt›/s|¸ÌÛ~{WHŽ%.0}VȲAhEz~¢�è

View file

@ -0,0 +1,9 @@
age-encryption.org/v1
-> ssh-ed25519 2PpNmg 7/LkMYjajibcu5sLO2inuFhGEVFWmNm7qUnr632xjWk
nlPVfytJZwJPb+yUK65F7FGlx7qr5KZf0amCyNrr59Y
-> yFt7;"Zk-grease o>Bq cB",D: a
FyaoFSVUSuohR/Jx7g
--- eOCUycFPEH0Du123hXxaNyKJYaxSN++TaUB+yTaMtVM
ײꅀ6†
yCs©›×télÄ©é {í0“?Õß,|i Gb
"‘gšì�²ù_1;ùøy'þ’úûԗʳLê*û,�çA ðM"Ò

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 2PpNmg 2nliLnpdZpUd3hQj9PTbxZAbnVDhhr+BLrQ9YxCeh1k
/8/mZbj6pE0imMg3Rm7sCJ599u45jbRJc+NtGDAiykU
-> c7i-grease s
1aVkJHJXdVXk0R91F3HaCV4p9/yPqrMrAFYpUjvrvp7jVkyr6fwt3xJjTigrHvS4
bTa42nF3bxkU2u5sfD9Kr55l
--- XzYsO6Vi2ch5tUqKn1JZQ8qXg/e9A6ujd5j189Rufc8
«¨p{?2Jeƒ‘<Zµ"ýÏÉ}ë«-Æ.v¨É“¨ß‹Ï€EÎy¶†ªCwÛ²ŸL&ç�۶ͬ߷Ûö\žWP{(ˆÜ+ÊÝz]

Binary file not shown.

View file

@ -0,0 +1 @@
+rcp1Aobh/8XtB9anwQMymySe7JQ5bQMnIXg3AbPN08=