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

wip: more firezone clients

This commit is contained in:
oddlama 2025-02-07 03:44:29 +01:00
parent bbb76ae5ec
commit e918a6d581
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
14 changed files with 1174 additions and 141 deletions

View file

@ -8,6 +8,8 @@
./backups.nix
./deterministic-ids.nix
./distributed-config.nix
./firezone-relay.nix
./firezone-gateways.nix
./firezone-server.nix
./globals.nix
./meta.nix

View file

@ -0,0 +1,170 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
boolToString
concatMapAttrs
flip
getExe
mkEnableOption
mkOption
mkPackageOption
types
;
in
{
options = {
services.firezone.gateways = mkOption {
description = ''
A set of gateway clients to deploy on this machine. Each gateway can
connect to exactly one firezone server.
'';
default = { };
type = types.attrsOf (
types.submodule (gatewaysSubmod: {
options = {
package = mkPackageOption pkgs "firezone-gateway" { };
name = mkOption {
type = types.str;
default = gatewaysSubmod.config._module.args.name;
description = "The name of this gateway as shown in firezone";
};
user = mkOption {
type = types.strMatching "^[a-zA-Z0-9_-]{1,32}$";
default = "firezone-gw-${gatewaysSubmod.config._module.args.name}";
description = "The DynamicUser name under which the gateway will run. Cannot exceed 32 characters.";
};
interface = mkOption {
type = types.strMatching "^[a-zA-Z0-9_-]{1,15}$";
default = "tun-${gatewaysSubmod.config._module.args.name}";
description = "The name of the TUN interface which will be created by this gateway";
};
apiUrl = mkOption {
type = types.str;
example = "wss://firezone.example.com/api";
description = ''
The URL of your firezone server's API. This should be the same
as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
but with `wss://` instead of `https://`.
'';
};
tokenFile = mkOption {
type = types.path;
example = "/run/secrets/firezone-gateway-token";
description = ''
A file containing the firezone gateway token. Do not use a nix-store path here
as it will make the token publicly readable!
This file will be passed via systemd credentials, it should only be accessible
by the root user.
'';
};
logLevel = mkOption {
type = types.str;
default = "info";
description = ''
The log level for the firezone application. See
[RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
for the format.
'';
};
enableTelemetry = mkEnableOption "telemetry";
};
})
);
};
};
config = {
systemd.services = flip concatMapAttrs config.services.firezone.gateways (
gatewayName: gatewayCfg: {
"firezone-gateway-${gatewayName}" = {
description = "Gateway service for the Firezone zero-trust access platform";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.util-linux ];
script = ''
# If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
if [[ -z "''${FIREZONE_ID:-}" ]]; then
if [[ ! -e gateway_id ]]; then
uuidgen -r > gateway_id
fi
export FIREZONE_ID=$(< gateway_id)
fi
export FIREZONE_TOKEN=$(< "$CREDENTIALS_DIRECTORY/firezone-token")
exec ${getExe gatewayCfg.package}
'';
environment = {
FIREZONE_API_URL = gatewayCfg.apiUrl;
FIREZONE_NAME = gatewayCfg.name;
FIREZONE_NO_TELEMETRY = boolToString gatewayCfg.enableTelemetry;
FIREZONE_TUN_INTERFACE = gatewayCfg.interface;
RUST_LOG = gatewayCfg.logLevel;
};
serviceConfig = {
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
Type = "exec";
DynamicUser = true;
User = gatewayCfg.user;
LoadCredential = [ "firezone-token:${gatewayCfg.tokenFile}" ];
DeviceAllow = "/dev/net/tun";
AmbientCapabilities = [ "CAP_NET_ADMIN" ];
CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
StateDirectory = "firezone-gateways/${gatewayName}";
WorkingDirectory = "/var/lib/firezone-gateways/${gatewayName}";
};
};
}
);
};
meta.maintainers = with lib.maintainers; [
oddlama
patrickdag
];
}

177
modules/firezone-relay.nix Normal file
View file

