1import ./make-test-python.nix ({ pkgs, lib, ...}:
2{
3 name = "wpa_supplicant";
4 meta = with lib.maintainers; {
5 maintainers = [ oddlama rnhmjoj ];
6 };
7
8 nodes = let
9 machineWithHostapd = extraConfigModule: { ... }: {
10 imports = [
11 ../modules/profiles/minimal.nix
12 extraConfigModule
13 ];
14
15 # add a virtual wlan interface
16 boot.kernelModules = [ "mac80211_hwsim" ];
17
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 saeAddToMacAllow = true;
38 saePasswordsFile = pkgs.writeText "password" "reproducibility";
39 wpaPasswordFile = pkgs.writeText "password" "reproducibility";
40 };
41 bssid = "02:00:00:00:00:01";
42 };
43 wlan0-2 = {
44 ssid = "nixos-test-wpa2";
45 authentication = {
46 mode = "wpa2-sha256";
47 wpaPassword = "reproducibility";
48 };
49 bssid = "02:00:00:00:00:02";
50 };
51 };
52 };
53 };
54
55 # wireless client
56 networking.wireless = {
57 # the override is needed because the wifi is
58 # disabled with mkVMOverride in qemu-vm.nix.
59 enable = lib.mkOverride 0 true;
60 userControlled.enable = true;
61 interfaces = [ "wlan1" ];
62 fallbackToWPA2 = lib.mkDefault true;
63
64 # networks will be added on-demand below for the specific
65 # network that should be tested
66
67 # secrets
68 environmentFile = pkgs.writeText "wpa-secrets" ''
69 PSK_NIXOS_TEST="reproducibility"
70 '';
71 };
72 };
73 in {
74 basic = { ... }: {
75 imports = [ ../modules/profiles/minimal.nix ];
76
77 # add a virtual wlan interface
78 boot.kernelModules = [ "mac80211_hwsim" ];
79
80 # wireless client
81 networking.wireless = {
82 # the override is needed because the wifi is
83 # disabled with mkVMOverride in qemu-vm.nix.
84 enable = lib.mkOverride 0 true;
85 userControlled.enable = true;
86 interfaces = [ "wlan1" ];
87 fallbackToWPA2 = true;
88
89 networks = {
90 # test WPA2 fallback
91 mixed-wpa = {
92 psk = "password";
93 authProtocols = [ "WPA-PSK" "SAE" ];
94 };
95 sae-only = {
96 psk = "password";
97 authProtocols = [ "SAE" ];
98 };
99
100 # secrets substitution test cases
101 test1.psk = "@PSK_VALID@"; # should be replaced
102 test2.psk = "@PSK_SPECIAL@"; # should be replaced
103 test3.psk = "@PSK_MISSING@"; # should not be replaced
104 test4.psk = "P@ssowrdWithSome@tSymbol"; # should not be replaced
105 };
106
107 # secrets
108 environmentFile = pkgs.writeText "wpa-secrets" ''
109 PSK_VALID="S0m3BadP4ssw0rd";
110 # taken from https://github.com/minimaxir/big-list-of-naughty-strings
111 PSK_SPECIAL=",./;'[]\-= <>?:\"{}|_+ !@#$%^\&*()`~";
112 '';
113 };
114 };
115
116 # Test connecting to the SAE-only hotspot using SAE
117 machineSae = machineWithHostapd {
118 networking.wireless = {
119 fallbackToWPA2 = false;
120 networks.nixos-test-sae = {
121 psk = "@PSK_NIXOS_TEST@";
122 authProtocols = [ "SAE" ];
123 };
124 };
125 };
126
127 # Test connecting to the SAE and WPA2 mixed hotspot using SAE
128 machineMixedUsingSae = machineWithHostapd {
129 networking.wireless = {
130 fallbackToWPA2 = false;
131 networks.nixos-test-mixed = {
132 psk = "@PSK_NIXOS_TEST@";
133 authProtocols = [ "SAE" ];
134 };
135 };
136 };
137
138 # Test connecting to the SAE and WPA2 mixed hotspot using WPA2
139 machineMixedUsingWpa2 = machineWithHostapd {
140 networking.wireless = {
141 fallbackToWPA2 = true;
142 networks.nixos-test-mixed = {
143 psk = "@PSK_NIXOS_TEST@";
144 authProtocols = [ "WPA-PSK-SHA256" ];
145 };
146 };
147 };
148
149 # Test connecting to the WPA2 legacy hotspot using WPA2
150 machineWpa2 = machineWithHostapd {
151 networking.wireless = {
152 fallbackToWPA2 = true;
153 networks.nixos-test-wpa2 = {
154 psk = "@PSK_NIXOS_TEST@";
155 authProtocols = [ "WPA-PSK-SHA256" ];
156 };
157 };
158 };
159 };
160
161 testScript =
162 ''
163 config_file = "/run/wpa_supplicant/wpa_supplicant.conf"
164
165 with subtest("Configuration file is inaccessible to other users"):
166 basic.wait_for_file(config_file)
167 basic.fail(f"sudo -u nobody ls {config_file}")
168
169 with subtest("Secrets variables have been substituted"):
170 basic.fail(f"grep -q @PSK_VALID@ {config_file}")
171 basic.fail(f"grep -q @PSK_SPECIAL@ {config_file}")
172 basic.succeed(f"grep -q @PSK_MISSING@ {config_file}")
173 basic.succeed(f"grep -q P@ssowrdWithSome@tSymbol {config_file}")
174
175 with subtest("WPA2 fallbacks have been generated"):
176 assert int(basic.succeed(f"grep -c sae-only {config_file}")) == 1
177 assert int(basic.succeed(f"grep -c mixed-wpa {config_file}")) == 2
178
179 # save file for manual inspection
180 basic.copy_from_vm(config_file)
181
182 with subtest("Daemon is running and accepting connections"):
183 basic.wait_for_unit("wpa_supplicant-wlan1.service")
184 status = basic.succeed("wpa_cli -i wlan1 status")
185 assert "Failed to connect" not in status, \
186 "Failed to connect to the daemon"
187
188 machineSae.wait_for_unit("hostapd.service")
189 machineSae.copy_from_vm("/run/hostapd/wlan0.hostapd.conf")
190 with subtest("Daemon can connect to the SAE access point using SAE"):
191 machineSae.wait_until_succeeds(
192 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
193 )
194
195 with subtest("Daemon can connect to the SAE and WPA2 mixed access point using SAE"):
196 machineMixedUsingSae.wait_until_succeeds(
197 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
198 )
199
200 with subtest("Daemon can connect to the SAE and WPA2 mixed access point using WPA2"):
201 machineMixedUsingWpa2.wait_until_succeeds(
202 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
203 )
204
205 with subtest("Daemon can connect to the WPA2 access point using WPA2"):
206 machineWpa2.wait_until_succeeds(
207 "wpa_cli -i wlan1 status | grep -q wpa_state=COMPLETED"
208 )
209 '';
210})