From 7530b8f5c19919a37e122bcce12dcf842624506a Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Wed, 1 Nov 2023 11:44:00 -0400 Subject: [PATCH] Add E2E testing for usb (#1214) * e2e test usb mounting * no poweroff * no start * drive usb via sync server since its up sudo santactl status sudo? * revert nostart/nopoweroff * bump VMCLI minimum os version --- .github/workflows/e2e.yml | 2 + Testing/integration/BUILD | 5 ++ .../MacOSVirtualMachineConfigurationHelper.h | 3 +- .../MacOSVirtualMachineConfigurationHelper.m | 28 +++++++++- Testing/integration/VM/VMCLI/BUILD | 2 +- Testing/integration/VM/VMCLI/main.m | 11 +++- Testing/integration/VM/VMGUI/AppDelegate.m | 34 ++++++++---- Testing/integration/actions/start_vm.py | 5 +- Testing/integration/dismiss_usb_popup.scpt | 10 ++++ Testing/integration/test_usb.sh | 55 +++++++++++++++++++ 10 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 Testing/integration/dismiss_usb_popup.scpt create mode 100755 Testing/integration/test_usb.sh diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 1e6968ac7..adc98e71a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -39,6 +39,8 @@ jobs: run: ./Testing/integration/test_config_changes.sh - name: Test sync server changes run: ./Testing/integration/test_sync_changes.sh + - name: Test USB blocking + run: ./Testing/integration/test_usb.sh - name: Poweroff if: ${{ always() }} run: sudo shutdown -h +1 diff --git a/Testing/integration/BUILD b/Testing/integration/BUILD index e50b36a98..c8cc4c3a6 100644 --- a/Testing/integration/BUILD +++ b/Testing/integration/BUILD @@ -37,3 +37,8 @@ run_command( name = "dismiss_santa_popup", cmd = "osascript $${BUILD_WORKSPACE_DIRECTORY}/Testing/integration/dismiss_santa_popup.scpt", ) + +run_command( + name = "dismiss_usb_popup", + cmd = "osascript $${BUILD_WORKSPACE_DIRECTORY}/Testing/integration/dismiss_usb_popup.scpt", +) diff --git a/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.h b/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.h index 50734b1f6..b8bf4110a 100644 --- a/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.h +++ b/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.h @@ -44,7 +44,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. (NSString *)bundleDir; + (VZVirtualMachine *)createVirtualMachineWithBundleDir:(NSString *)bundleDir; + (VZVirtualMachine *)createVirtualMachineWithBundleDir:(NSString *)bundleDir - roDisk:(NSString *)roDisk; + roDisk:(NSString *)roDisk + usbDisk:(NSString *)usbDisk; @end diff --git a/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.m b/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.m index c0362993a..158ae4d6b 100644 --- a/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.m +++ b/Testing/integration/VM/Common/MacOSVirtualMachineConfigurationHelper.m @@ -75,6 +75,21 @@ + (VZVirtioBlockDeviceConfiguration *)createBlockDeviceConfigurationForDisk:(NSU return disk; } ++ (VZUSBMassStorageDeviceConfiguration *)createUSBDeviceConfigurationForDisk:(NSURL *)diskURL + readOnly:(BOOL)ro { + NSError *error; + VZDiskImageStorageDeviceAttachment *diskAttachment = + [[VZDiskImageStorageDeviceAttachment alloc] initWithURL:diskURL readOnly:ro error:&error]; + if (!diskAttachment) { + NSLog(@"Failed to create VZDiskImageStorageDeviceAttachment: %@", error.localizedDescription); + exit(-1); + } + VZUSBMassStorageDeviceConfiguration *disk = + [[VZUSBMassStorageDeviceConfiguration alloc] initWithAttachment:diskAttachment]; + + return disk; +} + + (VZVirtioNetworkDeviceConfiguration *)createNetworkDeviceConfiguration { VZNATNetworkDeviceAttachment *natAttachment = [[VZNATNetworkDeviceAttachment alloc] init]; VZVirtioNetworkDeviceConfiguration *networkConfiguration = @@ -189,15 +204,22 @@ + (VZVirtualMachineConfiguration *)createBaseVirtualMachineConfigurationWithBund } + (VZVirtualMachine *)createVirtualMachineWithBundleDir:(NSString *)bundleDir - roDisk:(NSString *)roDisk { + roDisk:(NSString *)roDisk + usbDisk:(NSString*)usbDisk { VZVirtualMachineConfiguration *configuration = [self createBaseVirtualMachineConfigurationWithBundleDir:bundleDir]; - if (roDisk) { + if (roDisk && ![roDisk isEqualToString:@""]) { configuration.storageDevices = [configuration.storageDevices arrayByAddingObject:[self createBlockDeviceConfigurationForDisk:[[NSURL alloc] initFileURLWithPath:roDisk] readOnly:YES]]; } + if (usbDisk && ![usbDisk isEqualToString:@""]) { + configuration.storageDevices = [configuration.storageDevices + arrayByAddingObject:[self createUSBDeviceConfigurationForDisk:[[NSURL alloc] + initFileURLWithPath:usbDisk] + readOnly:NO]]; + } NSError *error; if (![configuration validateWithError:&error]) { NSLog(@"Failed to validate configuration: %@", error.localizedDescription); @@ -208,7 +230,7 @@ + (VZVirtualMachine *)createVirtualMachineWithBundleDir:(NSString *)bundleDir } + (VZVirtualMachine *)createVirtualMachineWithBundleDir:(NSString *)bundleDir { - return [self createVirtualMachineWithBundleDir:bundleDir roDisk:nil]; + return [self createVirtualMachineWithBundleDir:bundleDir roDisk:nil usbDisk:nil]; } @end diff --git a/Testing/integration/VM/VMCLI/BUILD b/Testing/integration/VM/VMCLI/BUILD index 8d26db9d7..cd14eff42 100644 --- a/Testing/integration/VM/VMCLI/BUILD +++ b/Testing/integration/VM/VMCLI/BUILD @@ -22,7 +22,7 @@ macos_application( bundle_id = "com.google.santa.e2e.vmcli", entitlements = "//Testing/integration/VM/Common:entitlements", infoplists = ["//Testing/integration/VM/Common:plist"], - minimum_os_version = "12.0", + minimum_os_version = "13.0", deps = [ ":vmcli_lib", ], diff --git a/Testing/integration/VM/VMCLI/main.m b/Testing/integration/VM/VMCLI/main.m index f22e18f0e..dafd0d971 100644 --- a/Testing/integration/VM/VMCLI/main.m +++ b/Testing/integration/VM/VMCLI/main.m @@ -40,8 +40,8 @@ - (void)guestDidStopVirtualMachine:(VZVirtualMachine *)virtualMachine { @end int main(int argc, const char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: %s bundle_path", argv[0]); + if (argc < 2) { + fprintf(stderr, "Usage: %s bundle_path [usb_disk]", argv[0]); exit(-1); } @@ -50,8 +50,13 @@ int main(int argc, const char *argv[]) { bundleDir = [bundleDir stringByAppendingString:@"/"]; } + NSString *usbDisk; + if (argc > 2) { + usbDisk = @(argv[2]); + } + VZVirtualMachine *vm = - [MacOSVirtualMachineConfigurationHelper createVirtualMachineWithBundleDir:bundleDir]; + [MacOSVirtualMachineConfigurationHelper createVirtualMachineWithBundleDir:bundleDir roDisk:nil usbDisk:usbDisk]; MacOSVirtualMachineDelegate *delegate = [MacOSVirtualMachineDelegate new]; vm.delegate = delegate; diff --git a/Testing/integration/VM/VMGUI/AppDelegate.m b/Testing/integration/VM/VMGUI/AppDelegate.m index fb5c58789..506c31607 100644 --- a/Testing/integration/VM/VMGUI/AppDelegate.m +++ b/Testing/integration/VM/VMGUI/AppDelegate.m @@ -47,28 +47,37 @@ @implementation AppDelegate { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { #ifdef __arm64__ dispatch_async(dispatch_get_main_queue(), ^{ - NSArray *args = [[NSProcessInfo processInfo] arguments]; + NSMutableArray *args = [NSMutableArray arrayWithArray:[[NSProcessInfo processInfo] arguments]]; VZMacOSVirtualMachineStartOptions *options = [VZMacOSVirtualMachineStartOptions new]; NSString *bundleDir; NSString *roDisk; + NSString *usbDisk; - if (args.count < 2) { - abortWithErrorMessage(@"Usage: VMGUI [-recovery] bundle_path [ro_disk]"); + [args removeObjectAtIndex:0]; + + if (args.count == 0) { + abortWithErrorMessage(@"Usage: VMGUI [-recovery] bundle_path [ro_disk] [usb_disk]"); } - int bundleArg = 1; - if ([args[1] isEqualToString:@"-recovery"]) { + if ([args[0] isEqualToString:@"-recovery"]) { options.startUpFromMacOSRecovery = YES; - if (args.count < 3) { - abortWithErrorMessage(@"Usage: VMGUI [-recovery] bundle_path [ro_disk]"); + [args removeObjectAtIndex:0]; + if (args.count == 0) { + abortWithErrorMessage(@"Usage: VMGUI [-recovery] bundle_path [ro_disk] [usb_disk]"); } - bundleArg = 2; } - bundleDir = args[bundleArg]; - if (args.count > bundleArg + 1) { - roDisk = args[bundleArg + 1]; + bundleDir = args[0]; + [args removeObjectAtIndex:0]; + if (args.count) { + roDisk = args[0]; + [args removeObjectAtIndex:0]; + } + + if (args.count) { + usbDisk = args[0]; + [args removeObjectAtIndex:0]; } if (![bundleDir hasSuffix:@"/"]) { @@ -77,7 +86,8 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { VZVirtualMachine *vm = [MacOSVirtualMachineConfigurationHelper createVirtualMachineWithBundleDir:bundleDir - roDisk:roDisk]; + roDisk:roDisk + usbDisk:usbDisk]; self->_virtualMachine = vm; self->_delegate = [MacOSVirtualMachineDelegate new]; diff --git a/Testing/integration/actions/start_vm.py b/Testing/integration/actions/start_vm.py index a33c87f45..292d286eb 100644 --- a/Testing/integration/actions/start_vm.py +++ b/Testing/integration/actions/start_vm.py @@ -105,9 +105,12 @@ print(f"Snapshot: {snapshot_dir}") # COW copy the image to this tempdir subprocess.check_output(["cp", "-rc", extracted_path, snapshot_dir]) + # Create a disk image for USB testing + usb_dmg = pathlib.Path(snapshot_dir) / "usb.dmg" + subprocess.check_output(["hdiutil", "create", "-size", "100M", "-fs", "ExFAT", "-volname", "USB", usb_dmg]) try: subprocess.check_output( - [VMCLI, pathlib.Path(snapshot_dir) / extracted_path.name], + [VMCLI, pathlib.Path(snapshot_dir) / extracted_path.name, usb_dmg], timeout=TIMEOUT, ) except subprocess.TimeoutExpired: diff --git a/Testing/integration/dismiss_usb_popup.scpt b/Testing/integration/dismiss_usb_popup.scpt new file mode 100644 index 000000000..41947fa25 --- /dev/null +++ b/Testing/integration/dismiss_usb_popup.scpt @@ -0,0 +1,10 @@ +-- Dismiss the "disk remounted" popup from Santa. +-- This is run inside test VMs. + +on run argv + tell application "System Events" + tell process "Santa" + click button 1 of group 1 of window 1 + end tell + end tell +end run diff --git a/Testing/integration/test_usb.sh b/Testing/integration/test_usb.sh new file mode 100755 index 000000000..9136d68b7 --- /dev/null +++ b/Testing/integration/test_usb.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -xe + +bazel run //Testing/integration:install_profile -- Testing/integration/configs/default.mobileconfig +sudo diskutil unmount force USB || true + +killall moroz +/tmp/moroz -configs="$GITHUB_WORKSPACE/Testing/integration/configs/moroz_default/global.toml" -use-tls=false & +sudo santactl sync --debug +if [[ "$(sudo santactl status --json | jq .daemon.block_usb)" != "false" ]]; then + echo "USB blocking enabled with minimal config" >&2 + exit 1 +fi + +sudo diskutil mount USB +echo test > /Volumes/USB/test +sync +sudo diskutil unmount force USB + +killall moroz +/tmp/moroz -configs="$GITHUB_WORKSPACE/Testing/integration/configs/moroz_changed/global.toml" -use-tls=false & +sudo santactl sync --debug +if [[ "$(sudo santactl status --json | jq .daemon.block_usb)" != "true" ]]; then + echo "USB blocking config change didnt take effect" >&2 + exit 1 +fi + +set +e +sudo diskutil mount USB +blocked=$? +set -e + +if [[ $blocked == 0 ]]; then + echo "R/W mount succeeded with USB blocking enabled" >&2 + exit 1 +fi + +sleep 5 + +# Santa should have remounted the disk RO for us. Check that it did. +bazel run //Testing/integration:dismiss_usb_popup +cat /Volumes/USB/test + +sudo diskutil unmount force USB + +# Ensure things can still be normally mounted if mount flags match remount opts. +set +e +sudo diskutil mount -mountOptions ro,noexec USB +blocked=$? +set -e + +if [[ $blocked != 0 ]]; then + echo "RO+noexec mount failed with USB blocking enabled" >&2 + exit 1 +fi