From 80f60f2ae98ebfd3723124dd115f283193fc722c Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Fri, 27 Oct 2023 17:05:40 -0400 Subject: [PATCH 1/6] e2e test usb mounting --- .github/workflows/e2e.yml | 2 + Testing/integration/BUILD | 5 ++ .../MacOSVirtualMachineConfigurationHelper.h | 3 +- .../MacOSVirtualMachineConfigurationHelper.m | 28 +++++++++-- 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 | 47 +++++++++++++++++++ 9 files changed, 125 insertions(+), 20 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/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..032bbfee0 --- /dev/null +++ b/Testing/integration/test_usb.sh @@ -0,0 +1,47 @@ +#!/bin/bash +set -xe + +bazel run //Testing/integration:install_profile -- Testing/integration/configs/default.mobileconfig +if [[ "$(santactl status --json | jq .daemon.block_usb)" != "false" ]]; then + echo "USB blocking enabled with minimal config" >&2 + exit 1 +fi + +diskutil mount USB +echo test > /Volumes/USB/test +diskutil unmount USB + +bazel run //Testing/integration:install_profile -- Testing/integration/configs/usb-block.mobileconfig +if [[ "$(santactl status --json | jq .daemon.block_usb)" != "true" ]]; then + echo "USB blocking config change didnt take effect" >&2 + exit 1 +fi + +set +e +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 + +diskutil unmount USB + +# Ensure things can still be normally mounted if mount flags match remount opts. +set +e +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 From b9045ca2094d37be3b288580f7015ed0c8d873aa Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Mon, 30 Oct 2023 18:04:06 -0400 Subject: [PATCH 2/6] no poweroff --- .github/workflows/e2e.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index adc98e71a..d2dc070d3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -41,6 +41,6 @@ jobs: 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 + # - name: Poweroff + # if: ${{ always() }} + # run: sudo shutdown -h +1 From 6d478bd250955f3c23ba853b6ca1e0aab5dfb69d Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Mon, 30 Oct 2023 18:04:22 -0400 Subject: [PATCH 3/6] no start --- .github/workflows/e2e.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d2dc070d3..0a09d66d3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -6,12 +6,12 @@ on: workflow_dispatch: jobs: - start_vm: - runs-on: e2e-host - steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 - - name: Start VM - run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz + # start_vm: + # runs-on: e2e-host + # steps: + # - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 + # - name: Start VM + # run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz integration: runs-on: e2e-vm From 6c6d19c73512806ed9306dd24f1be1602e99a558 Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Mon, 30 Oct 2023 18:46:26 -0400 Subject: [PATCH 4/6] drive usb via sync server since its up sudo santactl status sudo? --- Testing/integration/test_usb.sh | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Testing/integration/test_usb.sh b/Testing/integration/test_usb.sh index 032bbfee0..9136d68b7 100755 --- a/Testing/integration/test_usb.sh +++ b/Testing/integration/test_usb.sh @@ -2,23 +2,31 @@ set -xe bazel run //Testing/integration:install_profile -- Testing/integration/configs/default.mobileconfig -if [[ "$(santactl status --json | jq .daemon.block_usb)" != "false" ]]; then +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 -diskutil mount USB +sudo diskutil mount USB echo test > /Volumes/USB/test -diskutil unmount USB +sync +sudo diskutil unmount force USB -bazel run //Testing/integration:install_profile -- Testing/integration/configs/usb-block.mobileconfig -if [[ "$(santactl status --json | jq .daemon.block_usb)" != "true" ]]; then +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 -diskutil mount USB +sudo diskutil mount USB blocked=$? set -e @@ -33,11 +41,11 @@ sleep 5 bazel run //Testing/integration:dismiss_usb_popup cat /Volumes/USB/test -diskutil unmount USB +sudo diskutil unmount force USB # Ensure things can still be normally mounted if mount flags match remount opts. set +e -diskutil mount -mountOptions ro,noexec USB +sudo diskutil mount -mountOptions ro,noexec USB blocked=$? set -e From 79feeb72fd99dea47e114f013e85fd59fbb079a4 Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Mon, 30 Oct 2023 19:20:24 -0400 Subject: [PATCH 5/6] revert nostart/nopoweroff --- .github/workflows/e2e.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0a09d66d3..adc98e71a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -6,12 +6,12 @@ on: workflow_dispatch: jobs: - # start_vm: - # runs-on: e2e-host - # steps: - # - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 - # - name: Start VM - # run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz + start_vm: + runs-on: e2e-host + steps: + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3 + - name: Start VM + run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz integration: runs-on: e2e-vm @@ -41,6 +41,6 @@ jobs: 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 + - name: Poweroff + if: ${{ always() }} + run: sudo shutdown -h +1 From 8ce03e92e4338bb3740d064bbbece6e1996241da Mon Sep 17 00:00:00 2001 From: Nick Gregory Date: Mon, 30 Oct 2023 19:25:00 -0400 Subject: [PATCH 6/6] bump VMCLI minimum os version --- Testing/integration/VM/VMCLI/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", ],