at 24.11-pre 8.9 kB view raw
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}