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