at 18.03-beta 14 kB view raw
1{ system ? builtins.currentSystem, debug ? false }: 2 3with import ../lib/testing.nix { inherit system; }; 4with pkgs.lib; 5 6let 7 testVMConfig = vmName: attrs: { config, pkgs, lib, ... }: let 8 guestAdditions = pkgs.linuxPackages.virtualboxGuestAdditions; 9 10 miniInit = '' 11 #!${pkgs.stdenv.shell} -xe 12 export PATH="${lib.makeBinPath [ pkgs.coreutils pkgs.utillinux ]}" 13 14 mkdir -p /run/dbus 15 cat > /etc/passwd <<EOF 16 root:x:0:0::/root:/bin/false 17 messagebus:x:1:1::/run/dbus:/bin/false 18 EOF 19 cat > /etc/group <<EOF 20 root:x:0: 21 messagebus:x:1: 22 EOF 23 24 "${pkgs.dbus.daemon}/bin/dbus-daemon" --fork \ 25 --config-file="${pkgs.dbus.daemon}/share/dbus-1/system.conf" 26 27 ${guestAdditions}/bin/VBoxService 28 ${(attrs.vmScript or (const "")) pkgs} 29 30 i=0 31 while [ ! -e /mnt-root/shutdown ]; do 32 sleep 10 33 i=$(($i + 10)) 34 [ $i -le 120 ] || fail 35 done 36 37 rm -f /mnt-root/boot-done /mnt-root/shutdown 38 ''; 39 in { 40 boot.kernelParams = [ 41 "console=tty0" "console=ttyS0" "ignore_loglevel" 42 "boot.trace" "panic=1" "boot.panic_on_fail" 43 "init=${pkgs.writeScript "mini-init.sh" miniInit}" 44 ]; 45 46 fileSystems."/" = { 47 device = "vboxshare"; 48 fsType = "vboxsf"; 49 }; 50 51 virtualisation.virtualbox.guest.enable = true; 52 53 boot.initrd.kernelModules = [ 54 "af_packet" "vboxsf" 55 "virtio" "virtio_pci" "virtio_ring" "virtio_net" "vboxguest" 56 ]; 57 58 boot.initrd.extraUtilsCommands = '' 59 copy_bin_and_libs "${guestAdditions}/bin/mount.vboxsf" 60 copy_bin_and_libs "${pkgs.utillinux}/bin/unshare" 61 ${(attrs.extraUtilsCommands or (const "")) pkgs} 62 ''; 63 64 boot.initrd.postMountCommands = '' 65 touch /mnt-root/boot-done 66 hostname "${vmName}" 67 mkdir -p /nix/store 68 unshare -m ${escapeShellArg pkgs.stdenv.shell} -c ' 69 mount -t vboxsf nixstore /nix/store 70 exec "$stage2Init" 71 ' 72 poweroff -f 73 ''; 74 75 system.requiredKernelConfig = with config.lib.kernelConfig; [ 76 (isYes "SERIAL_8250_CONSOLE") 77 (isYes "SERIAL_8250") 78 ]; 79 }; 80 81 mkLog = logfile: tag: let 82 rotated = map (i: "${logfile}.${toString i}") (range 1 9); 83 all = concatMapStringsSep " " (f: "\"${f}\"") ([logfile] ++ rotated); 84 logcmd = "tail -F ${all} 2> /dev/null | logger -t \"${tag}\""; 85 in optionalString debug "$machine->execute(ru '${logcmd} & disown');"; 86 87 testVM = vmName: vmScript: let 88 cfg = (import ../lib/eval-config.nix { 89 system = "i686-linux"; 90 modules = [ 91 ../modules/profiles/minimal.nix 92 (testVMConfig vmName vmScript) 93 ]; 94 }).config; 95 in pkgs.vmTools.runInLinuxVM (pkgs.runCommand "virtualbox-image" { 96 preVM = '' 97 mkdir -p "$out" 98 diskImage="$(pwd)/qimage" 99 ${pkgs.vmTools.qemu}/bin/qemu-img create -f raw "$diskImage" 100M 100 ''; 101 102 postVM = '' 103 echo "creating VirtualBox disk image..." 104 ${pkgs.vmTools.qemu}/bin/qemu-img convert -f raw -O vdi \ 105 "$diskImage" "$out/disk.vdi" 106 ''; 107 108 buildInputs = [ pkgs.utillinux pkgs.perl ]; 109 } '' 110 ${pkgs.parted}/sbin/parted --script /dev/vda mklabel msdos 111 ${pkgs.parted}/sbin/parted --script /dev/vda -- mkpart primary ext2 1M -1s 112 ${pkgs.e2fsprogs}/sbin/mkfs.ext4 /dev/vda1 113 ${pkgs.e2fsprogs}/sbin/tune2fs -c 0 -i 0 /dev/vda1 114 mkdir /mnt 115 mount /dev/vda1 /mnt 116 cp "${cfg.system.build.kernel}/bzImage" /mnt/linux 117 cp "${cfg.system.build.initialRamdisk}/initrd" /mnt/initrd 118 119 ${pkgs.grub2}/bin/grub-install --boot-directory=/mnt /dev/vda 120 121 cat > /mnt/grub/grub.cfg <<GRUB 122 set root=hd0,1 123 linux /linux ${concatStringsSep " " cfg.boot.kernelParams} 124 initrd /initrd 125 boot 126 GRUB 127 umount /mnt 128 ''); 129 130 createVM = name: attrs: let 131 mkFlags = concatStringsSep " "; 132 133 sharePath = "/home/alice/vboxshare-${name}"; 134 135 createFlags = mkFlags [ 136 "--ostype Linux26" 137 "--register" 138 ]; 139 140 vmFlags = mkFlags ([ 141 "--uart1 0x3F8 4" 142 "--uartmode1 client /run/virtualbox-log-${name}.sock" 143 "--memory 768" 144 "--audio none" 145 ] ++ (attrs.vmFlags or [])); 146 147 controllerFlags = mkFlags [ 148 "--name SATA" 149 "--add sata" 150 "--bootable on" 151 "--hostiocache on" 152 ]; 153 154 diskFlags = mkFlags [ 155 "--storagectl SATA" 156 "--port 0" 157 "--device 0" 158 "--type hdd" 159 "--mtype immutable" 160 "--medium ${testVM name attrs}/disk.vdi" 161 ]; 162 163 sharedFlags = mkFlags [ 164 "--name vboxshare" 165 "--hostpath ${sharePath}" 166 ]; 167 168 nixstoreFlags = mkFlags [ 169 "--name nixstore" 170 "--hostpath /nix/store" 171 "--readonly" 172 ]; 173 in { 174 machine = { 175 systemd.sockets."vboxtestlog-${name}" = { 176 description = "VirtualBox Test Machine Log Socket For ${name}"; 177 wantedBy = [ "sockets.target" ]; 178 before = [ "multi-user.target" ]; 179 socketConfig.ListenStream = "/run/virtualbox-log-${name}.sock"; 180 socketConfig.Accept = true; 181 }; 182 183 systemd.services."vboxtestlog-${name}@" = { 184 description = "VirtualBox Test Machine Log For ${name}"; 185 serviceConfig.StandardInput = "socket"; 186 serviceConfig.StandardOutput = "syslog"; 187 serviceConfig.SyslogIdentifier = "GUEST-${name}"; 188 serviceConfig.ExecStart = "${pkgs.coreutils}/bin/cat"; 189 }; 190 }; 191 192 testSubs = '' 193 my ${"$" + name}_sharepath = '${sharePath}'; 194 195 sub checkRunning_${name} { 196 my $cmd = 'VBoxManage list runningvms | grep -q "^\"${name}\""'; 197 my ($status, $out) = $machine->execute(ru $cmd); 198 return $status == 0; 199 } 200 201 sub cleanup_${name} { 202 $machine->execute(ru "VBoxManage controlvm ${name} poweroff") 203 if checkRunning_${name}; 204 $machine->succeed("rm -rf ${sharePath}"); 205 $machine->succeed("mkdir -p ${sharePath}"); 206 $machine->succeed("chown alice.users ${sharePath}"); 207 } 208 209 sub createVM_${name} { 210 vbm("createvm --name ${name} ${createFlags}"); 211 vbm("modifyvm ${name} ${vmFlags}"); 212 vbm("setextradata ${name} VBoxInternal/PDM/HaltOnReset 1"); 213 vbm("storagectl ${name} ${controllerFlags}"); 214 vbm("storageattach ${name} ${diskFlags}"); 215 vbm("sharedfolder add ${name} ${sharedFlags}"); 216 vbm("sharedfolder add ${name} ${nixstoreFlags}"); 217 cleanup_${name}; 218 219 ${mkLog "$HOME/VirtualBox VMs/${name}/Logs/VBox.log" "HOST-${name}"} 220 } 221 222 sub destroyVM_${name} { 223 cleanup_${name}; 224 vbm("unregistervm ${name} --delete"); 225 } 226 227 sub waitForVMBoot_${name} { 228 $machine->execute(ru( 229 'set -e; i=0; '. 230 'while ! test -e ${sharePath}/boot-done; do '. 231 'sleep 10; i=$(($i + 10)); [ $i -le 3600 ]; '. 232 'VBoxManage list runningvms | grep -q "^\"${name}\""; '. 233 'done' 234 )); 235 } 236 237 sub waitForIP_${name} ($) { 238 my $property = "/VirtualBox/GuestInfo/Net/$_[0]/V4/IP"; 239 my $getip = "VBoxManage guestproperty get ${name} $property | ". 240 "sed -n -e 's/^Value: //p'"; 241 my $ip = $machine->succeed(ru( 242 'for i in $(seq 1000); do '. 243 'if ipaddr="$('.$getip.')" && [ -n "$ipaddr" ]; then '. 244 'echo "$ipaddr"; exit 0; '. 245 'fi; '. 246 'sleep 1; '. 247 'done; '. 248 'echo "Could not get IPv4 address for ${name}!" >&2; '. 249 'exit 1' 250 )); 251 chomp $ip; 252 return $ip; 253 } 254 255 sub waitForStartup_${name} { 256 for (my $i = 0; $i <= 120; $i += 10) { 257 $machine->sleep(10); 258 return if checkRunning_${name}; 259 eval { $_[0]->() } if defined $_[0]; 260 } 261 die "VirtualBox VM didn't start up within 2 minutes"; 262 } 263 264 sub waitForShutdown_${name} { 265 for (my $i = 0; $i <= 120; $i += 10) { 266 $machine->sleep(10); 267 return unless checkRunning_${name}; 268 } 269 die "VirtualBox VM didn't shut down within 2 minutes"; 270 } 271 272 sub shutdownVM_${name} { 273 $machine->succeed(ru "touch ${sharePath}/shutdown"); 274 $machine->execute( 275 'set -e; i=0; '. 276 'while test -e ${sharePath}/shutdown '. 277 ' -o -e ${sharePath}/boot-done; do '. 278 'sleep 1; i=$(($i + 1)); [ $i -le 3600 ]; '. 279 'done' 280 ); 281 waitForShutdown_${name}; 282 } 283 ''; 284 }; 285 286 hostonlyVMFlags = [ 287 "--nictype1 virtio" 288 "--nictype2 virtio" 289 "--nic2 hostonly" 290 "--hostonlyadapter2 vboxnet0" 291 ]; 292 293 dhcpScript = pkgs: '' 294 ${pkgs.dhcp}/bin/dhclient \ 295 -lf /run/dhcp.leases \ 296 -pf /run/dhclient.pid \ 297 -v eth0 eth1 298 299 otherIP="$(${pkgs.netcat}/bin/nc -l 1234 || :)" 300 ${pkgs.iputils}/bin/ping -I eth1 -c1 "$otherIP" 301 echo "$otherIP reachable" | ${pkgs.netcat}/bin/nc -l 5678 || : 302 ''; 303 304 sysdDetectVirt = pkgs: '' 305 ${pkgs.systemd}/bin/systemd-detect-virt > /mnt-root/result 306 ''; 307 308 vboxVMs = mapAttrs createVM { 309 simple = {}; 310 311 detectvirt.vmScript = sysdDetectVirt; 312 313 test1.vmFlags = hostonlyVMFlags; 314 test1.vmScript = dhcpScript; 315 316 test2.vmFlags = hostonlyVMFlags; 317 test2.vmScript = dhcpScript; 318 319 headless.virtualisation.virtualbox.headless = true; 320 headless.services.xserver.enable = false; 321 }; 322 323 mkVBoxTest = name: testScript: makeTest { 324 name = "virtualbox-${name}"; 325 326 machine = { lib, config, ... }: { 327 imports = let 328 mkVMConf = name: val: val.machine // { key = "${name}-config"; }; 329 vmConfigs = mapAttrsToList mkVMConf vboxVMs; 330 in [ ./common/user-account.nix ./common/x11.nix ] ++ vmConfigs; 331 virtualisation.memorySize = 2048; 332 virtualisation.virtualbox.host.enable = true; 333 services.xserver.displayManager.auto.user = "alice"; 334 users.extraUsers.alice.extraGroups = let 335 inherit (config.virtualisation.virtualbox.host) enableHardening; 336 in lib.mkIf enableHardening (lib.singleton "vboxusers"); 337 }; 338 339 testScript = '' 340 sub ru ($) { 341 my $esc = $_[0] =~ s/'/'\\${"'"}'/gr; 342 return "su - alice -c '$esc'"; 343 } 344 345 sub vbm { 346 $machine->succeed(ru("VBoxManage ".$_[0])); 347 }; 348 349 sub removeUUIDs { 350 return join("\n", grep { $_ !~ /^UUID:/ } split(/\n/, $_[0]))."\n"; 351 } 352 353 ${concatStrings (mapAttrsToList (_: getAttr "testSubs") vboxVMs)} 354 355 $machine->waitForX; 356 357 ${mkLog "$HOME/.config/VirtualBox/VBoxSVC.log" "HOST-SVC"} 358 359 ${testScript} 360 ''; 361 362 meta = with pkgs.stdenv.lib.maintainers; { 363 maintainers = [ aszlig wkennington ]; 364 }; 365 }; 366 367in mapAttrs mkVBoxTest { 368 simple-gui = '' 369 createVM_simple; 370 $machine->succeed(ru "VirtualBox &"); 371 $machine->waitUntilSucceeds( 372 ru "xprop -name 'Oracle VM VirtualBox Manager'" 373 ); 374 $machine->sleep(5); 375 $machine->screenshot("gui_manager_started"); 376 $machine->sendKeys("ret"); 377 $machine->screenshot("gui_manager_sent_startup"); 378 waitForStartup_simple (sub { 379 $machine->sendKeys("ret"); 380 }); 381 $machine->screenshot("gui_started"); 382 waitForVMBoot_simple; 383 $machine->screenshot("gui_booted"); 384 shutdownVM_simple; 385 $machine->sleep(5); 386 $machine->screenshot("gui_stopped"); 387 $machine->sendKeys("ctrl-q"); 388 $machine->sleep(5); 389 $machine->screenshot("gui_manager_stopped"); 390 destroyVM_simple; 391 ''; 392 393 simple-cli = '' 394 createVM_simple; 395 vbm("startvm simple"); 396 waitForStartup_simple; 397 $machine->screenshot("cli_started"); 398 waitForVMBoot_simple; 399 $machine->screenshot("cli_booted"); 400 401 $machine->nest("Checking for privilege escalation", sub { 402 $machine->fail("test -e '/root/VirtualBox VMs'"); 403 $machine->fail("test -e '/root/.config/VirtualBox'"); 404 $machine->succeed("test -e '/home/alice/VirtualBox VMs'"); 405 }); 406 407 shutdownVM_simple; 408 destroyVM_simple; 409 ''; 410 411 headless = '' 412 createVM_headless; 413 $machine->succeed(ru("VBoxHeadless --startvm headless & disown %1")); 414 waitForStartup_headless; 415 waitForVMBoot_headless; 416 shutdownVM_headless; 417 destroyVM_headless; 418 ''; 419 420 host-usb-permissions = '' 421 my $userUSB = removeUUIDs vbm("list usbhost"); 422 print STDERR $userUSB; 423 my $rootUSB = removeUUIDs $machine->succeed("VBoxManage list usbhost"); 424 print STDERR $rootUSB; 425 426 die "USB host devices differ for root and normal user" 427 if $userUSB ne $rootUSB; 428 die "No USB host devices found" if $userUSB =~ /<none>/; 429 ''; 430 431 systemd-detect-virt = '' 432 createVM_detectvirt; 433 vbm("startvm detectvirt"); 434 waitForStartup_detectvirt; 435 waitForVMBoot_detectvirt; 436 shutdownVM_detectvirt; 437 my $result = $machine->succeed("cat '$detectvirt_sharepath/result'"); 438 chomp $result; 439 destroyVM_detectvirt; 440 die "systemd-detect-virt returned \"$result\" instead of \"oracle\"" 441 if $result ne "oracle"; 442 ''; 443 444 net-hostonlyif = '' 445 createVM_test1; 446 createVM_test2; 447 448 vbm("startvm test1"); 449 waitForStartup_test1; 450 waitForVMBoot_test1; 451 452 vbm("startvm test2"); 453 waitForStartup_test2; 454 waitForVMBoot_test2; 455 456 $machine->screenshot("net_booted"); 457 458 my $test1IP = waitForIP_test1 1; 459 my $test2IP = waitForIP_test2 1; 460 461 $machine->succeed("echo '$test2IP' | nc -N '$test1IP' 1234"); 462 $machine->succeed("echo '$test1IP' | nc -N '$test2IP' 1234"); 463 464 $machine->waitUntilSucceeds("nc -N '$test1IP' 5678 < /dev/null >&2"); 465 $machine->waitUntilSucceeds("nc -N '$test2IP' 5678 < /dev/null >&2"); 466 467 shutdownVM_test1; 468 shutdownVM_test2; 469 470 destroyVM_test1; 471 destroyVM_test2; 472 ''; 473}