@ -0,0 +1,177 @@
{
lib,
pkgs,
config,
...
}:
let
inherit (lib)
boolToString
getExe
mkEnableOption
mkIf
mkOption
mkPackageOption
types
;
cfg = config.services.firezone.relay;
in
{
options = {
services.firezone.relay = {
enable = mkEnableOption "the firezone relay server";
package = mkPackageOption pkgs "firezone-relay" { };
publicIpv4 = mkOption {
type = types.str;
description = "The public ipv4 address of this relay";
};
publicIpv6 = mkOption {
type = types.str;
description = "The public ipv6 address of this relay";
};
openFirewall = mkOption {
type = types.bool;
default = true;
description = "Opens up the main STUN port and the TURN allocation range.";
};
port = mkOption {
type = types.port;
default = 3478;
description = "The port to listen on for STUN messages";
};
lowestPort = mkOption {
type = types.port;
default = 49152;
description = "The lowest port to use in TURN allocation";
};
highestPort = mkOption {
type = types.port;
default = 65535;
description = "The highest port to use in TURN allocation";
};
apiUrl = mkOption {
type = types.str;
example = "wss://firezone.example.com/api";
description = ''
The URL of your firezone server's API. This should be the same
as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
but with `wss://` instead of `https://`.
'';
};
tokenFile = mkOption {
type = types.path;
example = "/run/secrets/firezone-relay-token";
description = ''
A file containing the firezone relay token. Do not use a nix-store path here
as it will make the token publicly readable!
This file will be passed via systemd credentials, it should only be accessible
by the root user.
'';
};
logLevel = mkOption {
type = types.str;
default = "firezone_relay=info,firezone_tunnel=info,connlib_shared=info,tunnel_state=info,phoenix_channel=info,snownet=info,str0m=info,warn";
description = ''
The log level for the firezone application. See
[RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
for the format.
'';
};
enableTelemetry = mkEnableOption "telemetry";
};
};
config = mkIf cfg.enable {
systemd.services."firezone-relay" = {
description = "relay service for the Firezone zero-trust access platform";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.util-linux ];
script = ''
# If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
if [[ -z "''${FIREZONE_ID:-}" ]]; then
if [[ ! -e relay_id ]]; then
uuidgen -r > relay_id
fi
export FIREZONE_ID=$(< relay_id)
fi
export FIREZONE_TOKEN=$(< "$CREDENTIALS_DIRECTORY/firezone-token")
exec ${getExe cfg.package}
'';
environment = {
FIREZONE_API_URL = cfg.apiUrl;
FIREZONE_NAME = cfg.name;
FIREZONE_TELEMETRY = boolToString cfg.enableTelemetry;
PUBLIC_IPV4_ADDRESS = cfg.publicIpv4;
PUBLIC_IPV6_ADDRESS = cfg.publicIpv6;
LISTEN_PORT = toString cfg.port;
LOWEST_PORT = toString cfg.lowestPort;
HIGHEST_PORT = toString cfg.highestPort;
RUST_LOG = cfg.logLevel;
LOG_FORMAT = "human";
};
serviceConfig = {
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
Type = "exec";
DynamicUser = true;
User = "firezone-relay";
LoadCredential = [ "firezone-token:${cfg.tokenFile}" ];
StateDirectory = "firezone-relay";
WorkingDirectory = "/var/lib/firezone-relay";
};
};
};
meta.maintainers = with lib.maintainers; [
oddlama
patrickdag
];
}

View file

