lol

Merge pull request #222536 from oddlama/master

authored by

Ryan Lahfa and committed by
GitHub
7be83143 89d68ceb

+1455 -243
+8
nixos/doc/manual/release-notes/rl-2311.section.md
··· 4 4 5 5 - FoundationDB now defaults to major version 7. 6 6 7 + - Support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK was enabled in the `hostapd` package, along with a significant rework of the hostapd module. 8 + 7 9 ## New Services {#sec-release-23.11-new-services} 8 10 9 11 - [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server built for redstone. Available as [services.mchprs](#opt-services.mchprs.enable). ··· 31 33 - `writeTextFile` now requires `executable` to be boolean, values like `null` or `""` will now fail to evaluate. 32 34 33 35 - The latest version of `clonehero` now stores custom content in `~/.clonehero`. See the [migration instructions](https://clonehero.net/2022/11/29/v23-to-v1-migration-instructions.html). Typically, these content files would exist along side the binary, but the previous build used a wrapper script that would store them in `~/.config/unity3d/srylain Inc_/Clone Hero`. 36 + 37 + - The `services.hostapd` module was rewritten to support `passwordFile` like options, WPA3-SAE, and management of multiple interfaces. This breaks compatibility with older configurations. 38 + - `hostapd` is now started with additional systemd sandbox/hardening options for better security. 39 + - `services.hostapd.interface` was replaced with a per-radio and per-bss configuration scheme using [services.hostapd.radios](#opt-services.hostapd.radios). 40 + - `services.hostapd.wpa` has been replaced by [services.hostapd.radios.<name>.networks.<name>.authentication.wpaPassword](#opt-services.hostapd.radios._name_.networks._name_.authentication.wpaPassword) and [services.hostapd.radios.<name>.networks.<name>.authentication.saePasswords](#opt-services.hostapd.radios._name_.networks._name_.authentication.saePasswords) which configure WPA2-PSK and WP3-SAE respectively. 41 + - The default authentication has been changed to WPA3-SAE. Options for other (legacy) schemes are still available. 34 42 35 43 - `python3.pkgs.fetchPypi` (and `python3Packages.fetchPypi`) has been deprecated in favor of top-level `fetchPypi`. 36 44
+1239 -176
nixos/modules/services/networking/hostapd.nix
··· 1 1 { config, lib, pkgs, utils, ... }: 2 + # All hope abandon ye who enter here. hostapd's configuration 3 + # format is ... special, and you won't be able to infer any 4 + # of their assumptions from just reading the "documentation" 5 + # (i.e. the example config). Assume footguns at all points - 6 + # to make informed decisions you will probably need to look 7 + # at hostapd's code. You have been warned, proceed with care. 8 + let 9 + inherit 10 + (lib) 11 + attrNames 12 + attrValues 13 + concatLists 14 + concatMap 15 + concatMapStrings 16 + concatStringsSep 17 + count 18 + escapeShellArg 19 + filter 20 + flip 21 + generators 22 + getAttr 23 + hasPrefix 24 + imap0 25 + isInt 26 + isString 27 + length 28 + literalExpression 29 + maintainers 30 + mapAttrsToList 31 + mdDoc 32 + mkDefault 33 + mkEnableOption 34 + mkIf 35 + mkOption 36 + mkPackageOption 37 + mkRemovedOptionModule 38 + optional 39 + optionalAttrs 40 + optionalString 41 + optionals 42 + singleton 43 + stringLength 44 + toLower 45 + types 46 + unique 47 + ; 2 48 3 - # TODO: 4 - # 5 - # asserts 6 - # ensure that the nl80211 module is loaded/compiled in the kernel 7 - # wpa_supplicant and hostapd on the same wireless interface doesn't make any sense 49 + cfg = config.services.hostapd; 8 50 9 - with lib; 51 + extraSettingsFormat = { 52 + type = let 53 + singleAtom = types.oneOf [ types.bool types.int types.str ]; 54 + atom = types.either singleAtom (types.listOf singleAtom) // { 55 + description = "atom (bool, int or string) or a list of them for duplicate keys"; 56 + }; 57 + in types.attrsOf atom; 10 58 11 - let 59 + generate = name: value: pkgs.writeText name (generators.toKeyValue { 60 + listsAsDuplicateKeys = true; 61 + mkKeyValue = generators.mkKeyValueDefault { 62 + mkValueString = v: 63 + if isInt v then toString v 64 + else if isString v then v 65 + else if true == v then "1" 66 + else if false == v then "0" 67 + else throw "unsupported type ${builtins.typeOf v}: ${(generators.toPretty {}) v}"; 68 + } "="; 69 + } value); 70 + }; 12 71 13 - cfg = config.services.hostapd; 72 + # Generates the header for a single BSS (i.e. WiFi network) 73 + writeBssHeader = radio: bss: bssIdx: pkgs.writeText "hostapd-radio-${radio}-bss-${bss}.conf" '' 74 + ''\n''\n# BSS ${toString bssIdx}: ${bss} 75 + ################################ 14 76 15 - escapedInterface = utils.escapeSystemdPath cfg.interface; 77 + ${if bssIdx == 0 then "interface" else "bss"}=${bss} 78 + ''; 79 + 80 + makeRadioRuntimeFiles = radio: radioCfg: 81 + pkgs.writeShellScript "make-hostapd-${radio}-files" ('' 82 + set -euo pipefail 16 83 17 - configFile = pkgs.writeText "hostapd.conf" '' 18 - interface=${cfg.interface} 19 - driver=${cfg.driver} 20 - ssid=${cfg.ssid} 21 - hw_mode=${cfg.hwMode} 22 - channel=${toString cfg.channel} 23 - ieee80211n=1 24 - ieee80211ac=1 25 - ${optionalString (cfg.countryCode != null) "country_code=${cfg.countryCode}"} 26 - ${optionalString (cfg.countryCode != null) "ieee80211d=1"} 84 + hostapd_config_file=/run/hostapd/${escapeShellArg radio}.hostapd.conf 85 + rm -f "$hostapd_config_file" 86 + cat > "$hostapd_config_file" <<EOF 87 + # Radio base configuration: ${radio} 88 + ################################ 27 89 28 - # logging (debug level) 29 - logger_syslog=-1 30 - logger_syslog_level=${toString cfg.logLevel} 31 - logger_stdout=-1 32 - logger_stdout_level=${toString cfg.logLevel} 90 + EOF 33 91 34 - ctrl_interface=/run/hostapd 35 - ctrl_interface_group=${cfg.group} 92 + cat ${escapeShellArg (extraSettingsFormat.generate "hostapd-radio-${radio}-extra.conf" radioCfg.settings)} >> "$hostapd_config_file" 93 + ${concatMapStrings (script: "${script} \"$hostapd_config_file\"\n") (attrValues radioCfg.dynamicConfigScripts)} 94 + '' 95 + + concatMapStrings (x: "${x}\n") (imap0 (i: f: f i) 96 + (mapAttrsToList (bss: bssCfg: bssIdx: '' 97 + ''\n# BSS configuration: ${bss} 36 98 37 - ${optionalString cfg.wpa '' 38 - wpa=2 39 - wpa_pairwise=CCMP 40 - wpa_passphrase=${cfg.wpaPassphrase} 41 - ''} 42 - ${optionalString cfg.noScan "noscan=1"} 99 + mac_allow_file=/run/hostapd/${escapeShellArg bss}.mac.allow 100 + rm -f "$mac_allow_file" 101 + touch "$mac_allow_file" 43 102 44 - ${cfg.extraConfig} 45 - '' ; 103 + mac_deny_file=/run/hostapd/${escapeShellArg bss}.mac.deny 104 + rm -f "$mac_deny_file" 105 + touch "$mac_deny_file" 46 106 47 - in 107 + cat ${writeBssHeader radio bss bssIdx} >> "$hostapd_config_file" 108 + cat ${escapeShellArg (extraSettingsFormat.generate "hostapd-radio-${radio}-bss-${bss}-extra.conf" bssCfg.settings)} >> "$hostapd_config_file" 109 + ${concatMapStrings (script: "${script} \"$hostapd_config_file\" \"$mac_allow_file\" \"$mac_deny_file\"\n") (attrValues bssCfg.dynamicConfigScripts)} 110 + '') radioCfg.networks))); 48 111 49 - { 50 - ###### interface 112 + runtimeConfigFiles = mapAttrsToList (radio: _: "/run/hostapd/${radio}.hostapd.conf") cfg.radios; 113 + in { 114 + meta.maintainers = with maintainers; [ oddlama ]; 51 115 52 116 options = { 53 - 54 117 services.hostapd = { 118 + enable = mkEnableOption (mdDoc '' 119 + Whether to enable hostapd. hostapd is a user space daemon for access point and 120 + authentication servers. It implements IEEE 802.11 access point management, 121 + IEEE 802.1X/WPA/WPA2/EAP Authenticators, RADIUS client, EAP server, and RADIUS 122 + authentication server. 123 + ''); 55 124 56 - enable = mkOption { 57 - type = types.bool; 58 - default = false; 59 - description = lib.mdDoc '' 60 - Enable putting a wireless interface into infrastructure mode, 61 - allowing other wireless devices to associate with the wireless 62 - interface and do wireless networking. A simple access point will 63 - {option}`enable hostapd.wpa`, 64 - {option}`hostapd.wpaPassphrase`, and 65 - {option}`hostapd.ssid`, as well as DHCP on the wireless 66 - interface to provide IP addresses to the associated stations, and 67 - NAT (from the wireless interface to an upstream interface). 125 + package = mkPackageOption pkgs "hostapd" {}; 126 + 127 + radios = mkOption { 128 + default = {}; 129 + example = literalExpression '' 130 + { 131 + # Simple 2.4GHz AP 132 + wlp2s0 = { 133 + # countryCode = "US"; 134 + networks.wlp2s0 = { 135 + ssid = "AP 1"; 136 + authentication.saePasswords = [{ password = "a flakey password"; }]; # Use saePasswordsFile if possible. 137 + }; 138 + }; 139 + 140 + # WiFi 5 (5GHz) with two advertised networks 141 + wlp3s0 = { 142 + band = "5g"; 143 + channel = 0; # Enable automatic channel selection (ACS). Use only if your hardware supports it. 144 + # countryCode = "US"; 145 + networks.wlp3s0 = { 146 + ssid = "My AP"; 147 + authentication.saePasswords = [{ password = "a flakey password"; }]; # Use saePasswordsFile if possible. 148 + }; 149 + networks.wlp3s0-1 = { 150 + ssid = "Open AP with WiFi5"; 151 + authentication.mode = "none"; 152 + }; 153 + }; 154 + 155 + # Legacy WPA2 example 156 + wlp4s0 = { 157 + # countryCode = "US"; 158 + networks.wlp4s0 = { 159 + ssid = "AP 2"; 160 + authentication = { 161 + mode = "wpa2-sha256"; 162 + wpaPassword = "a flakey password"; # Use wpaPasswordFile if possible. 163 + }; 164 + }; 165 + }; 166 + } 68 167 ''; 69 - }; 168 + description = mdDoc '' 169 + This option allows you to define APs for one or multiple physical radios. 170 + At least one radio must be specified. 70 171 71 - interface = mkOption { 72 - example = "wlp2s0"; 73 - type = types.str; 74 - description = lib.mdDoc '' 75 - The interfaces {command}`hostapd` will use. 172 + For each radio, hostapd requires a separate logical interface (like wlp3s0, wlp3s1, ...). 173 + A default interface is usually be created automatically by your system, but to use 174 + multiple radios of a single device, it may be required to create additional logical interfaces 175 + for example by using {option}`networking.wlanInterfaces`. 176 + 177 + Each physical radio can only support a single hardware-mode that is configured via 178 + ({option}`services.hostapd.radios.<radio>.band`). To create a dual-band 179 + or tri-band AP, you will have to use a device that has multiple physical radios 180 + and supports configuring multiple APs (Refer to valid interface combinations in 181 + {command}`iw list`). 76 182 ''; 77 - }; 183 + type = types.attrsOf (types.submodule (radioSubmod: { 184 + options = { 185 + driver = mkOption { 186 + default = "nl80211"; 187 + example = "none"; 188 + type = types.str; 189 + description = mdDoc '' 190 + The driver {command}`hostapd` will use. 191 + {var}`nl80211` is used with all Linux mac80211 drivers. 192 + {var}`none` is used if building a standalone RADIUS server that does 193 + not control any wireless/wired driver. 194 + Most applications will probably use the default. 195 + ''; 196 + }; 197 + 198 + noScan = mkOption { 199 + type = types.bool; 200 + default = false; 201 + description = mdDoc '' 202 + Disables scan for overlapping BSSs in HT40+/- mode. 203 + Caution: turning this on will likely violate regulatory requirements! 204 + ''; 205 + }; 206 + 207 + countryCode = mkOption { 208 + default = null; 209 + example = "US"; 210 + type = types.nullOr types.str; 211 + description = mdDoc '' 212 + Country code (ISO/IEC 3166-1). Used to set regulatory domain. 213 + Set as needed to indicate country in which device is operating. 214 + This can limit available channels and transmit power. 215 + These two octets are used as the first two octets of the Country String 216 + (dot11CountryString). 217 + 218 + Setting this will force you to also enable IEEE 802.11d and IEEE 802.11h. 219 + 220 + IEEE 802.11d: This advertises the countryCode and the set of allowed channels 221 + and transmit power levels based on the regulatory limits. 222 + 223 + IEEE802.11h: This enables radar detection and DFS (Dynamic Frequency Selection) 224 + support if available. DFS support is required on outdoor 5 GHz channels in most 225 + countries of the world. 226 + ''; 227 + }; 228 + 229 + band = mkOption { 230 + default = "2g"; 231 + type = types.enum ["2g" "5g" "6g" "60g"]; 232 + description = mdDoc '' 233 + Specifies the frequency band to use, possible values are 2g for 2.4 GHz, 234 + 5g for 5 GHz, 6g for 6 GHz and 60g for 60 GHz. 235 + ''; 236 + }; 237 + 238 + channel = mkOption { 239 + default = 7; 240 + example = 11; 241 + type = types.int; 242 + description = mdDoc '' 243 + The channel to operate on. Use 0 to enable ACS (Automatic Channel Selection). 244 + Beware that not every device supports ACS in which case {command}`hostapd` 245 + will fail to start. 246 + ''; 247 + }; 248 + 249 + settings = mkOption { 250 + default = {}; 251 + example = { acs_exclude_dfs = true; }; 252 + type = types.submodule { 253 + freeformType = extraSettingsFormat.type; 254 + }; 255 + description = mdDoc '' 256 + Extra configuration options to put at the end of global initialization, before defining BSSs. 257 + To find out which options are global and which are per-bss you have to read hostapd's source code, 258 + which is non-trivial and not documented otherwise. 259 + 260 + Lists will be converted to multiple definitions of the same key, and booleans to 0/1. 261 + Otherwise, the inputs are not modified or checked for correctness. 262 + ''; 263 + }; 264 + 265 + dynamicConfigScripts = mkOption { 266 + default = {}; 267 + type = types.attrsOf types.path; 268 + example = literalExpression '' 269 + { 270 + exampleDynamicConfig = pkgs.writeShellScript "dynamic-config" ''' 271 + HOSTAPD_CONFIG=$1 272 + 273 + cat >> "$HOSTAPD_CONFIG" << EOF 274 + # Add some dynamically generated statements here, 275 + # for example based on the physical adapter in use 276 + EOF 277 + '''; 278 + } 279 + ''; 280 + description = mdDoc '' 281 + All of these scripts will be executed in lexicographical order before hostapd 282 + is started, right after the global segment was generated and may dynamically 283 + append global options the generated configuration file. 284 + 285 + The first argument will point to the configuration file that you may append to. 286 + ''; 287 + }; 288 + 289 + #### IEEE 802.11n (WiFi 4) related configuration 290 + 291 + wifi4 = { 292 + enable = mkOption { 293 + default = true; 294 + type = types.bool; 295 + description = mdDoc '' 296 + Enables support for IEEE 802.11n (WiFi 4, HT). 297 + This is enabled by default, since the vase majority of devices 298 + are expected to support this. 299 + ''; 300 + }; 301 + 302 + capabilities = mkOption { 303 + type = types.listOf types.str; 304 + default = ["HT40" "HT40-" "SHORT-GI-20" "SHORT-GI-40"]; 305 + example = ["LDPC" "HT40+" "HT40-" "GF" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1"]; 306 + description = mdDoc '' 307 + HT (High Throughput) capabilities given as a list of flags. 308 + Please refer to the hostapd documentation for allowed values and 309 + only set values supported by your physical adapter. 310 + 311 + The default contains common values supported by most adapters. 312 + ''; 313 + }; 314 + 315 + require = mkOption { 316 + default = false; 317 + type = types.bool; 318 + description = mdDoc "Require stations (clients) to support WiFi 4 (HT) and disassociate them if they don't."; 319 + }; 320 + }; 321 + 322 + #### IEEE 802.11ac (WiFi 5) related configuration 323 + 324 + wifi5 = { 325 + enable = mkOption { 326 + default = true; 327 + type = types.bool; 328 + description = mdDoc "Enables support for IEEE 802.11ac (WiFi 5, VHT)"; 329 + }; 330 + 331 + capabilities = mkOption { 332 + type = types.listOf types.str; 333 + default = []; 334 + example = ["SHORT-GI-80" "TX-STBC-2BY1" "RX-STBC-1" "RX-ANTENNA-PATTERN" "TX-ANTENNA-PATTERN"]; 335 + description = mdDoc '' 336 + VHT (Very High Throughput) capabilities given as a list of flags. 337 + Please refer to the hostapd documentation for allowed values and 338 + only set values supported by your physical adapter. 339 + ''; 340 + }; 341 + 342 + require = mkOption { 343 + default = false; 344 + type = types.bool; 345 + description = mdDoc "Require stations (clients) to support WiFi 5 (VHT) and disassociate them if they don't."; 346 + }; 347 + 348 + operatingChannelWidth = mkOption { 349 + default = "20or40"; 350 + type = types.enum ["20or40" "80" "160" "80+80"]; 351 + apply = x: 352 + getAttr x { 353 + "20or40" = 0; 354 + "80" = 1; 355 + "160" = 2; 356 + "80+80" = 3; 357 + }; 358 + description = mdDoc '' 359 + Determines the operating channel width for VHT. 360 + 361 + - {var}`"20or40"`: 20 or 40 MHz operating channel width 362 + - {var}`"80"`: 80 MHz channel width 363 + - {var}`"160"`: 160 MHz channel width 364 + - {var}`"80+80"`: 80+80 MHz channel width 365 + ''; 366 + }; 367 + }; 368 + 369 + #### IEEE 802.11ax (WiFi 6) related configuration 370 + 371 + wifi6 = { 372 + enable = mkOption { 373 + default = false; 374 + type = types.bool; 375 + description = mdDoc "Enables support for IEEE 802.11ax (WiFi 6, HE)"; 376 + }; 377 + 378 + require = mkOption { 379 + default = false; 380 + type = types.bool; 381 + description = mdDoc "Require stations (clients) to support WiFi 6 (HE) and disassociate them if they don't."; 382 + }; 383 + 384 + singleUserBeamformer = mkOption { 385 + default = false; 386 + type = types.bool; 387 + description = mdDoc "HE single user beamformer support"; 388 + }; 389 + 390 + singleUserBeamformee = mkOption { 391 + default = false; 392 + type = types.bool; 393 + description = mdDoc "HE single user beamformee support"; 394 + }; 395 + 396 + multiUserBeamformer = mkOption { 397 + default = false; 398 + type = types.bool; 399 + description = mdDoc "HE multi user beamformee support"; 400 + }; 401 + 402 + operatingChannelWidth = mkOption { 403 + default = "20or40"; 404 + type = types.enum ["20or40" "80" "160" "80+80"]; 405 + apply = x: 406 + getAttr x { 407 + "20or40" = 0; 408 + "80" = 1; 409 + "160" = 2; 410 + "80+80" = 3; 411 + }; 412 + description = mdDoc '' 413 + Determines the operating channel width for HE. 414 + 415 + - {var}`"20or40"`: 20 or 40 MHz operating channel width 416 + - {var}`"80"`: 80 MHz channel width 417 + - {var}`"160"`: 160 MHz channel width 418 + - {var}`"80+80"`: 80+80 MHz channel width 419 + ''; 420 + }; 421 + }; 422 + 423 + #### IEEE 802.11be (WiFi 7) related configuration 424 + 425 + wifi7 = { 426 + enable = mkOption { 427 + default = false; 428 + type = types.bool; 429 + description = mdDoc '' 430 + Enables support for IEEE 802.11be (WiFi 7, EHT). This is currently experimental 431 + and requires you to manually enable CONFIG_IEEE80211BE when building hostapd. 432 + ''; 433 + }; 434 + 435 + singleUserBeamformer = mkOption { 436 + default = false; 437 + type = types.bool; 438 + description = mdDoc "EHT single user beamformer support"; 439 + }; 440 + 441 + singleUserBeamformee = mkOption { 442 + default = false; 443 + type = types.bool; 444 + description = mdDoc "EHT single user beamformee support"; 445 + }; 446 + 447 + multiUserBeamformer = mkOption { 448 + default = false; 449 + type = types.bool; 450 + description = mdDoc "EHT multi user beamformee support"; 451 + }; 452 + 453 + operatingChannelWidth = mkOption { 454 + default = "20or40"; 455 + type = types.enum ["20or40" "80" "160" "80+80"]; 456 + apply = x: 457 + getAttr x { 458 + "20or40" = 0; 459 + "80" = 1; 460 + "160" = 2; 461 + "80+80" = 3; 462 + }; 463 + description = mdDoc '' 464 + Determines the operating channel width for EHT. 465 + 466 + - {var}`"20or40"`: 20 or 40 MHz operating channel width 467 + - {var}`"80"`: 80 MHz channel width 468 + - {var}`"160"`: 160 MHz channel width 469 + - {var}`"80+80"`: 80+80 MHz channel width 470 + ''; 471 + }; 472 + }; 473 + 474 + #### BSS definitions 475 + 476 + networks = mkOption { 477 + default = {}; 478 + example = literalExpression '' 479 + { 480 + wlp2s0 = { 481 + ssid = "Primary advertised network"; 482 + authentication.saePasswords = [{ password = "a flakey password"; }]; # Use saePasswordsFile if possible. 483 + }; 484 + wlp2s0-1 = { 485 + ssid = "Secondary advertised network (Open)"; 486 + authentication.mode = "none"; 487 + }; 488 + } 489 + ''; 490 + description = mdDoc '' 491 + This defines a BSS, colloquially known as a WiFi network. 492 + You have to specify at least one. 493 + ''; 494 + type = types.attrsOf (types.submodule (bssSubmod: { 495 + options = { 496 + logLevel = mkOption { 497 + default = 2; 498 + type = types.int; 499 + description = mdDoc '' 500 + Levels (minimum value for logged events): 501 + 0 = verbose debugging 502 + 1 = debugging 503 + 2 = informational messages 504 + 3 = notification 505 + 4 = warning 506 + ''; 507 + }; 508 + 509 + group = mkOption { 510 + default = "wheel"; 511 + example = "network"; 512 + type = types.str; 513 + description = mdDoc '' 514 + Members of this group can access the control socket for this interface. 515 + ''; 516 + }; 517 + 518 + utf8Ssid = mkOption { 519 + default = true; 520 + type = types.bool; 521 + description = mdDoc "Whether the SSID is to be interpreted using UTF-8 encoding."; 522 + }; 523 + 524 + ssid = mkOption { 525 + example = "❄️ cool ❄️"; 526 + type = types.str; 527 + description = mdDoc "SSID to be used in IEEE 802.11 management frames."; 528 + }; 529 + 530 + bssid = mkOption { 531 + type = types.nullOr types.str; 532 + default = null; 533 + example = "11:22:33:44:55:66"; 534 + description = mdDoc '' 535 + Specifies the BSSID for this BSS. Usually determined automatically, 536 + but for now you have to manually specify them when using multiple BSS. 537 + Try assigning related addresses from the locally administered MAC address ranges, 538 + by reusing the hardware address but replacing the second nibble with 2, 6, A or E. 539 + (e.g. if real address is `XX:XX:XX:XX:XX`, try `X2:XX:XX:XX:XX:XX`, `X6:XX:XX:XX:XX:XX`, ... 540 + for the second, third, ... BSS) 541 + ''; 542 + }; 543 + 544 + macAcl = mkOption { 545 + default = "deny"; 546 + type = types.enum ["deny" "allow" "radius"]; 547 + apply = x: 548 + getAttr x { 549 + "deny" = 0; 550 + "allow" = 1; 551 + "radius" = 2; 552 + }; 553 + description = mdDoc '' 554 + Station MAC address -based authentication. The following modes are available: 555 + 556 + - {var}`"deny"`: Allow unless listed in {option}`macDeny` (default) 557 + - {var}`"allow"`: Deny unless listed in {option}`macAllow` 558 + - {var}`"radius"`: Use external radius server, but check both {option}`macAllow` and {option}`macDeny` first 559 + 560 + Please note that this kind of access control requires a driver that uses 561 + hostapd to take care of management frame processing and as such, this can be 562 + used with driver=hostap or driver=nl80211, but not with driver=atheros. 563 + ''; 564 + }; 565 + 566 + macAllow = mkOption { 567 + type = types.listOf types.str; 568 + default = []; 569 + example = ["11:22:33:44:55:66"]; 570 + description = mdDoc '' 571 + Specifies the MAC addresses to allow if {option}`macAcl` is set to {var}`"allow"` or {var}`"radius"`. 572 + These values will be world-readable in the Nix store. Values will automatically be merged with 573 + {option}`macAllowFile` if necessary. 574 + ''; 575 + }; 576 + 577 + macAllowFile = mkOption { 578 + type = types.nullOr types.path; 579 + default = null; 580 + description = mdDoc '' 581 + Specifies a file containing the MAC addresses to allow if {option}`macAcl` is set to {var}`"allow"` or {var}`"radius"`. 582 + The file should contain exactly one MAC address per line. Comments and empty lines are ignored, 583 + only lines starting with a valid MAC address will be considered (e.g. `11:22:33:44:55:66`) and 584 + any content after the MAC address is ignored. 585 + ''; 586 + }; 587 + 588 + macDeny = mkOption { 589 + type = types.listOf types.str; 590 + default = []; 591 + example = ["11:22:33:44:55:66"]; 592 + description = mdDoc '' 593 + Specifies the MAC addresses to deny if {option}`macAcl` is set to {var}`"deny"` or {var}`"radius"`. 594 + These values will be world-readable in the Nix store. Values will automatically be merged with 595 + {option}`macDenyFile` if necessary. 596 + ''; 597 + }; 598 + 599 + macDenyFile = mkOption { 600 + type = types.nullOr types.path; 601 + default = null; 602 + description = mdDoc '' 603 + Specifies a file containing the MAC addresses to deny if {option}`macAcl` is set to {var}`"deny"` or {var}`"radius"`. 604 + The file should contain exactly one MAC address per line. Comments and empty lines are ignored, 605 + only lines starting with a valid MAC address will be considered (e.g. `11:22:33:44:55:66`) and 606 + any content after the MAC address is ignored. 607 + ''; 608 + }; 609 + 610 + ignoreBroadcastSsid = mkOption { 611 + default = "disabled"; 612 + type = types.enum ["disabled" "empty" "clear"]; 613 + apply = x: 614 + getAttr x { 615 + "disabled" = 0; 616 + "empty" = 1; 617 + "clear" = 2; 618 + }; 619 + description = mdDoc '' 620 + Send empty SSID in beacons and ignore probe request frames that do not 621 + specify full SSID, i.e., require stations to know SSID. Note that this does 622 + not increase security, since your clients will then broadcast the SSID instead, 623 + which can increase congestion. 624 + 625 + - {var}`"disabled"`: Advertise ssid normally. 626 + - {var}`"empty"`: send empty (length=0) SSID in beacon and ignore probe request for broadcast SSID 627 + - {var}`"clear"`: clear SSID (ASCII 0), but keep the original length (this may be required with some 628 + legacy clients that do not support empty SSID) and ignore probe requests for broadcast SSID. Only 629 + use this if empty does not work with your clients. 630 + ''; 631 + }; 632 + 633 + apIsolate = mkOption { 634 + default = false; 635 + type = types.bool; 636 + description = mdDoc '' 637 + Isolate traffic between stations (clients) and prevent them from 638 + communicating with each other. 639 + ''; 640 + }; 641 + 642 + settings = mkOption { 643 + default = {}; 644 + example = { multi_ap = true; }; 645 + type = types.submodule { 646 + freeformType = extraSettingsFormat.type; 647 + }; 648 + description = mdDoc '' 649 + Extra configuration options to put at the end of this BSS's defintion in the 650 + hostapd.conf for the associated interface. To find out which options are global 651 + and which are per-bss you have to read hostapd's source code, which is non-trivial 652 + and not documented otherwise. 653 + 654 + Lists will be converted to multiple definitions of the same key, and booleans to 0/1. 655 + Otherwise, the inputs are not modified or checked for correctness. 656 + ''; 657 + }; 658 + 659 + dynamicConfigScripts = mkOption { 660 + default = {}; 661 + type = types.attrsOf types.path; 662 + example = literalExpression '' 663 + { 664 + exampleDynamicConfig = pkgs.writeShellScript "dynamic-config" ''' 665 + HOSTAPD_CONFIG=$1 666 + # These always exist, but may or may not be used depending on the actual configuration 667 + MAC_ALLOW_FILE=$2 668 + MAC_DENY_FILE=$3 669 + 670 + cat >> "$HOSTAPD_CONFIG" << EOF 671 + # Add some dynamically generated statements here 672 + EOF 673 + '''; 674 + } 675 + ''; 676 + description = mdDoc '' 677 + All of these scripts will be executed in lexicographical order before hostapd 678 + is started, right after the bss segment was generated and may dynamically 679 + append bss options to the generated configuration file. 680 + 681 + The first argument will point to the configuration file that you may append to. 682 + The second and third argument will point to this BSS's MAC allow and MAC deny file respectively. 683 + ''; 684 + }; 685 + 686 + #### IEEE 802.11i (WPA) configuration 687 + 688 + authentication = { 689 + mode = mkOption { 690 + default = "wpa3-sae"; 691 + type = types.enum ["none" "wpa2-sha256" "wpa3-sae-transition" "wpa3-sae"]; 692 + description = mdDoc '' 693 + Selects the authentication mode for this AP. 694 + 695 + - {var}`"none"`: Don't configure any authentication. This will disable wpa alltogether 696 + and create an open AP. Use {option}`settings` together with this option if you 697 + want to configure the authentication manually. Any password options will still be 698 + effective, if set. 699 + - {var}`"wpa2-sha256"`: WPA2-Personal using SHA256 (IEEE 802.11i/RSN). Passwords are set 700 + using {option}`wpaPassword` or preferably by {option}`wpaPasswordFile` or {option}`wpaPskFile`. 701 + - {var}`"wpa3-sae-transition"`: Use WPA3-Personal (SAE) if possible, otherwise fallback 702 + to WPA2-SHA256. Only use if necessary and switch to the newer WPA3-SAE when possible. 703 + You will have to specify both {option}`wpaPassword` and {option}`saePasswords` (or one of their alternatives). 704 + - {var}`"wpa3-sae"`: Use WPA3-Personal (SAE). This is currently the recommended way to 705 + setup a secured WiFi AP (as of March 2023) and therefore the default. Passwords are set 706 + using either {option}`saePasswords` or preferably {option}`saePasswordsFile`. 707 + ''; 708 + }; 709 + 710 + pairwiseCiphers = mkOption { 711 + default = ["CCMP"]; 712 + example = ["CCMP-256" "GCMP-256"]; 713 + type = types.listOf types.str; 714 + description = mdDoc '' 715 + Set of accepted cipher suites (encryption algorithms) for pairwise keys (unicast packets). 716 + By default this allows just CCMP, which is the only commonly supported secure option. 717 + Use {option}`enableRecommendedPairwiseCiphers` to also enable newer recommended ciphers. 78 718 79 - noScan = mkOption { 80 - type = types.bool; 81 - default = false; 82 - description = lib.mdDoc '' 83 - Do not scan for overlapping BSSs in HT40+/- mode. 84 - Caution: turning this on will violate regulatory requirements! 85 - ''; 86 - }; 719 + Please refer to the hostapd documentation for allowed values. Generally, only 720 + CCMP or GCMP modes should be considered safe options. Most devices support CCMP while 721 + GCMP is often only available with devices supporting WiFi 5 (IEEE 802.11ac) or higher. 722 + ''; 723 + }; 87 724 88 - driver = mkOption { 89 - default = "nl80211"; 90 - example = "hostapd"; 91 - type = types.str; 92 - description = lib.mdDoc '' 93 - Which driver {command}`hostapd` will use. 94 - Most applications will probably use the default. 95 - ''; 96 - }; 725 + enableRecommendedPairwiseCiphers = mkOption { 726 + default = false; 727 + example = true; 728 + type = types.bool; 729 + description = mdDoc '' 730 + Additionally enable the recommended set of pairwise ciphers. 731 + This enables newer secure ciphers, additionally to those defined in {option}`pairwiseCiphers`. 732 + You will have to test whether your hardware supports these by trial-and-error, because 733 + even if `iw list` indicates hardware support, your driver might not expose it. 734 + 735 + Beware {command}`hostapd` will most likely not return a useful error message in case 736 + this is enabled despite the driver or hardware not supporting the newer ciphers. 737 + Look out for messages like `Failed to set beacon parameters`. 738 + ''; 739 + }; 740 + 741 + wpaPassword = mkOption { 742 + default = null; 743 + example = "a flakey password"; 744 + type = types.nullOr types.str; 745 + description = mdDoc '' 746 + Sets the password for WPA-PSK that will be converted to the pre-shared key. 747 + The password length must be in the range [8, 63] characters. While some devices 748 + may allow arbitrary characters (such as UTF-8) to be used, but the standard specifies 749 + that each character in the passphrase must be an ASCII character in the range [0x20, 0x7e] 750 + (IEEE Std. 802.11i-2004, Annex H.4.1). Use emojis at your own risk. 97 751 98 - ssid = mkOption { 99 - default = config.system.nixos.distroId; 100 - defaultText = literalExpression "config.system.nixos.distroId"; 101 - example = "mySpecialSSID"; 102 - type = types.str; 103 - description = lib.mdDoc "SSID to be used in IEEE 802.11 management frames."; 104 - }; 752 + Not used when {option}`mode` is {var}`"wpa3-sae"`. 105 753 106 - hwMode = mkOption { 107 - default = "g"; 108 - type = types.enum [ "a" "b" "g" ]; 109 - description = lib.mdDoc '' 110 - Operation mode. 111 - (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g). 112 - ''; 113 - }; 754 + Warning: This password will get put into a world-readable file in the Nix store! 755 + Using {option}`wpaPasswordFile` or {option}`wpaPskFile` instead is recommended. 756 + ''; 757 + }; 114 758 115 - channel = mkOption { 116 - default = 7; 117 - example = 11; 118 - type = types.int; 119 - description = lib.mdDoc '' 120 - Channel number (IEEE 802.11) 121 - Please note that some drivers do not use this value from 122 - {command}`hostapd` and the channel will need to be configured 123 - separately with {command}`iwconfig`. 124 - ''; 125 - }; 759 + wpaPasswordFile = mkOption { 760 + default = null; 761 + type = types.nullOr types.path; 762 + description = mdDoc '' 763 + Sets the password for WPA-PSK. Follows the same rules as {option}`wpaPassword`, 764 + but reads the password from the given file to prevent the password from being 765 + put into the Nix store. 126 766 127 - group = mkOption { 128 - default = "wheel"; 129 - example = "network"; 130 - type = types.str; 131 - description = lib.mdDoc '' 132 - Members of this group can control {command}`hostapd`. 133 - ''; 134 - }; 767 + Not used when {option}`mode` is {var}`"wpa3-sae"`. 768 + ''; 769 + }; 135 770 136 - wpa = mkOption { 137 - type = types.bool; 138 - default = true; 139 - description = lib.mdDoc '' 140 - Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point. 141 - ''; 142 - }; 771 + wpaPskFile = mkOption { 772 + default = null; 773 + type = types.nullOr types.path; 774 + description = mdDoc '' 775 + Sets the password(s) for WPA-PSK. Similar to {option}`wpaPasswordFile`, 776 + but additionally allows specifying multiple passwords, and some other options. 143 777 144 - wpaPassphrase = mkOption { 145 - default = "my_sekret"; 146 - example = "any_64_char_string"; 147 - type = types.str; 148 - description = lib.mdDoc '' 149 - WPA-PSK (pre-shared-key) passphrase. Clients will need this 150 - passphrase to associate with this access point. 151 - Warning: This passphrase will get put into a world-readable file in 152 - the Nix store! 153 - ''; 154 - }; 778 + Each line, except for empty lines and lines starting with #, must contain a 779 + MAC address and either a 64-hex-digit PSK or a password separated with a space. 780 + The password must follow the same rules as outlined in {option}`wpaPassword`. 781 + The special MAC address `00:00:00:00:00:00` can be used to configure PSKs 782 + that any client can use. 155 783 156 - logLevel = mkOption { 157 - default = 2; 158 - type = types.int; 159 - description = lib.mdDoc '' 160 - Levels (minimum value for logged events): 161 - 0 = verbose debugging 162 - 1 = debugging 163 - 2 = informational messages 164 - 3 = notification 165 - 4 = warning 166 - ''; 167 - }; 784 + An optional key identifier can be added by prefixing the line with `keyid=<keyid_string>` 785 + An optional VLAN ID can be specified by prefixing the line with `vlanid=<VLAN ID>`. 786 + An optional WPS tag can be added by prefixing the line with `wps=<0/1>` (default: 0). 787 + Any matching entry with that tag will be used when generating a PSK for a WPS Enrollee 788 + instead of generating a new random per-Enrollee PSK. 789 + 790 + Not used when {option}`mode` is {var}`"wpa3-sae"`. 791 + ''; 792 + }; 793 + 794 + saePasswords = mkOption { 795 + default = []; 796 + example = literalExpression '' 797 + [ 798 + # Any client may use these passwords 799 + { password = "Wi-Figure it out"; } 800 + { password = "second password for everyone"; mac = "ff:ff:ff:ff:ff:ff"; } 801 + 802 + # Only the client with MAC-address 11:22:33:44:55:66 can use this password 803 + { password = "sekret pazzword"; mac = "11:22:33:44:55:66"; } 804 + ] 805 + ''; 806 + description = mdDoc '' 807 + Sets allowed passwords for WPA3-SAE. 808 + 809 + The last matching (based on peer MAC address and identifier) entry is used to 810 + select which password to use. An empty string has the special meaning of 811 + removing all previously added entries. 812 + 813 + Warning: These entries will get put into a world-readable file in 814 + the Nix store! Using {option}`saePasswordFile` instead is recommended. 815 + 816 + Not used when {option}`mode` is {var}`"wpa2-sha256"`. 817 + ''; 818 + type = types.listOf (types.submodule { 819 + options = { 820 + password = mkOption { 821 + example = "a flakey password"; 822 + type = types.str; 823 + description = mdDoc '' 824 + The password for this entry. SAE technically imposes no restrictions on 825 + password length or character set. But due to limitations of {command}`hostapd`'s 826 + config file format, a true newline character cannot be parsed. 827 + 828 + Warning: This password will get put into a world-readable file in 829 + the Nix store! Using {option}`wpaPasswordFile` or {option}`wpaPskFile` is recommended. 830 + ''; 831 + }; 832 + 833 + mac = mkOption { 834 + default = null; 835 + example = "11:22:33:44:55:66"; 836 + type = types.nullOr types.str; 837 + description = mdDoc '' 838 + If this attribute is not included, or if is set to the wildcard address (`ff:ff:ff:ff:ff:ff`), 839 + the entry is available for any station (client) to use. If a specific peer MAC address is included, 840 + only a station with that MAC address is allowed to use the entry. 841 + ''; 842 + }; 843 + 844 + vlanid = mkOption { 845 + default = null; 846 + example = 1; 847 + type = types.nullOr types.int; 848 + description = mdDoc "If this attribute is given, all clients using this entry will get tagged with the given VLAN ID."; 849 + }; 850 + 851 + pk = mkOption { 852 + default = null; 853 + example = ""; 854 + type = types.nullOr types.str; 855 + description = mdDoc '' 856 + If this attribute is given, SAE-PK will be enabled for this connection. 857 + This prevents evil-twin attacks, but a public key is required additionally to connect. 858 + (Essentially adds pubkey authentication such that the client can verify identity of the AP) 859 + ''; 860 + }; 861 + 862 + id = mkOption { 863 + default = null; 864 + example = ""; 865 + type = types.nullOr types.str; 866 + description = mdDoc '' 867 + If this attribute is given with non-zero length, it will set the password identifier 868 + for this entry. It can then only be used with that identifier. 869 + ''; 870 + }; 871 + }; 872 + }); 873 + }; 874 + 875 + saePasswordsFile = mkOption { 876 + default = null; 877 + type = types.nullOr types.path; 878 + description = mdDoc '' 879 + Sets the password for WPA3-SAE. Follows the same rules as {option}`saePasswords`, 880 + but reads the entries from the given file to prevent them from being 881 + put into the Nix store. 882 + 883 + One entry per line, empty lines and lines beginning with # will be ignored. 884 + Each line must match the following format, although the order of optional 885 + parameters doesn't matter: 886 + `<password>[|mac=<peer mac>][|vlanid=<VLAN ID>][|pk=<m:ECPrivateKey-base64>][|id=<identifier>]` 887 + 888 + Not used when {option}`mode` is {var}`"wpa2-sha256"`. 889 + ''; 890 + }; 891 + 892 + saeAddToMacAllow = mkOption { 893 + type = types.bool; 894 + default = false; 895 + description = mdDoc '' 896 + If set, all sae password entries that have a non-wildcard MAC associated to 897 + them will additionally be used to populate the MAC allow list. This is 898 + additional to any entries set via {option}`macAllow` or {option}`macAllowFile`. 899 + ''; 900 + }; 901 + }; 902 + 903 + managementFrameProtection = mkOption { 904 + default = "required"; 905 + type = types.enum ["disabled" "optional" "required"]; 906 + apply = x: 907 + getAttr x { 908 + "disabled" = 0; 909 + "optional" = 1; 910 + "required" = 2; 911 + }; 912 + description = mdDoc '' 913 + Management frame protection (MFP) authenticates management frames 914 + to prevent deauthentication (or related) attacks. 915 + 916 + - {var}`"disabled"`: No management frame protection 917 + - {var}`"optional"`: Use MFP if a connection allows it 918 + - {var}`"required"`: Force MFP for all clients 919 + ''; 920 + }; 921 + }; 922 + 923 + config = let 924 + bss = bssSubmod.name; 925 + bssCfg = bssSubmod.config; 926 + 927 + pairwiseCiphers = 928 + concatStringsSep " " (unique (bssCfg.authentication.pairwiseCiphers 929 + ++ optionals bssCfg.authentication.enableRecommendedPairwiseCiphers ["CCMP" "CCMP-256" "GCMP" "GCMP-256"])); 930 + in { 931 + settings = { 932 + ssid = bssCfg.ssid; 933 + utf8_ssid = bssCfg.ssid; 934 + 935 + logger_syslog = mkDefault (-1); 936 + logger_syslog_level = bssCfg.logLevel; 937 + logger_stdout = mkDefault (-1); 938 + logger_stdout_level = bssCfg.logLevel; 939 + ctrl_interface = mkDefault "/run/hostapd"; 940 + ctrl_interface_group = bssCfg.group; 941 + 942 + macaddr_acl = bssCfg.macAcl; 943 + 944 + ignore_broadcast_ssid = bssCfg.ignoreBroadcastSsid; 945 + 946 + # IEEE 802.11i (authentication) related configuration 947 + # Encrypt management frames to protect against deauthentication and similar attacks 948 + ieee80211w = bssCfg.managementFrameProtection; 949 + 950 + # Only allow WPA by default and disable insecure WEP 951 + auth_algs = mkDefault 1; 952 + # Always enable QoS, which is required for 802.11n and above 953 + wmm_enabled = mkDefault true; 954 + ap_isolate = bssCfg.apIsolate; 955 + 956 + sae_password = flip map bssCfg.authentication.saePasswords ( 957 + entry: 958 + entry.password 959 + + optionalString (entry.mac != null) "|mac=${entry.mac}" 960 + + optionalString (entry.vlanid != null) "|vlanid=${toString entry.vlanid}" 961 + + optionalString (entry.pk != null) "|pk=${entry.pk}" 962 + + optionalString (entry.id != null) "|id=${entry.id}" 963 + ); 964 + } // optionalAttrs (bssCfg.bssid != null) { 965 + bssid = bssCfg.bssid; 966 + } // optionalAttrs (bssCfg.macAllow != [] || bssCfg.macAllowFile != null || bssCfg.authentication.saeAddToMacAllow) { 967 + accept_mac_file = "/run/hostapd/${bss}.mac.allow"; 968 + } // optionalAttrs (bssCfg.macDeny != [] || bssCfg.macDenyFile != null) { 969 + deny_mac_file = "/run/hostapd/${bss}.mac.deny"; 970 + } // optionalAttrs (bssCfg.authentication.mode == "none") { 971 + wpa = mkDefault 0; 972 + } // optionalAttrs (bssCfg.authentication.mode == "wpa3-sae") { 973 + wpa = 2; 974 + wpa_key_mgmt = "SAE"; 975 + # Derive PWE using both hunting-and-pecking loop and hash-to-element 976 + sae_pwe = 2; 977 + # Prevent downgrade attacks by indicating to clients that they should 978 + # disable any transition modes from now on. 979 + transition_disable = "0x01"; 980 + } // optionalAttrs (bssCfg.authentication.mode == "wpa3-sae-transition") { 981 + wpa = 2; 982 + wpa_key_mgmt = "WPA-PSK-SHA256 SAE"; 983 + } // optionalAttrs (bssCfg.authentication.mode == "wpa2-sha256") { 984 + wpa = 2; 985 + wpa_key_mgmt = "WPA-PSK-SHA256"; 986 + } // optionalAttrs (bssCfg.authentication.mode != "none") { 987 + wpa_pairwise = pairwiseCiphers; 988 + rsn_pairwise = pairwiseCiphers; 989 + } // optionalAttrs (bssCfg.authentication.wpaPassword != null) { 990 + wpa_passphrase = bssCfg.authentication.wpaPassword; 991 + } // optionalAttrs (bssCfg.authentication.wpaPskFile != null) { 992 + wpa_psk_file = bssCfg.authentication.wpaPskFile; 993 + }; 168 994 169 - countryCode = mkOption { 170 - default = null; 171 - example = "US"; 172 - type = with types; nullOr str; 173 - description = lib.mdDoc '' 174 - Country code (ISO/IEC 3166-1). Used to set regulatory domain. 175 - Set as needed to indicate country in which device is operating. 176 - This can limit available channels and transmit power. 177 - These two octets are used as the first two octets of the Country String 178 - (dot11CountryString). 179 - If set this enables IEEE 802.11d. This advertises the countryCode and 180 - the set of allowed channels and transmit power levels based on the 181 - regulatory limits. 182 - ''; 183 - }; 995 + dynamicConfigScripts = let 996 + # All MAC addresses from SAE entries that aren't the wildcard address 997 + saeMacs = filter (mac: mac != null && (toLower mac) != "ff:ff:ff:ff:ff:ff") (map (x: x.mac) bssCfg.authentication.saePasswords); 998 + in { 999 + "20-addMacAllow" = mkIf (bssCfg.macAllow != []) (pkgs.writeShellScript "add-mac-allow" '' 1000 + MAC_ALLOW_FILE=$2 1001 + cat >> "$MAC_ALLOW_FILE" <<EOF 1002 + ${concatStringsSep "\n" bssCfg.macAllow} 1003 + EOF 1004 + ''); 1005 + "20-addMacAllowFile" = mkIf (bssCfg.macAllowFile != null) (pkgs.writeShellScript "add-mac-allow-file" '' 1006 + MAC_ALLOW_FILE=$2 1007 + grep -Eo '^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})' ${escapeShellArg bssCfg.macAllowFile} >> "$MAC_ALLOW_FILE" 1008 + ''); 1009 + "20-addMacAllowFromSae" = mkIf (bssCfg.authentication.saeAddToMacAllow && saeMacs != []) (pkgs.writeShellScript "add-mac-allow-from-sae" '' 1010 + MAC_ALLOW_FILE=$2 1011 + cat >> "$MAC_ALLOW_FILE" <<EOF 1012 + ${concatStringsSep "\n" saeMacs} 1013 + EOF 1014 + ''); 1015 + # Populate mac allow list from saePasswordsFile 1016 + # (filter for lines with mac=; exclude commented lines; filter for real mac-addresses; strip mac=) 1017 + "20-addMacAllowFromSaeFile" = mkIf (bssCfg.authentication.saeAddToMacAllow && bssCfg.authentication.saePasswordsFile != null) (pkgs.writeShellScript "add-mac-allow-from-sae-file" '' 1018 + MAC_ALLOW_FILE=$2 1019 + grep mac= ${escapeShellArg bssCfg.authentication.saePasswordsFile} \ 1020 + | grep -v '\s*#' \ 1021 + | grep -Eo 'mac=([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})' \ 1022 + | sed 's|^mac=||' >> "$MAC_ALLOW_FILE" 1023 + ''); 1024 + "20-addMacDeny" = mkIf (bssCfg.macDeny != []) (pkgs.writeShellScript "add-mac-deny" '' 1025 + MAC_DENY_FILE=$3 1026 + cat >> "$MAC_DENY_FILE" <<EOF 1027 + ${concatStringsSep "\n" bssCfg.macDeny} 1028 + EOF 1029 + ''); 1030 + "20-addMacDenyFile" = mkIf (bssCfg.macDenyFile != null) (pkgs.writeShellScript "add-mac-deny-file" '' 1031 + MAC_DENY_FILE=$3 1032 + grep -Eo '^([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2})' ${escapeShellArg bssCfg.macDenyFile} >> "$MAC_DENY_FILE" 1033 + ''); 1034 + # Add wpa_passphrase from file 1035 + "20-wpaPasswordFile" = mkIf (bssCfg.authentication.wpaPasswordFile != null) (pkgs.writeShellScript "wpa-password-file" '' 1036 + HOSTAPD_CONFIG_FILE=$1 1037 + cat >> "$HOSTAPD_CONFIG_FILE" <<EOF 1038 + wpa_passphrase=$(cat ${escapeShellArg bssCfg.authentication.wpaPasswordFile}) 1039 + EOF 1040 + ''); 1041 + # Add sae passwords from file 1042 + "20-saePasswordsFile" = mkIf (bssCfg.authentication.saePasswordsFile != null) (pkgs.writeShellScript "sae-passwords-file" '' 1043 + HOSTAPD_CONFIG_FILE=$1 1044 + grep -v '\s*#' ${escapeShellArg bssCfg.authentication.saePasswordsFile} \ 1045 + | sed 's/^/sae_password=/' >> "$HOSTAPD_CONFIG_FILE" 1046 + ''); 1047 + }; 1048 + }; 1049 + })); 1050 + }; 1051 + }; 184 1052 185 - extraConfig = mkOption { 186 - default = ""; 187 - example = '' 188 - auth_algo=0 189 - ieee80211n=1 190 - ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40] 191 - ''; 192 - type = types.lines; 193 - description = lib.mdDoc "Extra configuration options to put in hostapd.conf."; 1053 + config.settings = let 1054 + radio = radioSubmod.name; 1055 + radioCfg = radioSubmod.config; 1056 + in { 1057 + driver = radioCfg.driver; 1058 + hw_mode = { 1059 + "2g" = "g"; 1060 + "5g" = "a"; 1061 + "6g" = "a"; 1062 + "60g" = "ad"; 1063 + }.${radioCfg.band}; 1064 + channel = radioCfg.channel; 1065 + noscan = radioCfg.noScan; 1066 + } // optionalAttrs (radioCfg.countryCode != null) { 1067 + country_code = radioCfg.countryCode; 1068 + # IEEE 802.11d: Limit to frequencies allowed in country 1069 + ieee80211d = true; 1070 + # IEEE 802.11h: Enable radar detection and DFS (Dynamic Frequency Selection) 1071 + ieee80211h = true; 1072 + } // optionalAttrs radioCfg.wifi4.enable { 1073 + # IEEE 802.11n (WiFi 4) related configuration 1074 + ieee80211n = true; 1075 + require_ht = radioCfg.wifi4.require; 1076 + ht_capab = concatMapStrings (x: "[${x}]") radioCfg.wifi4.capabilities; 1077 + } // optionalAttrs radioCfg.wifi5.enable { 1078 + # IEEE 802.11ac (WiFi 5) related configuration 1079 + ieee80211ac = true; 1080 + require_vht = radioCfg.wifi5.require; 1081 + vht_oper_chwidth = radioCfg.wifi5.operatingChannelWidth; 1082 + vht_capab = concatMapStrings (x: "[${x}]") radioCfg.wifi5.capabilities; 1083 + } // optionalAttrs radioCfg.wifi6.enable { 1084 + # IEEE 802.11ax (WiFi 6) related configuration 1085 + ieee80211ax = true; 1086 + require_he = mkIf radioCfg.wifi6.require true; 1087 + he_oper_chwidth = radioCfg.wifi6.operatingChannelWidth; 1088 + he_su_beamformer = radioCfg.wifi6.singleUserBeamformer; 1089 + he_su_beamformee = radioCfg.wifi6.singleUserBeamformee; 1090 + he_mu_beamformer = radioCfg.wifi6.multiUserBeamformer; 1091 + } // optionalAttrs radioCfg.wifi7.enable { 1092 + # IEEE 802.11be (WiFi 7) related configuration 1093 + ieee80211be = true; 1094 + eht_oper_chwidth = radioCfg.wifi7.operatingChannelWidth; 1095 + eht_su_beamformer = radioCfg.wifi7.singleUserBeamformer; 1096 + eht_su_beamformee = radioCfg.wifi7.singleUserBeamformee; 1097 + eht_mu_beamformer = radioCfg.wifi7.multiUserBeamformer; 1098 + }; 1099 + })); 194 1100 }; 195 1101 }; 196 1102 }; 197 1103 1104 + imports = let 1105 + renamedOptionMessage = message: '' 1106 + ${message} 1107 + Refer to the documentation of `services.hostapd.radios` for an example and more information. 1108 + ''; 1109 + in [ 1110 + (mkRemovedOptionModule ["services" "hostapd" "interface"] 1111 + (renamedOptionMessage "All other options for this interface are now set via `services.hostapd.radios.«interface».*`.")) 198 1112 199 - ###### implementation 1113 + (mkRemovedOptionModule ["services" "hostapd" "driver"] 1114 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».driver`.")) 1115 + (mkRemovedOptionModule ["services" "hostapd" "noScan"] 1116 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».noScan`.")) 1117 + (mkRemovedOptionModule ["services" "hostapd" "countryCode"] 1118 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».countryCode`.")) 1119 + (mkRemovedOptionModule ["services" "hostapd" "hwMode"] 1120 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».band`.")) 1121 + (mkRemovedOptionModule ["services" "hostapd" "channel"] 1122 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».channel`.")) 1123 + (mkRemovedOptionModule ["services" "hostapd" "extraConfig"] 1124 + (renamedOptionMessage '' 1125 + It has been replaced by `services.hostapd.radios.«interface».settings` and 1126 + `services.hostapd.radios.«interface».networks.«network».settings` respectively 1127 + for per-radio and per-network extra configuration. The module now supports a lot more 1128 + options inherently, so please re-check whether using settings is still necessary.'')) 1129 + 1130 + (mkRemovedOptionModule ["services" "hostapd" "logLevel"] 1131 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».networks.«network».logLevel`.")) 1132 + (mkRemovedOptionModule ["services" "hostapd" "group"] 1133 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».networks.«network».group`.")) 1134 + (mkRemovedOptionModule ["services" "hostapd" "ssid"] 1135 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».networks.«network».ssid`.")) 1136 + 1137 + (mkRemovedOptionModule ["services" "hostapd" "wpa"] 1138 + (renamedOptionMessage "It has been replaced by `services.hostapd.radios.«interface».networks.«network».authentication.mode`.")) 1139 + (mkRemovedOptionModule ["services" "hostapd" "wpaPassphrase"] 1140 + (renamedOptionMessage '' 1141 + It has been replaced by `services.hostapd.radios.«interface».networks.«network».authentication.wpaPassword`. 1142 + While upgrading your config, please consider using the newer SAE authentication scheme 1143 + and one of the new `passwordFile`-like options to avoid putting the password into the world readable nix-store.'')) 1144 + ]; 200 1145 201 1146 config = mkIf cfg.enable { 1147 + assertions = 1148 + [ 1149 + { 1150 + assertion = cfg.radios != {}; 1151 + message = "At least one radio must be configured with hostapd!"; 1152 + } 1153 + ] 1154 + # Radio warnings 1155 + ++ (concatLists (mapAttrsToList ( 1156 + radio: radioCfg: 1157 + [ 1158 + { 1159 + assertion = radioCfg.networks != {}; 1160 + message = "hostapd radio ${radio}: At least one network must be configured!"; 1161 + } 1162 + # XXX: There could be many more useful assertions about (band == xy) -> ensure other required settings. 1163 + # see https://github.com/openwrt/openwrt/blob/539cb5389d9514c99ec1f87bd4465f77c7ed9b93/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh#L158 1164 + { 1165 + assertion = length (filter (bss: bss == radio) (attrNames radioCfg.networks)) == 1; 1166 + message = ''hostapd radio ${radio}: Exactly one network must be named like the radio, for reasons internal to hostapd.''; 1167 + } 1168 + { 1169 + assertion = (radioCfg.wifi4.enable && builtins.elem "HT40-" radioCfg.wifi4.capabilities) -> radioCfg.channel != 0; 1170 + message = ''hostapd radio ${radio}: using ACS (channel = 0) together with HT40- (wifi4.capabilities) is unsupported by hostapd''; 1171 + } 1172 + ] 1173 + # BSS warnings 1174 + ++ (concatLists (mapAttrsToList (bss: bssCfg: let 1175 + auth = bssCfg.authentication; 1176 + countWpaPasswordDefinitions = count (x: x != null) [ 1177 + auth.wpaPassword 1178 + auth.wpaPasswordFile 1179 + auth.wpaPskFile 1180 + ]; 1181 + in [ 1182 + { 1183 + assertion = hasPrefix radio bss; 1184 + message = "hostapd radio ${radio} bss ${bss}: The bss (network) name ${bss} is invalid. It must be prefixed by the radio name for reasons internal to hostapd. A valid name would be e.g. ${radio}, ${radio}-1, ..."; 1185 + } 1186 + { 1187 + assertion = (length (attrNames radioCfg.networks) > 1) -> (bssCfg.bssid != null); 1188 + message = ''hostapd radio ${radio} bss ${bss}: bssid must be specified manually (for now) since this radio uses multiple BSS.''; 1189 + } 1190 + { 1191 + assertion = auth.mode == "wpa3-sae" -> bssCfg.managementFrameProtection == 2; 1192 + message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE which requires managementFrameProtection="required"''; 1193 + } 1194 + { 1195 + assertion = auth.mode == "wpa3-sae-transition" -> bssCfg.managementFrameProtection != 0; 1196 + message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE in transition mode with WPA2-SHA256, which requires managementFrameProtection="optional" or ="required"''; 1197 + } 1198 + { 1199 + assertion = countWpaPasswordDefinitions <= 1; 1200 + message = ''hostapd radio ${radio} bss ${bss}: must use at most one WPA password option (wpaPassword, wpaPasswordFile, wpaPskFile)''; 1201 + } 1202 + { 1203 + assertion = auth.wpaPassword != null -> (stringLength auth.wpaPassword >= 8 && stringLength auth.wpaPassword <= 63); 1204 + message = ''hostapd radio ${radio} bss ${bss}: uses a wpaPassword of invalid length (must be in [8,63]).''; 1205 + } 1206 + { 1207 + assertion = auth.saePasswords == [] || auth.saePasswordsFile == null; 1208 + message = ''hostapd radio ${radio} bss ${bss}: must use only one SAE password option (saePasswords or saePasswordsFile)''; 1209 + } 1210 + { 1211 + assertion = auth.mode == "wpa3-sae" -> (auth.saePasswords != [] || auth.saePasswordsFile != null); 1212 + message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE which requires defining a sae password option''; 1213 + } 1214 + { 1215 + assertion = auth.mode == "wpa3-sae-transition" -> (auth.saePasswords != [] || auth.saePasswordsFile != null) && countWpaPasswordDefinitions == 1; 1216 + message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE in transition mode requires defining both a wpa password option and a sae password option''; 1217 + } 1218 + { 1219 + assertion = auth.mode == "wpa2-sha256" -> countWpaPasswordDefinitions == 1; 1220 + message = ''hostapd radio ${radio} bss ${bss}: uses WPA2-SHA256 which requires defining a wpa password option''; 1221 + } 1222 + ]) 1223 + radioCfg.networks)) 1224 + ) 1225 + cfg.radios)); 202 1226 203 - environment.systemPackages = [ pkgs.hostapd ]; 1227 + environment.systemPackages = [cfg.package]; 204 1228 205 - services.udev.packages = optionals (cfg.countryCode != null) [ pkgs.crda ]; 1229 + services.udev.packages = with pkgs; [crda]; 206 1230 207 - systemd.services.hostapd = 208 - { description = "hostapd wireless AP"; 1231 + systemd.services.hostapd = { 1232 + description = "IEEE 802.11 Host Access-Point Daemon"; 209 1233 210 - path = [ pkgs.hostapd ]; 211 - after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ]; 212 - bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ]; 213 - requiredBy = [ "network-link-${cfg.interface}.service" ]; 214 - wantedBy = [ "multi-user.target" ]; 1234 + path = [cfg.package]; 1235 + after = map (radio: "sys-subsystem-net-devices-${utils.escapeSystemdPath radio}.device") (attrNames cfg.radios); 1236 + bindsTo = map (radio: "sys-subsystem-net-devices-${utils.escapeSystemdPath radio}.device") (attrNames cfg.radios); 1237 + wantedBy = ["multi-user.target"]; 215 1238 216 - serviceConfig = 217 - { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}"; 218 - Restart = "always"; 219 - }; 1239 + # Create merged configuration and acl files for each radio (and their bss's) prior to starting 1240 + preStart = concatStringsSep "\n" (mapAttrsToList makeRadioRuntimeFiles cfg.radios); 1241 + 1242 + serviceConfig = { 1243 + ExecStart = "${cfg.package}/bin/hostapd ${concatStringsSep " " runtimeConfigFiles}"; 1244 + Restart = "always"; 1245 + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 1246 + RuntimeDirectory = "hostapd"; 1247 + 1248 + # Hardening 1249 + LockPersonality = true; 1250 + MemoryDenyWriteExecute = true; 1251 + DevicePolicy = "closed"; 1252 + DeviceAllow = "/dev/rfkill rw"; 1253 + NoNewPrivileges = true; 1254 + PrivateUsers = false; # hostapd requires true root access. 1255 + PrivateTmp = true; 1256 + ProtectClock = true; 1257 + ProtectControlGroups = true; 1258 + ProtectHome = true; 1259 + ProtectHostname = true; 1260 + ProtectKernelLogs = true; 1261 + ProtectKernelModules = true; 1262 + ProtectKernelTunables = true; 1263 + ProtectProc = "invisible"; 1264 + ProcSubset = "pid"; 1265 + ProtectSystem = "strict"; 1266 + RestrictAddressFamilies = [ 1267 + "AF_INET" 1268 + "AF_INET6" 1269 + "AF_NETLINK" 1270 + "AF_UNIX" 1271 + ]; 1272 + RestrictNamespaces = true; 1273 + RestrictRealtime = true; 1274 + RestrictSUIDSGID = true; 1275 + SystemCallArchitectures = "native"; 1276 + SystemCallFilter = [ 1277 + "@system-service" 1278 + "~@privileged" 1279 + "@chown" 1280 + ]; 1281 + UMask = "0077"; 220 1282 }; 1283 + }; 221 1284 }; 222 1285 }
+168 -55
nixos/tests/wpa_supplicant.nix
··· 2 2 { 3 3 name = "wpa_supplicant"; 4 4 meta = with lib.maintainers; { 5 - maintainers = [ rnhmjoj ]; 5 + maintainers = [ oddlama rnhmjoj ]; 6 6 }; 7 7 8 - nodes.machine = { ... }: { 9 - imports = [ ../modules/profiles/minimal.nix ]; 8 + nodes = let 9 + machineWithHostapd = extraConfigModule: { ... }: { 10 + imports = [ 11 + ../modules/profiles/minimal.nix 12 + extraConfigModule 13 + ]; 10 14 11 - # add a virtual wlan interface 12 - boot.kernelModules = [ "mac80211_hwsim" ]; 15 + # add a virtual wlan interface 16 + boot.kernelModules = [ "mac80211_hwsim" ]; 13 17 14 - # wireless access point 15 - services.hostapd = { 16 - enable = true; 17 - wpa = true; 18 - interface = "wlan0"; 19 - ssid = "nixos-test"; 20 - wpaPassphrase = "reproducibility"; 18 + # wireless access point 19 + services.hostapd = { 20 + enable = true; 21 + radios.wlan0 = { 22 + band = "2g"; 23 + countryCode = "US"; 24 + networks = { 25 + wlan0 = { 26 + ssid = "nixos-test-sae"; 27 + authentication = { 28 + mode = "wpa3-sae"; 29 + saePasswords = [ { password = "reproducibility"; } ]; 30 + }; 31 + bssid = "02:00:00:00:00:00"; 32 + }; 33 + wlan0-1 = { 34 + ssid = "nixos-test-mixed"; 35 + authentication = { 36 + mode = "wpa3-sae-transition"; 37 + saePasswordsFile = pkgs.writeText "password" "reproducibility"; 38 + wpaPasswordFile = pkgs.writeText "password" "reproducibility"; 39 + }; 40 + bssid = "02:00:00:00:00:01"; 41 + }; 42 + wlan0-2 = { 43 + ssid = "nixos-test-wpa2"; 44 + authentication = { 45 + mode = "wpa2-sha256"; 46 + wpaPassword = "reproducibility"; 47 + }; 48 + bssid = "02:00:00:00:00:02"; 49 + }; 50 + }; 51 + }; 52 + }; 53 + 54 + # wireless client 55 + networking.wireless = { 56 + # the override is needed because the wifi is 57 + # disabled with mkVMOverride in qemu-vm.nix. 58 + enable = lib.mkOverride 0 true; 59 + userControlled.enable = true; 60 + interfaces = [ "wlan1" ]; 61 + fallbackToWPA2 = lib.mkDefault true; 62 + 63 + # networks will be added on-demand below for the specific 64 + # network that should be tested 65 + 66 + # secrets 67 + environmentFile = pkgs.writeText "wpa-secrets" '' 68 + PSK_NIXOS_TEST="reproducibility" 69 + ''; 70 + }; 21 71 }; 72 + in { 73 + basic = { ... }: { 74 + imports = [ ../modules/profiles/minimal.nix ]; 22 75 23 - # wireless client 24 - networking.wireless = { 25 - # the override is needed because the wifi is 26 - # disabled with mkVMOverride in qemu-vm.nix. 27 - enable = lib.mkOverride 0 true; 28 - userControlled.enable = true; 29 - interfaces = [ "wlan1" ]; 30 - fallbackToWPA2 = true; 76 + # add a virtual wlan interface 77 + boot.kernelModules = [ "mac80211_hwsim" ]; 31 78 32 - networks = { 33 - # test WPA2 fallback 34 - mixed-wpa = { 35 - psk = "password"; 36 - authProtocols = [ "WPA-PSK" "SAE" ]; 79 + # wireless client 80 + networking.wireless = { 81 + # the override is needed because the wifi is 82 + # disabled with mkVMOverride in qemu-vm.nix. 83 + enable = lib.mkOverride 0 true; 84 + userControlled.enable = true; 85 + interfaces = [ "wlan1" ]; 86 + fallbackToWPA2 = true; 87 + 88 + networks = { 89 + # test WPA2 fallback 90 + mixed-wpa = { 91 + psk = "password"; 92 + authProtocols = [ "WPA-PSK" "SAE" ]; 93 + }; 94 + sae-only = { 95 + psk = "password"; 96 + authProtocols = [ "SAE" ]; 97 + }; 98 + 99 + # secrets substitution test cases 100 + test1.psk = "@PSK_VALID@"; # should be replaced 101 + test2.psk = "@PSK_SPECIAL@"; # should be replaced 102 + test3.psk = "@PSK_MISSING@"; # should not be replaced 103 + test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced 37 104 }; 38 - sae-only = { 39 - psk = "password"; 105 + 106 + # secrets 107 + environmentFile = pkgs.writeText "wpa-secrets" '' 108 + PSK_VALID="S0m3BadP4ssw0rd"; 109 + # taken from https://github.com/minimaxir/big-list-of-naughty-strings 110 + PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~"; 111 + ''; 112 + }; 113 + }; 114 + 115 + # Test connecting to the SAE-only hotspot using SAE 116 + machineSae = machineWithHostapd { 117 + networking.wireless = { 118 + fallbackToWPA2 = false; 119 + networks.nixos-test-sae = { 120 + psk = "@PSK_NIXOS_TEST@"; 40 121 authProtocols = [ "SAE" ]; 41 122 }; 123 + }; 124 + }; 42 125 43 - # test network 44 - nixos-test.psk = "@PSK_NIXOS_TEST@"; 126 + # Test connecting to the SAE and WPA2 mixed hotspot using SAE 127 + machineMixedUsingSae = machineWithHostapd { 128 + networking.wireless = { 129 + fallbackToWPA2 = false; 130 + networks.nixos-test-mixed = { 131 + psk = "@PSK_NIXOS_TEST@"; 132 + authProtocols = [ "SAE" ]; 133 + }; 134 + }; 135 + }; 45 136 46 - # secrets substitution test cases 47 - test1.psk = "@PSK_VALID@"; # should be replaced 48 - test2.psk = "@PSK_SPECIAL@"; # should be replaced 49 - test3.psk = "@PSK_MISSING@"; # should not be replaced 50 - test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced 137 + # Test connecting to the SAE and WPA2 mixed hotspot using WPA2 138 + machineMixedUsingWpa2 = machineWithHostapd { 139 + networking.wireless = { 140 + fallbackToWPA2 = true; 141 + networks.nixos-test-mixed = { 142 + psk = "@PSK_NIXOS_TEST@"; 143 + authProtocols = [ "WPA-PSK-SHA256" ]; 144 + }; 51 145 }; 146 + }; 52 147 53 - # secrets 54 - environmentFile = pkgs.writeText "wpa-secrets" '' 55 - PSK_NIXOS_TEST="reproducibility" 56 - PSK_VALID="S0m3BadP4ssw0rd"; 57 - # taken from https://github.com/minimaxir/big-list-of-naughty-strings 58 - PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~"; 59 - ''; 148 + # Test connecting to the WPA2 legacy hotspot using WPA2 149 + machineWpa2 = machineWithHostapd { 150 + networking.wireless = { 151 + fallbackToWPA2 = true; 152 + networks.nixos-test-wpa2 = { 153 + psk = "@PSK_NIXOS_TEST@"; 154 + authProtocols = [ "WPA-PSK-SHA256" ]; 155 + }; 156 + }; 60 157 }; 61 - 62 158 }; 63 159 64 160 testScript = ··· 66 162 config_file = "/run/wpa_supplicant/wpa_supplicant.conf" 67 163 68 164 with subtest("Configuration file is inaccessible to other users"): 69 - machine.wait_for_file(config_file) 70 - machine.fail(f"sudo -u nobody ls {config_file}") 165 + basic.wait_for_file(config_file) 166 + basic.fail(f"sudo -u nobody ls {config_file}") 71 167 72 168 with subtest("Secrets variables have been substituted"): 73 - machine.fail(f"grep -q @PSK_VALID@ {config_file}") 74 - machine.fail(f"grep -q @PSK_SPECIAL@ {config_file}") 75 - machine.succeed(f"grep -q @PSK_MISSING@ {config_file}") 76 - machine.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}") 169 + basic.fail(f"grep -q @PSK_VALID@ {config_file}") 170 + basic.fail(f"grep -q @PSK_SPECIAL@ {config_file}") 171 + basic.succeed(f"grep -q @PSK_MISSING@ {config_file}") 172 + basic.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}") 77 173 78 174 with subtest("WPA2 fallbacks have been generated"): 79 - assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1 80 - assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2 175 + assert int(basic.succeed(f"grep -c sae-only {config_file}")) == 1 176 + assert int(basic.succeed(f"grep -c mixed-wpa {config_file}")) == 2 81 177 82 178 # save file for manual inspection 83 - machine.copy_from_vm(config_file) 179 + basic.copy_from_vm(config_file) 84 180 85 181 with subtest("Daemon is running and accepting connections"): 86 - machine.wait_for_unit("wpa_supplicant-wlan1.service") 87 - status = machine.succeed("wpa_cli -i wlan1 status") 182 + basic.wait_for_unit("wpa_supplicant-wlan1.service") 183 + status = basic.succeed("wpa_cli -i wlan1 status") 88 184 assert "Failed to connect" not in status, \ 89 185 "Failed to connect to the daemon" 90 186 91 - with subtest("Daemon can connect to the access point"): 92 - machine.wait_until_succeeds( 187 + machineSae.wait_for_unit("hostapd.service") 188 + machineSae.copy_from_vm("/run/hostapd/wlan0.hostapd.conf") 189 + with subtest("Daemon can connect to the SAE access point using SAE"): 190 + machineSae.wait_until_succeeds( 191 + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" 192 + ) 193 + 194 + with subtest("Daemon can connect to the SAE and WPA2 mixed access point using SAE"): 195 + machineMixedUsingSae.wait_until_succeeds( 196 + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" 197 + ) 198 + 199 + with subtest("Daemon can connect to the SAE and WPA2 mixed access point using WPA2"): 200 + machineMixedUsingWpa2.wait_until_succeeds( 201 + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" 202 + ) 203 + 204 + with subtest("Daemon can connect to the WPA2 access point using WPA2"): 205 + machineWpa2.wait_until_succeeds( 93 206 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" 94 207 ) 95 208 '';
+39 -12
pkgs/os-specific/linux/hostapd/default.nix
··· 23 23 24 24 outputs = [ "out" "man" ]; 25 25 26 + # Based on hostapd's defconfig. Only differences are tracked. 26 27 extraConfig = '' 28 + # Use epoll(7) instead of select(2) on linux 29 + CONFIG_ELOOP_EPOLL=y 30 + 31 + # Drivers 27 32 CONFIG_DRIVER_WIRED=y 28 - CONFIG_LIBNL32=y 33 + CONFIG_DRIVER_NONE=y 34 + 35 + # Integrated EAP server 29 36 CONFIG_EAP_SIM=y 30 37 CONFIG_EAP_AKA=y 31 38 CONFIG_EAP_AKA_PRIME=y 32 39 CONFIG_EAP_PAX=y 40 + CONFIG_EAP_PSK=y 33 41 CONFIG_EAP_PWD=y 34 42 CONFIG_EAP_SAKE=y 35 43 CONFIG_EAP_GPSK=y ··· 38 46 CONFIG_EAP_IKEV2=y 39 47 CONFIG_EAP_TNC=y 40 48 CONFIG_EAP_EKE=y 49 + 50 + CONFIG_TLS=openssl 51 + CONFIG_TLSV11=y 52 + CONFIG_TLSV12=y 53 + 54 + CONFIG_SAE=y 55 + CONFIG_SAE_PK=y 56 + 57 + CONFIG_OWE=y 58 + CONFIG_OCV=y 59 + 60 + # TKIP is considered insecure and upstream support will be removed in the future 61 + CONFIG_NO_TKIP=y 62 + 63 + # Misc 41 64 CONFIG_RADIUS_SERVER=y 65 + CONFIG_FULL_DYNAMIC_VLAN=y 66 + CONFIG_VLAN_NETLINK=y 67 + CONFIG_GETRANDOM=y 68 + CONFIG_INTERWORKING=y 69 + CONFIG_HS20=y 70 + CONFIG_FST=y 71 + CONFIG_FST_TEST=y 72 + CONFIG_ACS=y 73 + CONFIG_WNM=y 74 + CONFIG_MBO=y 75 + 42 76 CONFIG_IEEE80211R=y 77 + CONFIG_IEEE80211W=y 43 78 CONFIG_IEEE80211N=y 44 79 CONFIG_IEEE80211AC=y 45 80 CONFIG_IEEE80211AX=y 46 - CONFIG_FULL_DYNAMIC_VLAN=y 47 - CONFIG_VLAN_NETLINK=y 48 - CONFIG_TLS=openssl 49 - CONFIG_TLSV11=y 50 - CONFIG_TLSV12=y 51 - CONFIG_INTERNETWORKING=y 52 - CONFIG_HS20=y 53 - CONFIG_ACS=y 54 - CONFIG_GETRANDOM=y 55 - CONFIG_SAE=y 56 81 '' + lib.optionalString (sqlite != null) '' 57 82 CONFIG_SQLITE=y 58 83 ''; 59 84 85 + passAsFile = [ "extraConfig" ]; 86 + 60 87 configurePhase = '' 61 88 cd hostapd 62 89 cp -v defconfig .config 63 - echo "$extraConfig" >> .config 90 + cat $extraConfigPath >> .config 64 91 cat -n .config 65 92 substituteInPlace Makefile --replace /usr/local $out 66 93 export NIX_CFLAGS_COMPILE="$NIX_CFLAGS_COMPILE $(pkg-config --cflags libnl-3.0)"
+1
pkgs/os-specific/linux/wpa_supplicant/default.nix
··· 57 57 CONFIG_LIBNL32=y 58 58 CONFIG_OWE=y 59 59 CONFIG_P2P=y 60 + CONFIG_SAE_PK=y 60 61 CONFIG_TDLS=y 61 62 CONFIG_TLS=openssl 62 63 CONFIG_TLSV11=y