diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 0053b51c607c..412ddc6f3a58 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -250,6 +250,8 @@ - `services.gitea` supports sending notifications with sendmail again. To do this, activate the parameter `services.gitea.mailerUseSendmail` and configure SMTP server. +- `networking.wireless.networks.` now has an option to specify SSID, hence allowing duplicated SSID setup. The BSSID option is added along side with this. + - Revamp of the ACME certificate acquisication and renewal process to help scale systems with lots (100+) of certificates. Units and targets have been reshaped to better support more specific dependency propagation and avoid diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 88eaffdc1ee1..8564295551a5 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -37,7 +37,9 @@ let mkWPA2Fallback = opts: opts // { authProtocols = subtractLists wpa3Protocols opts.authProtocols; }; # Networks attrset as a list - networkList = mapAttrsToList (ssid: opts: opts // { inherit ssid; }) cfg.networks; + # We use the ssid from the options, which defaults to the ssid but can be overridden. + # The ssid in attrNames is hence unused here. + networkList = attrValues cfg.networks; # List of all networks (normal + generated fallbacks) allNetworks = @@ -89,6 +91,7 @@ let "key_mgmt=NONE" ) ] + ++ optional (opts.bssid != null) "bssid=${opts.bssid}" ++ optional opts.hidden "scan_ssid=1" ++ optional (pskString != null) "psk=${pskString}" ++ optionals (opts.auth != null) (filter (x: x != "") (splitString "\n" opts.auth)) @@ -280,170 +283,195 @@ in networks = mkOption { type = types.attrsOf ( - types.submodule { - options = { - psk = mkOption { - type = types.nullOr (types.strMatching "[[:print:]]{8,63}"); - default = null; - description = '' - The network's pre-shared key in plaintext defaulting - to being a network without any authentication. + types.submodule ( + { name, ... }: + { + options = { + ssid = mkOption { + type = types.str; + default = name; + description = '' + You could use this field to override the network's ssid. + This can be useful to, for example, specify two networks + that share the same SSID but not the same password. + Specifying the BSSID of the network can make two entries of + the same SSID show up as different ones in wpa_cli. + ''; + }; - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Use {var}`pskRaw` with an external - reference to keep it safe. - ::: + bssid = mkOption { + type = types.nullOr types.str; + default = null; + example = "02:00:00:00:00:01"; + description = '' + If set, this network block is used only when associating with + the AP using the configured BSSID. + ''; + }; - ::: {.note} - Mutually exclusive with {var}`pskRaw`. - ::: - ''; - }; + psk = mkOption { + type = types.nullOr (types.strMatching "[[:print:]]{8,63}"); + default = null; + description = '' + The network's pre-shared key in plaintext defaulting + to being a network without any authentication. - pskRaw = mkOption { - type = types.nullOr (types.strMatching "([[:xdigit:]]{64})|(ext:[^=]+)"); - default = null; - example = "ext:name_of_the_secret_here"; - description = '' - Either the raw pre-shared key in hexadecimal format - or the name of the secret (as defined inside - [](#opt-networking.wireless.secretsFile) and prefixed - with `ext:`) containing the network pre-shared key. + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Use {var}`pskRaw` with an external + reference to keep it safe. + ::: - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Always use an external reference. - ::: + ::: {.note} + Mutually exclusive with {var}`pskRaw`. + ::: + ''; + }; - ::: {.note} - The external secret can be either the plaintext - passphrase or the raw pre-shared key. - ::: + pskRaw = mkOption { + type = types.nullOr (types.strMatching "([[:xdigit:]]{64})|(ext:[^=]+)"); + default = null; + example = "ext:name_of_the_secret_here"; + description = '' + Either the raw pre-shared key in hexadecimal format + or the name of the secret (as defined inside + [](#opt-networking.wireless.secretsFile) and prefixed + with `ext:`) containing the network pre-shared key. - ::: {.note} - Mutually exclusive with {var}`psk` and {var}`auth`. - ::: - ''; - }; + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Always use an external reference. + ::: - authProtocols = mkOption { - default = [ - # WPA2 and WPA3 - "WPA-PSK" - "WPA-EAP" - "SAE" - # 802.11r variants of the above - "FT-PSK" - "FT-EAP" - "FT-SAE" - ]; - # The list can be obtained by running this command - # awk ' - # /^# key_mgmt: /{ run=1 } - # /^#$/{ run=0 } - # /^# [A-Z0-9-]{2,}/{ if(run){printf("\"%s\"\n", $2)} } - # ' /run/current-system/sw/share/doc/wpa_supplicant/wpa_supplicant.conf.example - type = types.listOf ( - types.enum [ + ::: {.note} + The external secret can be either the plaintext + passphrase or the raw pre-shared key. + ::: + + ::: {.note} + Mutually exclusive with {var}`psk` and {var}`auth`. + ::: + ''; + }; + + authProtocols = mkOption { + default = [ + # WPA2 and WPA3 "WPA-PSK" "WPA-EAP" - "IEEE8021X" - "NONE" - "WPA-NONE" + "SAE" + # 802.11r variants of the above "FT-PSK" "FT-EAP" - "FT-EAP-SHA384" - "WPA-PSK-SHA256" - "WPA-EAP-SHA256" - "SAE" "FT-SAE" - "WPA-EAP-SUITE-B" - "WPA-EAP-SUITE-B-192" - "OSEN" - "FILS-SHA256" - "FILS-SHA384" - "FT-FILS-SHA256" - "FT-FILS-SHA384" - "OWE" - "DPP" - ] - ); - description = '' - The list of authentication protocols accepted by this network. - This corresponds to the `key_mgmt` option in wpa_supplicant. - ''; + ]; + # The list can be obtained by running this command + # awk ' + # /^# key_mgmt: /{ run=1 } + # /^#$/{ run=0 } + # /^# [A-Z0-9-]{2,}/{ if(run){printf("\"%s\"\n", $2)} } + # ' /run/current-system/sw/share/doc/wpa_supplicant/wpa_supplicant.conf.example + type = types.listOf ( + types.enum [ + "WPA-PSK" + "WPA-EAP" + "IEEE8021X" + "NONE" + "WPA-NONE" + "FT-PSK" + "FT-EAP" + "FT-EAP-SHA384" + "WPA-PSK-SHA256" + "WPA-EAP-SHA256" + "SAE" + "FT-SAE" + "WPA-EAP-SUITE-B" + "WPA-EAP-SUITE-B-192" + "OSEN" + "FILS-SHA256" + "FILS-SHA384" + "FT-FILS-SHA256" + "FT-FILS-SHA384" + "OWE" + "DPP" + ] + ); + description = '' + The list of authentication protocols accepted by this network. + This corresponds to the `key_mgmt` option in wpa_supplicant. + ''; + }; + + auth = mkOption { + type = types.nullOr types.str; + default = null; + example = '' + eap=PEAP + identity="user@example.com" + password=ext:example_password + ''; + description = '' + Use this option to configure advanced authentication methods + like EAP. See {manpage}`wpa_supplicant.conf(5)` for example + configurations. + + ::: {.warning} + Be aware that this will be written to the Nix store + in plaintext! Use an external reference like + `ext:secretname` for secrets. + ::: + + ::: {.note} + Mutually exclusive with {var}`psk` and {var}`pskRaw`. + ::: + ''; + }; + + hidden = mkOption { + type = types.bool; + default = false; + description = '' + Set this to `true` if the SSID of the network is hidden. + ''; + example = literalExpression '' + { echelon = { + hidden = true; + psk = "abcdefgh"; + }; + } + ''; + }; + + priority = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + By default, all networks will get same priority group (0). If + some of the networks are more desirable, this field can be used + to change the order in which wpa_supplicant goes through the + networks when selecting a BSS. The priority groups will be + iterated in decreasing priority (i.e., the larger the priority + value, the sooner the network is matched against the scan + results). Within each priority group, networks will be selected + based on security policy, signal strength, etc. + ''; + }; + + extraConfig = mkOption { + type = types.str; + default = ""; + example = '' + bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66 + ''; + description = '' + Extra configuration lines appended to the network block. + See {manpage}`wpa_supplicant.conf(5)` for available options. + ''; + }; + }; - - auth = mkOption { - type = types.nullOr types.str; - default = null; - example = '' - eap=PEAP - identity="user@example.com" - password=ext:example_password - ''; - description = '' - Use this option to configure advanced authentication methods - like EAP. See {manpage}`wpa_supplicant.conf(5)` for example - configurations. - - ::: {.warning} - Be aware that this will be written to the Nix store - in plaintext! Use an external reference like - `ext:secretname` for secrets. - ::: - - ::: {.note} - Mutually exclusive with {var}`psk` and {var}`pskRaw`. - ::: - ''; - }; - - hidden = mkOption { - type = types.bool; - default = false; - description = '' - Set this to `true` if the SSID of the network is hidden. - ''; - example = literalExpression '' - { echelon = { - hidden = true; - psk = "abcdefgh"; - }; - } - ''; - }; - - priority = mkOption { - type = types.nullOr types.int; - default = null; - description = '' - By default, all networks will get same priority group (0). If - some of the networks are more desirable, this field can be used - to change the order in which wpa_supplicant goes through the - networks when selecting a BSS. The priority groups will be - iterated in decreasing priority (i.e., the larger the priority - value, the sooner the network is matched against the scan - results). Within each priority group, networks will be selected - based on security policy, signal strength, etc. - ''; - }; - - extraConfig = mkOption { - type = types.str; - default = ""; - example = '' - bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66 - ''; - description = '' - Extra configuration lines appended to the network block. - See {manpage}`wpa_supplicant.conf(5)` for available options. - ''; - }; - - }; - } + } + ) ); description = '' The network definitions to automatically connect to when diff --git a/nixos/tests/wpa_supplicant.nix b/nixos/tests/wpa_supplicant.nix index d893885ae65b..f2a0c13de47c 100644 --- a/nixos/tests/wpa_supplicant.nix +++ b/nixos/tests/wpa_supplicant.nix @@ -13,8 +13,31 @@ let naughtyPassphrase = ''!,./;'[]\-=<>?:"{}|_+@$%^&*()`~ # ceci n'est pas un commentaire''; + runBssidTest = + name: expectedBssid: extraConfig: + runSimulatorTest name extraConfig '' + with subtest("Daemon can connect to the right access point"): + machine.wait_for_unit("wpa_supplicant-wlan1.service") + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q bssid=${expectedBssid}" + ) + ''; + runConnectionTest = name: extraConfig: + runSimulatorTest name extraConfig '' + with subtest("Daemon can connect to the access point"): + machine.wait_for_unit("wpa_supplicant-wlan1.service") + machine.wait_until_succeeds( + "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" + ) + ''; + + runSimulatorTest = + name: extraConfig: extraTestScript: runTest { name = "wpa_supplicant-${name}"; inherit meta; @@ -50,12 +73,22 @@ let bssid = "02:00:00:00:00:01"; }; wlan0-2 = { + ssid = "nixos-test-mixed"; + authentication = { + mode = "wpa3-sae-transition"; + saeAddToMacAllow = true; + saePasswordsFile = pkgs.writeText "password" naughtyPassphrase; + wpaPasswordFile = pkgs.writeText "password" naughtyPassphrase; + }; + bssid = "02:00:00:00:00:02"; + }; + wlan0-3 = { ssid = "nixos-test-wpa2"; authentication = { mode = "wpa2-sha256"; wpaPassword = naughtyPassphrase; }; - bssid = "02:00:00:00:00:02"; + bssid = "02:00:00:00:00:03"; }; }; }; @@ -85,11 +118,7 @@ let machine.wait_for_unit("hostapd.service") machine.copy_from_vm("/run/hostapd/wlan0.hostapd.conf") - with subtest("Daemon can connect to the access point"): - machine.wait_for_unit("wpa_supplicant-wlan1.service") - machine.wait_until_succeeds( - "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED" - ) + ${extraTestScript} ''; }; @@ -129,6 +158,18 @@ in psk = "password"; authProtocols = [ "SAE" ]; }; + + # Test duplicate SSID generation + duplicate1 = { + ssid = "duplicate"; + bssid = "00:00:00:00:00:01"; + psk = "password"; + }; + duplicate2 = { + ssid = "duplicate"; + bssid = "00:00:00:00:00:02"; + psk = "password"; + }; }; }; }; @@ -148,6 +189,12 @@ in assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1 assert int(machine.succeed(f"grep -c mixed-wpa {config_file}")) == 2 + with subtest("Duplicate SSID network blocks have been generated"): + # more duplication due to fallbacks + assert int(machine.succeed(f"grep -c duplicate {config_file}")) == 4 + assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:01 {config_file}")) == 2 + assert int(machine.succeed(f"grep -c bssid=00:00:00:00:00:02 {config_file}")) == 2 + # save file for manual inspection machine.copy_from_vm(config_file) ''; @@ -222,4 +269,23 @@ in authProtocols = [ "WPA-PSK-SHA256" ]; }; }; + + # Test connection with the highest prio "matching" network block found. + # "Matching" meaning with the right SSID and BSSID + bssidGuard = runBssidTest "bssid-guard" "02:00:00:00:00:02" { + networks = { + "1_first" = { + ssid = "nixos-test-mixed"; + bssid = "02:00:00:00:00:01"; + pskRaw = "ext:psk_nixos_test"; + }; + "2_second" = { + ssid = "nixos-test-mixed"; + bssid = "02:00:00:00:00:02"; + pskRaw = "ext:psk_nixos_test"; + priority = 1; + }; + }; + }; + }