mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-09 19:13:26 +03:00
nixos/kismet: init module
Use vwifi to write a proper test for Kismet. This test demonstrates how to simulate wireless networks in NixOS tests, and extract meaningful data by putting an interface in monitor mode using Kismet.
This commit is contained in:
parent
583a74d8ad
commit
36cddaaa6f
4 changed files with 727 additions and 0 deletions
|
@ -1175,6 +1175,7 @@
|
|||
./services/networking/kea.nix
|
||||
./services/networking/keepalived/default.nix
|
||||
./services/networking/keybase.nix
|
||||
./services/networking/kismet.nix
|
||||
./services/networking/knot.nix
|
||||
./services/networking/kresd.nix
|
||||
./services/networking/lambdabot.nix
|
||||
|
|
459
nixos/modules/services/networking/kismet.nix
Normal file
459
nixos/modules/services/networking/kismet.nix
Normal file
|
@ -0,0 +1,459 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (lib.trivial) isFloat isInt isBool;
|
||||
inherit (lib.modules) mkIf;
|
||||
inherit (lib.options)
|
||||
literalExpression
|
||||
mkOption
|
||||
mkPackageOption
|
||||
mkEnableOption
|
||||
;
|
||||
inherit (lib.strings)
|
||||
isString
|
||||
escapeShellArg
|
||||
escapeShellArgs
|
||||
concatMapStringsSep
|
||||
concatMapAttrsStringSep
|
||||
replaceStrings
|
||||
substring
|
||||
stringLength
|
||||
hasInfix
|
||||
hasSuffix
|
||||
typeOf
|
||||
match
|
||||
;
|
||||
inherit (lib.lists) all isList flatten;
|
||||
inherit (lib.attrsets)
|
||||
attrsToList
|
||||
filterAttrs
|
||||
optionalAttrs
|
||||
mapAttrs'
|
||||
mapAttrsToList
|
||||
nameValuePair
|
||||
;
|
||||
inherit (lib.generators) toKeyValue;
|
||||
inherit (lib) types;
|
||||
|
||||
# Deeply checks types for a given type function. Calls `override` with type and value.
|
||||
deep =
|
||||
func: override: type:
|
||||
let
|
||||
prev = func type;
|
||||
in
|
||||
prev
|
||||
// {
|
||||
check = value: prev.check value && (override type value);
|
||||
};
|
||||
|
||||
# Deep listOf.
|
||||
listOf' = deep types.listOf (type: value: all type.check value);
|
||||
|
||||
# Deep attrsOf.
|
||||
attrsOf' = deep types.attrsOf (type: value: all (item: type.check item.value) (attrsToList value));
|
||||
|
||||
# Kismet config atoms.
|
||||
atom =
|
||||
with types;
|
||||
oneOf [
|
||||
number
|
||||
bool
|
||||
str
|
||||
];
|
||||
|
||||
# Composite types.
|
||||
listOfAtom = listOf' atom;
|
||||
atomOrList = with types; either atom listOfAtom;
|
||||
lists = listOf' atomOrList;
|
||||
kvPair = attrsOf' atomOrList;
|
||||
kvPairs = listOf' kvPair;
|
||||
|
||||
# Options that eval to a string with a header (foo:key=value)
|
||||
headerKvPair = attrsOf' (attrsOf' atomOrList);
|
||||
headerKvPairs = attrsOf' (listOf' (attrsOf' atomOrList));
|
||||
|
||||
# Toplevel config type.
|
||||
topLevel =
|
||||
let
|
||||
topLevel' =
|
||||
with types;
|
||||
oneOf [
|
||||
headerKvPairs
|
||||
headerKvPair
|
||||
kvPairs
|
||||
kvPair
|
||||
listOfAtom
|
||||
lists
|
||||
atom
|
||||
];
|
||||
in
|
||||
topLevel'
|
||||
// {
|
||||
description = "Kismet config stanza";
|
||||
};
|
||||
|
||||
# Throws invalid.
|
||||
invalid = atom: throw "invalid value '${toString atom}' of type '${typeOf atom}'";
|
||||
|
||||
# Converts an atom.
|
||||
mkAtom =
|
||||
atom:
|
||||
if isString atom then
|
||||
if hasInfix "\"" atom || hasInfix "," atom then
|
||||
''"${replaceStrings [ ''"'' ] [ ''\"'' ] atom}"''
|
||||
else
|
||||
atom
|
||||
else if isFloat atom || isInt atom || isBool atom then
|
||||
toString atom
|
||||
else
|
||||
invalid atom;
|
||||
|
||||
# Converts an inline atom or list to a string.
|
||||
mkAtomOrListInline =
|
||||
atomOrList:
|
||||
if isList atomOrList then
|
||||
mkAtom "${concatMapStringsSep "," mkAtom atomOrList}"
|
||||
else
|
||||
mkAtom atomOrList;
|
||||
|
||||
# Converts an out of line atom or list to a string.
|
||||
mkAtomOrList =
|
||||
atomOrList:
|
||||
if isList atomOrList then
|
||||
"${concatMapStringsSep "," mkAtomOrListInline atomOrList}"
|
||||
else
|
||||
mkAtom atomOrList;
|
||||
|
||||
# Throws if the string matches the given regex.
|
||||
deny =
|
||||
regex: str:
|
||||
assert (match regex str) == null;
|
||||
str;
|
||||
|
||||
# Converts a set of k/v pairs.
|
||||
convertKv = concatMapAttrsStringSep "," (
|
||||
name: value: "${mkAtom (deny "=" name)}=${mkAtomOrListInline value}"
|
||||
);
|
||||
|
||||
# Converts k/v pairs with a header.
|
||||
convertKvWithHeader = header: attrs: "${mkAtom (deny ":" header)}:${convertKv attrs}";
|
||||
|
||||
# Converts the entire config.
|
||||
convertConfig = mapAttrs' (
|
||||
name: value:
|
||||
let
|
||||
# Convert foo' into 'foo+' for support for '+=' syntax.
|
||||
newName = if hasSuffix "'" name then substring 0 (stringLength name - 1) name + "+" else name;
|
||||
|
||||
# Get the stringified value.
|
||||
newValue =
|
||||
if headerKvPairs.check value then
|
||||
flatten (
|
||||
mapAttrsToList (header: values: (map (value: convertKvWithHeader header value) values)) value
|
||||
)
|
||||
else if headerKvPair.check value then
|
||||
mapAttrsToList convertKvWithHeader value
|
||||
else if kvPairs.check value then
|
||||
map convertKv value
|
||||
else if kvPair.check value then
|
||||
convertKv value
|
||||
else if listOfAtom.check value then
|
||||
mkAtomOrList value
|
||||
else if lists.check value then
|
||||
map mkAtomOrList value
|
||||
else if atom.check value then
|
||||
mkAtom value
|
||||
else
|
||||
invalid value;
|
||||
in
|
||||
nameValuePair newName newValue
|
||||
);
|
||||
|
||||
mkKismetConf =
|
||||
options:
|
||||
(toKeyValue { listsAsDuplicateKeys = true; }) (
|
||||
filterAttrs (_: value: value != null) (convertConfig options)
|
||||
);
|
||||
|
||||
cfg = config.services.kismet;
|
||||
in
|
||||
{
|
||||
options.services.kismet = {
|
||||
enable = mkEnableOption "kismet";
|
||||
package = mkPackageOption pkgs "kismet" { };
|
||||
user = mkOption {
|
||||
description = "The user to run Kismet as.";
|
||||
type = types.str;
|
||||
default = "kismet";
|
||||
};
|
||||
group = mkOption {
|
||||
description = "The group to run Kismet as.";
|
||||
type = types.str;
|
||||
default = "kismet";
|
||||
};
|
||||
serverName = mkOption {
|
||||
description = "The name of the server.";
|
||||
type = types.str;
|
||||
default = "Kismet";
|
||||
};
|
||||
serverDescription = mkOption {
|
||||
description = "The description of the server.";
|
||||
type = types.str;
|
||||
default = "NixOS Kismet server";
|
||||
};
|
||||
logTypes = mkOption {
|
||||
description = "The log types.";
|
||||
type = with types; listOf str;
|
||||
default = [ "kismet" ];
|
||||
};
|
||||
dataDir = mkOption {
|
||||
description = "The Kismet data directory.";
|
||||
type = types.path;
|
||||
default = "/var/lib/kismet";
|
||||
};
|
||||
httpd = {
|
||||
enable = mkOption {
|
||||
description = "True to enable the HTTP server.";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
address = mkOption {
|
||||
description = "The address to listen on. Note that this cannot be a hostname or Kismet will not start.";
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
};
|
||||
port = mkOption {
|
||||
description = "The port to listen on.";
|
||||
type = types.port;
|
||||
default = 2501;
|
||||
};
|
||||
};
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Options for Kismet. See:
|
||||
https://www.kismetwireless.net/docs/readme/configuring/configfiles/
|
||||
'';
|
||||
default = { };
|
||||
type = with types; attrsOf topLevel;
|
||||
example = literalExpression ''
|
||||
{
|
||||
/* Examples for atoms */
|
||||
# dot11_link_bssts=false
|
||||
dot11_link_bssts = false; # Boolean
|
||||
|
||||
# dot11_related_bss_window=10000000
|
||||
dot11_related_bss_window = 10000000; # Integer
|
||||
|
||||
# devicefound=00:11:22:33:44:55
|
||||
devicefound = "00:11:22:33:44:55"; # String
|
||||
|
||||
# log_types+=wiglecsv
|
||||
log_types' = "wiglecsv";
|
||||
|
||||
/* Examples for lists of atoms */
|
||||
# wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
|
||||
wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
|
||||
|
||||
# alert=ADHOCCONFLICT,5/min,1/sec
|
||||
# alert=ADVCRYPTCHANGE,5/min,1/sec
|
||||
alert = [
|
||||
[ "ADHOCCONFLICT" "5/min" "1/sec" ]
|
||||
[ "ADVCRYPTCHANGE" "5/min" "1/sec" ]
|
||||
];
|
||||
|
||||
/* Examples for sets of atoms */
|
||||
# source=wlan0:name=ath11k
|
||||
source.wlan0 = { name = "ath11k"; };
|
||||
|
||||
/* Examples with colon-suffixed headers */
|
||||
# gps=gpsd:host=localhost,port=2947
|
||||
gps.gpsd = {
|
||||
host = "localhost";
|
||||
port = 2947;
|
||||
};
|
||||
|
||||
# apspoof=Foo1:ssid=Bar1,validmacs="00:11:22:33:44:55,aa:bb:cc:dd:ee:ff"
|
||||
# apspoof=Foo1:ssid=Bar2,validmacs="01:12:23:34:45:56,ab:bc:cd:de:ef:f0"
|
||||
# apspoof=Foo2:ssid=Baz1,validmacs="11:22:33:44:55:66,bb:cc:dd:ee:ff:00"
|
||||
apspoof.Foo1 = [
|
||||
{ ssid = "Bar1"; validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ]; }
|
||||
{ ssid = "Bar2"; validmacs = [ "01:12:23:34:45:56" "ab:bc:cd:de:ef:f0" ]; }
|
||||
];
|
||||
|
||||
# because Foo1 is a list, Foo2 needs to be as well
|
||||
apspoof.Foo2 = [
|
||||
{
|
||||
ssid = "Bar2";
|
||||
validmacs = [ "00:11:22:33:44:55" "aa:bb:cc:dd:ee:ff" ];
|
||||
};
|
||||
];
|
||||
}
|
||||
'';
|
||||
};
|
||||
extraConfig = mkOption {
|
||||
description = ''
|
||||
Literal Kismet config lines appended to the site config.
|
||||
Note that `services.kismet.settings` allows you to define
|
||||
all options here using Nix attribute sets.
|
||||
'';
|
||||
default = "";
|
||||
type = types.str;
|
||||
example = ''
|
||||
# Looks like the following in `services.kismet.settings`:
|
||||
# wepkey = [ "00:DE:AD:C0:DE:00" "FEEDFACE42" ];
|
||||
wepkey=00:DE:AD:C0:DE:00,FEEDFACE42
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
configDir = "${cfg.dataDir}/.kismet";
|
||||
settings =
|
||||
cfg.settings
|
||||
// {
|
||||
server_name = cfg.serverName;
|
||||
server_description = cfg.serverDescription;
|
||||
logging_enabled = cfg.logTypes != [ ];
|
||||
log_types = cfg.logTypes;
|
||||
}
|
||||
// optionalAttrs cfg.httpd.enable {
|
||||
httpd_bind_address = cfg.httpd.address;
|
||||
httpd_port = cfg.httpd.port;
|
||||
httpd_auth_file = "${configDir}/kismet_httpd.conf";
|
||||
httpd_home = "${cfg.package}/share/kismet/httpd";
|
||||
};
|
||||
in
|
||||
mkIf cfg.enable {
|
||||
systemd.tmpfiles.settings = {
|
||||
"10-kismet" = {
|
||||
${cfg.dataDir} = {
|
||||
d = {
|
||||
inherit (cfg) user group;
|
||||
mode = "0750";
|
||||
};
|
||||
};
|
||||
${configDir} = {
|
||||
d = {
|
||||
inherit (cfg) user group;
|
||||
mode = "0750";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
systemd.services.kismet =
|
||||
let
|
||||
kismetConf = pkgs.writeText "kismet.conf" ''
|
||||
${mkKismetConf settings}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
in
|
||||
{
|
||||
description = "Kismet monitoring service";
|
||||
wants = [ "basic.target" ];
|
||||
after = [
|
||||
"basic.target"
|
||||
"network.target"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig =
|
||||
let
|
||||
capabilities = [
|
||||
"CAP_NET_ADMIN"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
kismetPreStart = pkgs.writeShellScript "kismet-pre-start" ''
|
||||
owner=${escapeShellArg "${cfg.user}:${cfg.group}"}
|
||||
mkdir -p ~/.kismet
|
||||
|
||||
# Ensure permissions on directories Kismet uses.
|
||||
chown "$owner" ~/ ~/.kismet
|
||||
cd ~/.kismet
|
||||
|
||||
package=${cfg.package}
|
||||
if [ -d "$package/etc" ]; then
|
||||
for file in "$package/etc"/*.conf; do
|
||||
# Symlink the config files if they exist or are already a link.
|
||||
base="''${file##*/}"
|
||||
if [ ! -f "$base" ] || [ -L "$base" ]; then
|
||||
ln -sf "$file" "$base"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
for file in kismet_httpd.conf; do
|
||||
# Un-symlink these files.
|
||||
if [ -L "$file" ]; then
|
||||
cp "$file" ".$file"
|
||||
rm -f "$file"
|
||||
mv ".$file" "$file"
|
||||
chmod 0640 "$file"
|
||||
chown "$owner" "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Link the site config.
|
||||
ln -sf ${kismetConf} kismet_site.conf
|
||||
'';
|
||||
in
|
||||
{
|
||||
Type = "simple";
|
||||
ExecStart = escapeShellArgs [
|
||||
"${cfg.package}/bin/kismet"
|
||||
"--homedir"
|
||||
cfg.dataDir
|
||||
"--confdir"
|
||||
configDir
|
||||
"--datadir"
|
||||
"${cfg.package}/share"
|
||||
"--no-ncurses"
|
||||
"-f"
|
||||
"${configDir}/kismet.conf"
|
||||
];
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
ExecStartPre = "+${kismetPreStart}";
|
||||
Restart = "always";
|
||||
KillMode = "control-group";
|
||||
CapabilityBoundingSet = capabilities;
|
||||
AmbientCapabilities = capabilities;
|
||||
LockPersonality = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = false;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = false;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "full";
|
||||
RestrictNamespaces = true;
|
||||
RestrictSUIDSGID = true;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
UMask = "0007";
|
||||
TimeoutStopSec = 30;
|
||||
};
|
||||
|
||||
# Allow it to restart if the wifi interface is not up
|
||||
unitConfig.StartLimitIntervalSec = 5;
|
||||
};
|
||||
users.groups.${cfg.group} = { };
|
||||
users.users.${cfg.user} = {
|
||||
inherit (cfg) group;
|
||||
description = "User for running Kismet";
|
||||
isSystemUser = true;
|
||||
home = cfg.dataDir;
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ numinit ];
|
||||
}
|
|
@ -702,6 +702,7 @@ in
|
|||
keyd = handleTest ./keyd.nix { };
|
||||
keymap = handleTest ./keymap.nix { };
|
||||
kimai = runTest ./kimai.nix;
|
||||
kismet = runTest ./kismet.nix;
|
||||
kmonad = runTest ./kmonad.nix;
|
||||
knot = runTest ./knot.nix;
|
||||
komga = handleTest ./komga.nix { };
|
||||
|
|
266
nixos/tests/kismet.nix
Normal file
266
nixos/tests/kismet.nix
Normal file
|
@ -0,0 +1,266 @@
|
|||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
ssid = "Hydra SmokeNet";
|
||||
psk = "stayoffmywifi";
|
||||
wlanInterface = "wlan0";
|
||||
in
|
||||
{
|
||||
name = "kismet";
|
||||
|
||||
nodes =
|
||||
let
|
||||
hostAddress = id: "192.168.1.${toString (id + 1)}";
|
||||
serverAddress = hostAddress 1;
|
||||
in
|
||||
{
|
||||
airgap =
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = serverAddress;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
services.vwifi = {
|
||||
server = {
|
||||
enable = true;
|
||||
ports.tcp = 8212;
|
||||
ports.spy = 8213;
|
||||
openFirewall = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ap =
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = hostAddress 2;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
services.hostapd = {
|
||||
enable = true;
|
||||
radios.${wlanInterface} = {
|
||||
channel = 1;
|
||||
networks.${wlanInterface} = {
|
||||
inherit ssid;
|
||||
authentication = {
|
||||
mode = "wpa3-sae";
|
||||
saePasswords = [ { password = psk; } ];
|
||||
enableRecommendedPairwiseCiphers = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
services.vwifi = {
|
||||
module = {
|
||||
enable = true;
|
||||
macPrefix = "74:F8:F6:00:01";
|
||||
};
|
||||
client = {
|
||||
enable = true;
|
||||
inherit serverAddress;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
station =
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = hostAddress 3;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
networking.wireless = {
|
||||
# No, really, we want it enabled!
|
||||
enable = lib.mkOverride 0 true;
|
||||
interfaces = [ wlanInterface ];
|
||||
networks = {
|
||||
${ssid} = {
|
||||
inherit psk;
|
||||
authProtocols = [ "SAE" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
services.vwifi = {
|
||||
module = {
|
||||
enable = true;
|
||||
macPrefix = "74:F8:F6:00:02";
|
||||
};
|
||||
client = {
|
||||
enable = true;
|
||||
inherit serverAddress;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
monitor =
|
||||
{ config, ... }:
|
||||
{
|
||||
networking.interfaces.eth1.ipv4.addresses = lib.mkForce [
|
||||
{
|
||||
address = hostAddress 4;
|
||||
prefixLength = 24;
|
||||
}
|
||||
];
|
||||
|
||||
services.kismet = {
|
||||
enable = true;
|
||||
serverName = "NixOS Kismet Smoke Test";
|
||||
serverDescription = "Server testing virtual wifi devices running on Hydra";
|
||||
httpd.enable = true;
|
||||
# Check that the settings all eval correctly
|
||||
settings = {
|
||||
# Should append to log_types
|
||||
log_types' = "wiglecsv";
|
||||
|
||||
# Should all generate correctly
|
||||
wepkey = [
|
||||
"00:DE:AD:C0:DE:00"
|
||||
"FEEDFACE42"
|
||||
];
|
||||
alert = [
|
||||
[
|
||||
"ADHOCCONFLICT"
|
||||
"5/min"
|
||||
"1/sec"
|
||||
]
|
||||
[
|
||||
"ADVCRYPTCHANGE"
|
||||
"5/min"
|
||||
"1/sec"
|
||||
]
|
||||
];
|
||||
gps.gpsd = {
|
||||
host = "localhost";
|
||||
port = 2947;
|
||||
};
|
||||
apspoof.Foo1 = [
|
||||
{
|
||||
ssid = "Bar1";
|
||||
validmacs = [
|
||||
"00:11:22:33:44:55"
|
||||
"aa:bb:cc:dd:ee:ff"
|
||||
];
|
||||
}
|
||||
{
|
||||
ssid = "Bar2";
|
||||
validmacs = [
|
||||
"01:12:23:34:45:56"
|
||||
"ab:bc:cd:de:ef:f0"
|
||||
];
|
||||
}
|
||||
];
|
||||
apspoof.Foo2 = [
|
||||
{
|
||||
ssid = "Bar2";
|
||||
validmacs = [
|
||||
"00:11:22:33:44:55"
|
||||
"aa:bb:cc:dd:ee:ff"
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
# The actual source
|
||||
source.${wlanInterface} = {
|
||||
name = "Virtual Wifi";
|
||||
};
|
||||
};
|
||||
extraConfig = ''
|
||||
# this comment should be ignored
|
||||
'';
|
||||
};
|
||||
|
||||
services.vwifi = {
|
||||
module = {
|
||||
enable = true;
|
||||
macPrefix = "74:F8:F6:00:03";
|
||||
};
|
||||
client = {
|
||||
enable = true;
|
||||
spy = true;
|
||||
inherit serverAddress;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
config.services.kismet.package
|
||||
config.services.vwifi.package
|
||||
jq
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
''
|
||||
import shlex
|
||||
|
||||
# Wait for the vwifi server to come up
|
||||
airgap.start()
|
||||
airgap.wait_for_unit("vwifi-server.service")
|
||||
airgap.wait_for_open_port(${toString nodes.airgap.services.vwifi.server.ports.tcp})
|
||||
|
||||
httpd_port = ${toString nodes.monitor.services.kismet.httpd.port}
|
||||
server_name = "${nodes.monitor.services.kismet.serverName}"
|
||||
server_description = "${nodes.monitor.services.kismet.serverDescription}"
|
||||
wlan_interface = "${wlanInterface}"
|
||||
ap_essid = "${ssid}"
|
||||
ap_mac_prefix = "${nodes.ap.services.vwifi.module.macPrefix}"
|
||||
station_mac_prefix = "${nodes.station.services.vwifi.module.macPrefix}"
|
||||
|
||||
# Spawn the other nodes.
|
||||
monitor.start()
|
||||
|
||||
# Wait for the monitor to come up
|
||||
monitor.wait_for_unit("kismet.service")
|
||||
monitor.wait_for_open_port(httpd_port)
|
||||
|
||||
# Should be up but require authentication.
|
||||
url = f"http://localhost:{httpd_port}"
|
||||
monitor.succeed(f"curl {url} | tee /dev/stderr | grep '<title>Kismet</title>'")
|
||||
|
||||
# Have to set the password now.
|
||||
monitor.succeed("echo httpd_username=nixos >> ~kismet/.kismet/kismet_httpd.conf")
|
||||
monitor.succeed("echo httpd_password=hydra >> ~kismet/.kismet/kismet_httpd.conf")
|
||||
monitor.systemctl("restart kismet.service")
|
||||
monitor.wait_for_unit("kismet.service")
|
||||
monitor.wait_for_open_port(httpd_port)
|
||||
|
||||
# Authentication should now work.
|
||||
url = f"http://nixos:hydra@localhost:{httpd_port}"
|
||||
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'")
|
||||
|
||||
# Wait for the station to connect to the AP while Kismet is monitoring
|
||||
ap.start()
|
||||
station.start()
|
||||
|
||||
unit = f"wpa_supplicant-{wlan_interface}"
|
||||
|
||||
# Generate handshakes until we detect both devices
|
||||
success = False
|
||||
for i in range(100):
|
||||
station.wait_for_unit(f"wpa_supplicant-{wlan_interface}.service")
|
||||
station.succeed(f"ifconfig {wlan_interface} down && ifconfig {wlan_interface} up")
|
||||
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')}")
|
||||
station.succeed(f"journalctl --rotate --unit={shlex.quote(unit)}")
|
||||
station.succeed(f"sleep 3 && journalctl --vacuum-time=1s --unit={shlex.quote(unit)}")
|
||||
|
||||
# We're connected, make sure Kismet sees both of our devices
|
||||
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'")
|
||||
if status != 0:
|
||||
continue
|
||||
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'")
|
||||
if status == 0:
|
||||
success = True
|
||||
break
|
||||
|
||||
assert success
|
||||
'';
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue