1{ config, lib, pkgs, ... }:
2
3with lib;
4let
5 cfg = config.services.usbguard;
6
7 # valid policy options
8 policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
9
10 # decide what file to use for rules
11 ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
12
13 daemonConf = ''
14 # generated by nixos/modules/services/security/usbguard.nix
15 RuleFile=${ruleFile}
16 ImplicitPolicyTarget=${cfg.implicitPolicyTarget}
17 PresentDevicePolicy=${cfg.presentDevicePolicy}
18 PresentControllerPolicy=${cfg.presentControllerPolicy}
19 InsertedDevicePolicy=${cfg.insertedDevicePolicy}
20 RestoreControllerDeviceState=${boolToString cfg.restoreControllerDeviceState}
21 # this does not seem useful for endusers to change
22 DeviceManagerBackend=uevent
23 IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
24 IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
25 IPCAccessControlFiles=/var/lib/usbguard/IPCAccessControl.d/
26 DeviceRulesWithPort=${boolToString cfg.deviceRulesWithPort}
27 # HACK: that way audit logs still land in the journal
28 AuditFilePath=/dev/null
29 '';
30
31 daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
32
33in
34{
35
36 ###### interface
37
38 options = {
39 services.usbguard = {
40 enable = mkEnableOption "USBGuard daemon";
41
42 package = mkPackageOption pkgs "usbguard" {
43 extraDescription = ''
44 If you do not need the Qt GUI, use `pkgs.usbguard-nox` to save disk space.
45 '';
46 };
47
48 ruleFile = mkOption {
49 type = types.nullOr types.path;
50 default = "/var/lib/usbguard/rules.conf";
51 example = "/run/secrets/usbguard-rules";
52 description = ''
53 This tells the USBGuard daemon which file to load as policy rule set.
54
55 The file can be changed manually or via the IPC interface assuming it has the right file permissions.
56
57 For more details see {manpage}`usbguard-rules.conf(5)`.
58 '';
59
60 };
61 rules = mkOption {
62 type = types.nullOr types.lines;
63 default = null;
64 example = ''
65 allow with-interface equals { 08:*:* }
66 '';
67 description = ''
68 The USBGuard daemon will load this as the policy rule set.
69 As these rules are NixOS managed they are immutable and can't
70 be changed by the IPC interface.
71
72 If you do not set this option, the USBGuard daemon will load
73 it's policy rule set from the option configured in `services.usbguard.ruleFile`.
74
75 Running `usbguard generate-policy` as root will
76 generate a config for your currently plugged in devices.
77
78 For more details see {manpage}`usbguard-rules.conf(5)`.
79 '';
80 };
81
82 implicitPolicyTarget = mkOption {
83 type = types.enum [ "allow" "block" "reject" ];
84 default = "block";
85 description = ''
86 How to treat USB devices that don't match any rule in the policy.
87 Target should be one of allow, block or reject (logically remove the
88 device node from the system).
89 '';
90 };
91
92 presentDevicePolicy = mkOption {
93 type = policy;
94 default = "apply-policy";
95 description = ''
96 How to treat USB devices that are already connected when the daemon
97 starts. Policy should be one of allow, block, reject, keep (keep
98 whatever state the device is currently in) or apply-policy (evaluate
99 the rule set for every present device).
100 '';
101 };
102
103 presentControllerPolicy = mkOption {
104 type = policy;
105 default = "keep";
106 description = ''
107 How to treat USB controller devices that are already connected when
108 the daemon starts. One of allow, block, reject, keep or apply-policy.
109 '';
110 };
111
112 insertedDevicePolicy = mkOption {
113 type = types.enum [ "block" "reject" "apply-policy" ];
114 default = "apply-policy";
115 description = ''
116 How to treat USB devices that are already connected after the daemon
117 starts. One of block, reject, apply-policy.
118 '';
119 };
120
121 restoreControllerDeviceState = mkOption {
122 type = types.bool;
123 default = false;
124 description = ''
125 The USBGuard daemon modifies some attributes of controller
126 devices like the default authorization state of new child device
127 instances. Using this setting, you can control whether the daemon
128 will try to restore the attribute values to the state before
129 modification on shutdown.
130 '';
131 };
132
133 IPCAllowedUsers = mkOption {
134 type = types.listOf types.str;
135 default = [ "root" ];
136 example = [ "root" "yourusername" ];
137 description = ''
138 A list of usernames that the daemon will accept IPC connections from.
139 '';
140 };
141
142 IPCAllowedGroups = mkOption {
143 type = types.listOf types.str;
144 default = [ ];
145 example = [ "wheel" ];
146 description = ''
147 A list of groupnames that the daemon will accept IPC connections
148 from.
149 '';
150 };
151
152 deviceRulesWithPort = mkOption {
153 type = types.bool;
154 default = false;
155 description = ''
156 Generate device specific rules including the "via-port" attribute.
157 '';
158 };
159
160 dbus.enable = mkEnableOption "USBGuard dbus daemon";
161 };
162 };
163
164
165 ###### implementation
166
167 config = mkIf cfg.enable {
168
169 environment.systemPackages = [ cfg.package ];
170
171 systemd.services = {
172 usbguard = {
173 description = "USBGuard daemon";
174
175 wantedBy = [ "basic.target" ];
176 wants = [ "systemd-udevd.service" ];
177
178 # make sure an empty rule file exists
179 preStart = ''[ -f "${ruleFile}" ] || touch ${ruleFile}'';
180
181 serviceConfig = {
182 Type = "simple";
183 ExecStart = "${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}";
184 Restart = "on-failure";
185
186 StateDirectory = [
187 "usbguard"
188 "usbguard/IPCAccessControl.d"
189 ];
190
191 AmbientCapabilities = "";
192 CapabilityBoundingSet = "CAP_CHOWN CAP_FOWNER";
193 DeviceAllow = "/dev/null rw";
194 DevicePolicy = "strict";
195 IPAddressDeny = "any";
196 LockPersonality = true;
197 MemoryDenyWriteExecute = true;
198 NoNewPrivileges = true;
199 PrivateDevices = true;
200 PrivateTmp = true;
201 ProtectControlGroups = true;
202 ProtectHome = true;
203 ProtectKernelModules = true;
204 ProtectSystem = true;
205 ReadOnlyPaths = "-/";
206 ReadWritePaths = "-/dev/shm -/tmp";
207 RestrictAddressFamilies = [ "AF_UNIX" "AF_NETLINK" ];
208 RestrictNamespaces = true;
209 RestrictRealtime = true;
210 SystemCallArchitectures = "native";
211 SystemCallFilter = "@system-service";
212 UMask = "0077";
213 };
214 };
215
216 usbguard-dbus = mkIf cfg.dbus.enable {
217 description = "USBGuard D-Bus Service";
218
219 wantedBy = [ "multi-user.target" ];
220 requires = [ "usbguard.service" ];
221
222 serviceConfig = {
223 Type = "dbus";
224 BusName = "org.usbguard1";
225 ExecStart = "${cfg.package}/bin/usbguard-dbus --system";
226 Restart = "on-failure";
227 };
228
229 aliases = [ "dbus-org.usbguard.service" ];
230 };
231 };
232
233 security.polkit.extraConfig =
234 let
235 groupCheck = (lib.concatStrings (map
236 (g: "subject.isInGroup(\"${g}\") || ")
237 cfg.IPCAllowedGroups))
238 + "false";
239 in
240 optionalString cfg.dbus.enable ''
241 polkit.addRule(function(action, subject) {
242 if ((action.id == "org.usbguard.Policy1.listRules" ||
243 action.id == "org.usbguard.Policy1.appendRule" ||
244 action.id == "org.usbguard.Policy1.removeRule" ||
245 action.id == "org.usbguard.Devices1.applyDevicePolicy" ||
246 action.id == "org.usbguard.Devices1.listDevices" ||
247 action.id == "org.usbguard1.getParameter" ||
248 action.id == "org.usbguard1.setParameter") &&
249 subject.active == true && subject.local == true &&
250 (${groupCheck})) {
251 return polkit.Result.YES;
252 }
253 });
254 '';
255 };
256 imports = [
257 (mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
258 (mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
259 (mkRenamedOptionModule [ "services" "usbguard" "implictPolicyTarget" ] [ "services" "usbguard" "implicitPolicyTarget" ])
260 ];
261}