@ -85,14 +85,15 @@ let
);
in
concatLines (
forEach relevantSecrets (secret: ''
export ${secret}=$(< ${
forEach relevantSecrets (
secret:
''export ${secret}=$(< ${
if cfg.settingsSecret.${secret} == null then
"secrets/${secret}"
else
"\"$CREDENTIALS_DIRECTORY/${secret}\""
})
'')
})''
)
);
provisionStateJson =
@ -121,20 +122,20 @@ let
commonServiceConfig = {
AmbientCapablities = [ ];
CapabilityBoundingSet = [ ];
LockPersonality = "true";
MemoryDenyWriteExecute = "true";
NoNewPrivileges = "true";
PrivateMounts = "true";
PrivateTmp = "true";
PrivateUsers = "false";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProcSubset = "pid";
ProtectClock = "true";
ProtectControlGroups = "true";
ProtectHome = "true";
ProtectHostname = "true";
ProtectKernelLogs = "true";
ProtectKernelModules = "true";
ProtectKernelTunables = "true";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = [
@ -143,9 +144,9 @@ let
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = "true";
RestrictRealtime = "true";
RestrictSUIDSGID = "true";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
UMask = "077";
@ -157,10 +158,12 @@ let
StateDirectory = "firezone";
WorkingDirectory = "/var/lib/firezone";
Type = "exec";
LoadCredential = mapAttrsToList (secretName: secretFile: "${secretName}:${secretFile}") (
filterAttrs (_: v: v != null) cfg.settingsSecret
);
Type = "exec";
Restart = "on-failure";
RestartSec = 10;
};
componentOptions = component: {
@ -199,20 +202,7 @@ in
options.services.firezone.server = {
enable = mkEnableOption "all Firezone components";
enableLocalDB = mkEnableOption "a local postgresql database for Firezone";
nginx = {
enable = mkEnableOption "nginx virtualhost definition";
apiDomain = mkOption {
type = types.str;
example = "api.firezone.example.com";
description = "The virtual host domain under which the api should be exposed";
};
webDomain = mkOption {
type = types.str;
example = "firezone.example.com";
description = "The virtual host domain under which the web interface should be exposed";
};
};
nginx.enable = mkEnableOption "nginx virtualhost definition";
openClusterFirewall = mkOption {
type = types.bool;
@ -482,7 +472,7 @@ in
api = componentOptions "api" // {
externalUrl = mkOption {
type = types.strMatching "^https://.+/$";
example = "https://api.firezone.example.com/";
example = "https://firezone.example.com/api/";
description = ''
The external URL under which you will serve the api. You need to
setup a reverse proxy for TLS termination, either with
@ -690,23 +680,45 @@ in
})
# Create a local nginx reverse proxy
(mkIf cfg.nginx.enable {
services.nginx = {
enable = true;
virtualHosts.${cfg.nginx.webDomain} = {
forceSSL = mkDefault true;
locations."/" = {
proxyPass = "http://${cfg.web.address}:${toString cfg.web.port}";
proxyWebsockets = true;
};
};
virtualHosts.${cfg.nginx.apiDomain} = {
forceSSL = mkDefault true;
locations."/" = {
proxyPass = "http://${cfg.api.address}:${toString cfg.api.port}";
proxyWebsockets = true;
};
};
};
services.nginx = mkMerge [
{
enable = true;
}
(
let
urlComponents = builtins.elemAt (builtins.split "https://([^/]*)(/?.*)" cfg.web.externalUrl) 1;
domain = builtins.elemAt urlComponents 0;
location = builtins.elemAt urlComponents 1;
in
{
virtualHosts.${domain} = {
forceSSL = mkDefault true;
locations.${location} = {
# The trailing slash is important to strip the location prefix from the request
proxyPass = "http://${cfg.web.address}:${toString cfg.web.port}/";
proxyWebsockets = true;
};
};
}
)
(
let
urlComponents = builtins.elemAt (builtins.split "https://([^/]*)(/?.*)" cfg.api.externalUrl) 1;
domain = builtins.elemAt urlComponents 0;
location = builtins.elemAt urlComponents 1;
in
{
virtualHosts.${domain} = {
forceSSL = mkDefault true;
locations.${location} = {
# The trailing slash is important to strip the location prefix from the request
proxyPass = "http://${cfg.api.address}:${toString cfg.api.port}/";
proxyWebsockets = true;
};
};
}
)
];
})
# Specify sensible defaults
{
@ -832,7 +844,7 @@ in
};
systemd.services.firezone-initialize = {
description = "Firezone initialization";
description = "Backend initialization service for the Firezone zero-trust access platform";
after = mkIf cfg.enableLocalDB [ "postgresql.service" ];
requires = mkIf cfg.enableLocalDB [ "postgresql.service" ];
@ -859,7 +871,7 @@ in
};
systemd.services.firezone-server-domain = mkIf cfg.domain.enable {
description = "Firezone domain server";
description = "Backend domain server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];
@ -870,12 +882,13 @@ in
exec ${getExe cfg.domain.package} start;
'';
path = [ pkgs.curl ];
postStart = mkIf cfg.provision.enable ''
${loadSecretEnvironment "domain"}
# Wait for the firezone server to come online
count=0
while ! ${getExe cfg.domain.package} pid >/dev/null
while [[ "$(curl -s "http://localhost:${toString cfg.domain.settings.HEALTHZ_PORT}" 2>/dev/null || echo)" != '{"status":"ok"}' ]]
do
sleep 1
if [[ "$count" -eq 30 ]]; then
@ -885,6 +898,7 @@ in
count=$((count++))
done
sleep 1 # Wait for server to fully come up. Not ideal to use sleep, but at least it works.
ln -sTf ${provisionStateJson} provision-state.json
${getExe cfg.domain.package} rpc 'Code.eval_file("${./provision.exs}")'
'';
@ -894,7 +908,7 @@ in
};
systemd.services.firezone-server-web = mkIf cfg.web.enable {
description = "Firezone web server";
description = "Backend web server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];
@ -910,7 +924,7 @@ in
};
systemd.services.firezone-server-api = mkIf cfg.api.enable {
description = "Firezone api server";
description = "Backend api server for the Firezone zero-trust access platform";
after = [ "firezone-initialize.service" ];
bindsTo = [ "firezone-initialize.service" ];
wantedBy = [ "firezone.target" ];