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.headless-client;
19in
20{
21 options = {
22 services.firezone.headless-client = {
23 enable = mkEnableOption "the firezone headless client";
24 package = mkPackageOption pkgs "firezone-headless-client" { };
25
26 name = mkOption {
27 type = types.str;
28 description = "The name of this client as shown in firezone";
29 };
30
31 apiUrl = mkOption {
32 type = types.strMatching "^wss://.+/$";
33 example = "wss://firezone.example.com/api/";
34 description = ''
35 The URL of your firezone server's API. This should be the same
36 as your server's setting for {option}`services.firezone.server.settings.api.externalUrl`,
37 but with `wss://` instead of `https://`.
38 '';
39 };
40
41 tokenFile = mkOption {
42 type = types.path;
43 example = "/run/secrets/firezone-client-token";
44 description = ''
45 A file containing the firezone client token. Do not use a nix-store path here
46 as it will make the token publicly readable!
47
48 This file will be passed via systemd credentials, it should only be accessible
49 by the root user.
50 '';
51 };
52
53 logLevel = mkOption {
54 type = types.str;
55 default = "info";
56 description = ''
57 The log level for the firezone application. See
58 [RUST_LOG](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
59 for the format.
60 '';
61 };
62
63 enableTelemetry = mkEnableOption "telemetry";
64 };
65 };
66
67 config = mkIf cfg.enable {
68 systemd.services.firezone-headless-client = {
69 description = "headless client service for the Firezone zero-trust access platform";
70 after = [ "network.target" ];
71 wantedBy = [ "multi-user.target" ];
72
73 path = [ pkgs.util-linux ];
74 script = ''
75 # If FIREZONE_ID is not given by the user, use a persisted (or newly generated) uuid.
76 if [[ -z "''${FIREZONE_ID:-}" ]]; then
77 if [[ ! -e client_id ]]; then
78 uuidgen -r > client_id
79 fi
80 export FIREZONE_ID=$(< client_id)
81 fi
82
83 exec ${getExe cfg.package}
84 '';
85
86 environment = {
87 FIREZONE_API_URL = cfg.apiUrl;
88 FIREZONE_NAME = cfg.name;
89 FIREZONE_NO_TELEMETRY = boolToString (!cfg.enableTelemetry);
90 FIREZONE_TOKEN_PATH = "%d/firezone-token";
91 LOG_DIR = "%L/dev.firezone.client";
92 RUST_LOG = cfg.logLevel;
93 };
94
95 serviceConfig = {
96 Type = "exec";
97 LoadCredential = [ "firezone-token:${cfg.tokenFile}" ];
98
99 DeviceAllow = "/dev/net/tun";
100 AmbientCapabilities = [ "CAP_NET_ADMIN" ];
101 CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
102
103 # Hardcoded values in the client :(
104 RuntimeDirectory = "dev.firezone.client";
105 StateDirectory = "dev.firezone.client";
106 WorkingDirectory = "/var/lib/dev.firezone.client";
107 LogsDirectory = "dev.firezone.client";
108
109 Restart = "on-failure";
110 RestartSec = 10;
111
112 LockPersonality = true;
113 MemoryDenyWriteExecute = true;
114 NoNewPrivileges = true;
115 PrivateMounts = true;
116 PrivateTmp = true;
117 PrivateUsers = false;
118 ProcSubset = "pid";
119 ProtectClock = true;
120 ProtectControlGroups = true;
121 ProtectHome = true;
122 ProtectHostname = true;
123 ProtectKernelLogs = true;
124 ProtectKernelModules = true;
125 ProtectKernelTunables = true;
126 ProtectProc = "invisible";
127 ProtectSystem = "strict";
128 RestrictAddressFamilies = [
129 "AF_INET"
130 "AF_INET6"
131 "AF_NETLINK"
132 "AF_UNIX"
133 ];
134 RestrictNamespaces = true;
135 RestrictRealtime = true;
136 RestrictSUIDSGID = true;
137 SystemCallArchitectures = "native";
138 SystemCallFilter = "@system-service";
139 UMask = "077";
140 };
141 };
142 };
143
144 meta.maintainers = with lib.maintainers; [
145 oddlama
146 patrickdag
147 ];
148}