at master 8.6 kB view raw
1{ pkgs, lib, ... }: 2 3let 4 ssid = "Hydra SmokeNet"; 5 psk = "stayoffmywifi"; 6 wlanInterface = "wlan0"; 7in 8{ 9 name = "kismet"; 10 11 nodes = 12 let 13 hostAddress = id: "192.168.1.${toString (id + 1)}"; 14 serverAddress = hostAddress 1; 15 in 16 { 17 airgap = 18 { config, ... }: 19 { 20 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 21 { 22 address = serverAddress; 23 prefixLength = 24; 24 } 25 ]; 26 services.vwifi = { 27 server = { 28 enable = true; 29 ports.tcp = 8212; 30 ports.spy = 8213; 31 openFirewall = true; 32 }; 33 }; 34 }; 35 36 ap = 37 { config, ... }: 38 { 39 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 40 { 41 address = hostAddress 2; 42 prefixLength = 24; 43 } 44 ]; 45 services.hostapd = { 46 enable = true; 47 radios.${wlanInterface} = { 48 channel = 1; 49 networks.${wlanInterface} = { 50 inherit ssid; 51 authentication = { 52 mode = "wpa3-sae"; 53 saePasswords = [ { password = psk; } ]; 54 enableRecommendedPairwiseCiphers = true; 55 }; 56 }; 57 }; 58 }; 59 services.vwifi = { 60 module = { 61 enable = true; 62 macPrefix = "74:F8:F6:00:01"; 63 }; 64 client = { 65 enable = true; 66 inherit serverAddress; 67 }; 68 }; 69 }; 70 71 station = 72 { config, ... }: 73 { 74 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 75 { 76 address = hostAddress 3; 77 prefixLength = 24; 78 } 79 ]; 80 networking.wireless = { 81 # No, really, we want it enabled! 82 enable = lib.mkOverride 0 true; 83 interfaces = [ wlanInterface ]; 84 networks = { 85 ${ssid} = { 86 inherit psk; 87 authProtocols = [ "SAE" ]; 88 }; 89 }; 90 }; 91 services.vwifi = { 92 module = { 93 enable = true; 94 macPrefix = "74:F8:F6:00:02"; 95 }; 96 client = { 97 enable = true; 98 inherit serverAddress; 99 }; 100 }; 101 }; 102 103 monitor = 104 { config, ... }: 105 { 106 networking.interfaces.eth1.ipv4.addresses = lib.mkForce [ 107 { 108 address = hostAddress 4; 109 prefixLength = 24; 110 } 111 ]; 112 113 services.kismet = { 114 enable = true; 115 serverName = "NixOS Kismet Smoke Test"; 116 serverDescription = "Server testing virtual wifi devices running on Hydra"; 117 httpd.enable = true; 118 # Check that the settings all eval correctly 119 settings = { 120 # Should append to log_types 121 log_types' = "wiglecsv"; 122 123 # Should all generate correctly 124 wepkey = [ 125 "00:DE:AD:C0:DE:00" 126 "FEEDFACE42" 127 ]; 128 alert = [ 129 [ 130 "ADHOCCONFLICT" 131 "5/min" 132 "1/sec" 133 ] 134 [ 135 "ADVCRYPTCHANGE" 136 "5/min" 137 "1/sec" 138 ] 139 ]; 140 gps.gpsd = { 141 host = "localhost"; 142 port = 2947; 143 }; 144 apspoof.Foo1 = [ 145 { 146 ssid = "Bar1"; 147 validmacs = [ 148 "00:11:22:33:44:55" 149 "aa:bb:cc:dd:ee:ff" 150 ]; 151 } 152 { 153 ssid = "Bar2"; 154 validmacs = [ 155 "01:12:23:34:45:56" 156 "ab:bc:cd:de:ef:f0" 157 ]; 158 } 159 ]; 160 apspoof.Foo2 = [ 161 { 162 ssid = "Bar2"; 163 validmacs = [ 164 "00:11:22:33:44:55" 165 "aa:bb:cc:dd:ee:ff" 166 ]; 167 } 168 ]; 169 170 # The actual source 171 source.${wlanInterface} = { 172 name = "Virtual Wifi"; 173 }; 174 }; 175 extraConfig = '' 176 # this comment should be ignored 177 ''; 178 }; 179 180 services.vwifi = { 181 module = { 182 enable = true; 183 macPrefix = "74:F8:F6:00:03"; 184 }; 185 client = { 186 enable = true; 187 spy = true; 188 inherit serverAddress; 189 }; 190 }; 191 192 environment.systemPackages = with pkgs; [ 193 config.services.kismet.package 194 config.services.vwifi.package 195 jq 196 ]; 197 }; 198 }; 199 200 testScript = 201 { nodes, ... }: 202 '' 203 import shlex 204 205 # Wait for the vwifi server to come up 206 airgap.start() 207 airgap.wait_for_unit("vwifi-server.service") 208 airgap.wait_for_open_port(${toString nodes.airgap.services.vwifi.server.ports.tcp}) 209 210 httpd_port = ${toString nodes.monitor.services.kismet.httpd.port} 211 server_name = "${nodes.monitor.services.kismet.serverName}" 212 server_description = "${nodes.monitor.services.kismet.serverDescription}" 213 wlan_interface = "${wlanInterface}" 214 ap_essid = "${ssid}" 215 ap_mac_prefix = "${nodes.ap.services.vwifi.module.macPrefix}" 216 station_mac_prefix = "${nodes.station.services.vwifi.module.macPrefix}" 217 218 # Spawn the other nodes. 219 monitor.start() 220 221 # Wait for the monitor to come up 222 monitor.wait_for_unit("kismet.service") 223 monitor.wait_for_open_port(httpd_port) 224 225 # Should be up but require authentication. 226 url = f"http://localhost:{httpd_port}" 227 monitor.succeed(f"curl {url} | tee /dev/stderr | grep '<title>Kismet</title>'") 228 229 # Have to set the password now. 230 monitor.succeed("echo httpd_username=nixos >> ~kismet/.kismet/kismet_httpd.conf") 231 monitor.succeed("echo httpd_password=hydra >> ~kismet/.kismet/kismet_httpd.conf") 232 monitor.systemctl("restart kismet.service") 233 monitor.wait_for_unit("kismet.service") 234 monitor.wait_for_open_port(httpd_port) 235 236 # Authentication should now work. 237 url = f"http://nixos:hydra@localhost:{httpd_port}" 238 monitor.succeed(f"curl {url}/system/status.json | tee /dev/stderr | jq -e --arg serverName {shlex.quote(server_name)} --arg serverDescription {shlex.quote(server_description)} '.\"kismet.system.server_name\" == $serverName and .\"kismet.system.server_description\" == $serverDescription'") 239 240 # Wait for the station to connect to the AP while Kismet is monitoring 241 ap.start() 242 station.start() 243 244 unit = f"wpa_supplicant-{wlan_interface}" 245 246 # Generate handshakes until we detect both devices 247 success = False 248 for i in range(100): 249 station.wait_for_unit(f"wpa_supplicant-{wlan_interface}.service") 250 station.succeed(f"ifconfig {wlan_interface} down && ifconfig {wlan_interface} up") 251 station.wait_until_succeeds(f"journalctl -u {shlex.quote(unit)} -e | grep -Eqi {shlex.quote(wlan_interface + ': CTRL-EVENT-CONNECTED - Connection to ' + ap_mac_prefix + '[0-9a-f:]* completed')}") 252 station.succeed(f"journalctl --rotate --unit={shlex.quote(unit)}") 253 station.succeed(f"sleep 3 && journalctl --vacuum-time=1s --unit={shlex.quote(unit)}") 254 255 # We're connected, make sure Kismet sees both of our devices 256 status, stdout = monitor.execute(f"curl {url}/devices/views/all/last-time/0/devices.json | tee /dev/stderr | jq -e --arg macPrefix {shlex.quote(ap_mac_prefix)} --arg ssid {shlex.quote(ap_essid)} '. | (map(select((.\"kismet.device.base.macaddr\"? | startswith($macPrefix)) and .\"dot11.device\"?.\"dot11.device.last_beaconed_ssid_record\"?.\"dot11.advertisedssid.ssid\" == $ssid)) | length) == 1'") 257 if status != 0: 258 continue 259 status, stdout = monitor.execute(f"curl {url}/devices/views/all/last-time/0/devices.json | tee /dev/stderr | jq -e --arg macPrefix {shlex.quote(station_mac_prefix)} '. | (map(select((.\"kismet.device.base.macaddr\"? | startswith($macPrefix)))) | length) == 1'") 260 if status == 0: 261 success = True 262 break 263 264 assert success 265 ''; 266}