1{
2 lib,
3 pkgs,
4 config,
5 ...
6}:
7let
8 inherit (lib)
9 boolToString
10 getExe
11 mkEnableOption
12 mkIf
13 mkOption
14 mkPackageOption
15 types
16 ;
17
18 cfg = config.services.firezone.relay;
19in
20{
21 options = {
22 services.firezone.relay = {
23 enable = mkEnableOption "the firezone relay server";
24 package = mkPackageOption pkgs "firezone-relay" { };
25
26 name = mkOption {
27 type = types.str;
28 example = "My relay";
29 description = "The name of this gateway as shown in firezone";
30 };
31
32 publicIpv4 = mkOption {
33 type = types.nullOr types.str;
34 default = null;
35 description = "The public ipv4 address of this relay";
36 };
37
38 publicIpv6 = mkOption {
39 type = types.nullOr types.str;
40 default = null;
41 description = "The public ipv6 address of this relay";
42 };
43
44 openFirewall = mkOption {
45 type = types.bool;
46 default = true;
47 description = "Opens up the main STUN port and the TURN allocation range.";
48 };
49
50 port = mkOption {
51 type = types.port;
52 default = 3478;
53 description = "The port to listen on for STUN messages";
54 };
55
56 lowestPort = mkOption {
57 type = types.port;
58 default = 49152;
59 description = "The lowest port to use in TURN allocation";
60 };
61
62 highestPort = mkOption {
63 type = types.port;
64 default = 65535;
65 description = "The highest port to use in TURN allocation";
66 };
67
68 apiUrl = mkOption {
69 type = types.strMatching "^wss://.+/$";
70 example = "wss://firezone.example.com/api/";
71 description = ''
72 The URL of your firezone server's API. This should be the same
73 as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
74 but with `wss://` instead of `https://`.
75 '';
76 };
77
78 tokenFile = mkOption {
79 type = types.path;
80 example = "/run/secrets/firezone-relay-token";
81 description = ''
82 A file containing the firezone relay token. Do not use a nix-store path here
83 as it will make the token publicly readable!
84
85 This file will be passed via systemd credentials, it should only be accessible
86 by the root user.
87 '';
88 };
89
90 logLevel = mkOption {
91 type = types.str;
92 default = "info";
93 description = ''
94 The log level for the firezone application. See
95 [RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
96 for the format.
97 '';
98 };
99
100 enableTelemetry = mkEnableOption "telemetry";
101 };
102 };
103
104 config = mkIf cfg.enable {
105 assertions = [
106 {
107 assertion = cfg.publicIpv4 != null || cfg.publicIpv6 != null;
108 message = "At least one of `services.firezone.relay.publicIpv4` and `services.firezone.relay.publicIpv6` must be set";
109 }
110 ];
111
112 networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.port ];
113 networking.firewall.allowedUDPPortRanges = mkIf cfg.openFirewall [
114 {
115 from = cfg.lowestPort;
116 to = cfg.highestPort;
117 }
118 ];
119
120 systemd.services.firezone-relay = {
121 description = "relay service for the Firezone zero-trust access platform";
122 after = [ "network.target" ];
123 wantedBy = [ "multi-user.target" ];
124
125 path = [ pkgs.util-linux ];
126 script = ''
127 # If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
128 if [[ -z "''${FIREZONE_ID:-}" ]]; then
129 if [[ ! -e relay_id ]]; then
130 uuidgen -r > relay_id
131 fi
132 export FIREZONE_ID=$(< relay_id)
133 fi
134
135 export FIREZONE_TOKEN=$(< "$CREDENTIALS_DIRECTORY/firezone-token")
136 exec ${getExe cfg.package}
137 '';
138
139 environment = {
140 FIREZONE_API_URL = cfg.apiUrl;
141 FIREZONE_NAME = cfg.name;
142 FIREZONE_TELEMETRY = boolToString cfg.enableTelemetry;
143
144 PUBLIC_IP4_ADDR = cfg.publicIpv4;
145 PUBLIC_IP6_ADDR = cfg.publicIpv6;
146
147 LISTEN_PORT = toString cfg.port;
148 LOWEST_PORT = toString cfg.lowestPort;
149 HIGHEST_PORT = toString cfg.highestPort;
150
151 RUST_LOG = cfg.logLevel;
152 LOG_FORMAT = "human";
153 };
154
155 serviceConfig = {
156 Type = "exec";
157 DynamicUser = true;
158 User = "firezone-relay";
159 LoadCredential = [ "firezone-token:${cfg.tokenFile}" ];
160
161 StateDirectory = "firezone-relay";
162 WorkingDirectory = "/var/lib/firezone-relay";
163
164 Restart = "on-failure";
165 RestartSec = 10;
166
167 LockPersonality = true;
168 MemoryDenyWriteExecute = true;
169 NoNewPrivileges = true;
170 PrivateMounts = true;
171 PrivateTmp = true;
172 PrivateUsers = false;
173 ProcSubset = "pid";
174 ProtectClock = true;
175 ProtectControlGroups = true;
176 ProtectHome = true;
177 ProtectHostname = true;
178 ProtectKernelLogs = true;
179 ProtectKernelModules = true;
180 ProtectKernelTunables = true;
181 ProtectProc = "invisible";
182 ProtectSystem = "strict";
183 RestrictAddressFamilies = [
184 "AF_INET"
185 "AF_INET6"
186 "AF_NETLINK"
187 ];
188 RestrictNamespaces = true;
189 RestrictRealtime = true;
190 RestrictSUIDSGID = true;
191 SystemCallArchitectures = "native";
192 SystemCallFilter = "@system-service";
193 UMask = "077";
194 };
195 };
196 };
197
198 meta.maintainers = with lib.maintainers; [
199 oddlama
200 patrickdag
201 ];
202}