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:
parent
bbb76ae5ec
commit
e918a6d581
14 changed files with 1174 additions and 141 deletions
|
@ -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
|
||||
|
|
170
modules/firezone-gateways.nix
Normal file
170
modules/firezone-gateways.nix
Normal 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
177
modules/firezone-relay.nix
Normal 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
|
||||
];
|
||||
}
|
|
@ -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" ];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue