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