diff --git a/internal/hcs/schema2/chipset.go b/internal/hcs/schema2/chipset.go index ca75277a3f..93857da69f 100644 --- a/internal/hcs/schema2/chipset.go +++ b/internal/hcs/schema2/chipset.go @@ -24,4 +24,6 @@ type Chipset struct { // LinuxKernelDirect - Added in v2.2 Builds >=181117 LinuxKernelDirect *LinuxKernelDirect `json:"LinuxKernelDirect,omitempty"` + + FirmwareFile *FirmwareFile `json:"FirmwareFile,omitempty"` } diff --git a/internal/hcs/schema2/firmware.go b/internal/hcs/schema2/firmware.go new file mode 100644 index 0000000000..43cf8eebf4 --- /dev/null +++ b/internal/hcs/schema2/firmware.go @@ -0,0 +1,5 @@ +package hcsschema + +type FirmwareFile struct { + Parameters []byte `json:"Parameters,omitempty"` +} diff --git a/internal/hcs/schema2/windows_crash_reporting.go b/internal/hcs/schema2/windows_crash_reporting.go index 8ed7e566d6..ee85c43b3e 100644 --- a/internal/hcs/schema2/windows_crash_reporting.go +++ b/internal/hcs/schema2/windows_crash_reporting.go @@ -13,4 +13,6 @@ type WindowsCrashReporting struct { DumpFileName string `json:"DumpFileName,omitempty"` MaxDumpSize int64 `json:"MaxDumpSize,omitempty"` + + DumpType string `json:"DumpType,omitempty"` } diff --git a/internal/layers/wcow_parse.go b/internal/layers/wcow_parse.go index 4e38305ed2..3dfbc6e763 100644 --- a/internal/layers/wcow_parse.go +++ b/internal/layers/wcow_parse.go @@ -263,8 +263,11 @@ func GetWCOWUVMBootFilesFromLayers(ctx context.Context, rootfs []*types.Mount, l } } return &uvm.WCOWBootFiles{ - OSFilesPath: filepath.Join(uvmFolder, `UtilityVM\Files`), - OSRelativeBootDirPath: `\EFI\Microsoft\Boot`, - ScratchVHDPath: scratchVHDPath, + BootType: uvm.VmbFSBoot, + VmbFSFiles: &uvm.VmbFSBootFiles{ + OSFilesPath: filepath.Join(uvmFolder, `UtilityVM\Files`), + OSRelativeBootDirPath: `\EFI\Microsoft\Boot`, + ScratchVHDPath: scratchVHDPath, + }, }, nil } diff --git a/internal/tools/uvmboot/conf_wcow.go b/internal/tools/uvmboot/conf_wcow.go new file mode 100644 index 0000000000..69d1d55212 --- /dev/null +++ b/internal/tools/uvmboot/conf_wcow.go @@ -0,0 +1,156 @@ +//go:build windows + +package main + +import ( + "context" + "os" + + "github.com/containerd/console" + "github.com/urfave/cli" + + "github.com/Microsoft/hcsshim/internal/cmd" + "github.com/Microsoft/hcsshim/internal/log" + "github.com/Microsoft/hcsshim/internal/uvm" +) + +const ( + confidentialArgName = "confidential" + vmgsFilePathArgName = "vmgs-path" + disableSBArgName = "disable-secure-boot" + isolationTypeArgName = "isolation-type" +) + +var ( + cwcowBootVHD string + cwcowEFIVHD string + cwcowScratchVHD string + cwcowEnableConfidential bool + cwcowVMGSPath string + cwcowDisableSecureBoot bool + cwcowIsolationMode string +) + +var cwcowCommand = cli.Command{ + Name: "cwcow", + Usage: "boot a confidential WCOW UVM", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "exec", + Usage: "Command to execute in the UVM.", + Destination: &wcowCommandLine, + }, + cli.BoolFlag{ + Name: "tty,t", + Usage: "create the process in the UVM with a TTY enabled", + Destination: &wcowUseTerminal, + }, + cli.StringFlag{ + Name: "efi-vhd", + Usage: "VHD at the provided path MUST have the EFI boot partition and be properly formatted for UEFI boot.", + Destination: &cwcowEFIVHD, + Required: true, + }, + cli.StringFlag{ + Name: "boot-cim-vhd", + Usage: "A VHD containing the block CIM that contains the OS files.", + Destination: &cwcowBootVHD, + Required: true, + }, + cli.StringFlag{ + Name: "scratch-vhd", + Usage: "A scratch VHD for the UVM", + Destination: &cwcowScratchVHD, + Required: true, + }, + cli.StringFlag{ + Name: vmgsFilePathArgName, + Usage: "VMGS file path (only applies when confidential mode is enabled). This option is only applicable in confidential mode.", + Destination: &cwcowVMGSPath, + Required: true, + }, + cli.BoolFlag{ + Name: disableSBArgName, + Usage: "Disables Secure Boot when running the UVM in confidential mode. This option is only applicable in confidential mode.", + Destination: &cwcowDisableSecureBoot, + }, + cli.StringFlag{ + Name: isolationTypeArgName, + Usage: "VM Isolation type (one of Disabled, GuestStateOnly, VirtualizationBasedSecurity, SecureNestedPaging or TrustDomain). Applicable only when using the confidential mode. This option is only applicable in confidential mode.", + Destination: &cwcowIsolationMode, + Required: true, + }, + }, + Action: func(c *cli.Context) error { + runMany(c, func(id string) error { + options := uvm.NewDefaultOptionsWCOW(id, "") + options.ProcessorCount = 2 + options.MemorySizeInMB = 2048 + options.AllowOvercommit = false + options.EnableDeferredCommit = false + options.DumpDirectoryPath = "C:\\crashdumps" + + // confidential specific options + options.SecurityPolicyEnabled = true + options.DisableSecureBoot = cwcowDisableSecureBoot + options.GuestStateFilePath = cwcowVMGSPath + options.IsolationType = cwcowIsolationMode + // always enable graphics console with uvmboot - helps with testing/debugging + options.EnableGraphicsConsole = true + options.BootFiles = &uvm.WCOWBootFiles{ + BootType: uvm.BlockCIMBoot, + BlockCIMFiles: &uvm.BlockCIMBootFiles{ + BootCIMVHDPath: cwcowBootVHD, + EFIVHDPath: cwcowEFIVHD, + ScratchVHDPath: cwcowScratchVHD, + }, + } + setGlobalOptions(c, options.Options) + tempDir, err := os.MkdirTemp("", "uvmboot") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + + vm, err := uvm.CreateWCOW(context.TODO(), options) + if err != nil { + return err + } + defer vm.Close() + if err := vm.Start(context.TODO()); err != nil { + return err + } + if wcowCommandLine != "" { + cmd := cmd.Command(vm, "cmd.exe", "/c", wcowCommandLine) + cmd.Spec.User.Username = `NT AUTHORITY\SYSTEM` + cmd.Log = log.L.Dup() + if wcowUseTerminal { + cmd.Spec.Terminal = true + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + con, err := console.ConsoleFromFile(os.Stdin) + if err == nil { + err = con.SetRaw() + if err != nil { + return err + } + defer func() { + _ = con.Reset() + }() + } + } else { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + } + err = cmd.Run() + if err != nil { + return err + } + } + _ = vm.Terminate(context.TODO()) + _ = vm.Wait() + return vm.ExitError() + }) + return nil + }, +} diff --git a/internal/tools/uvmboot/main.go b/internal/tools/uvmboot/main.go index 891012ec77..e242f8268c 100644 --- a/internal/tools/uvmboot/main.go +++ b/internal/tools/uvmboot/main.go @@ -86,6 +86,7 @@ func main() { app.Commands = []cli.Command{ lcowCommand, wcowCommand, + cwcowCommand, } app.Before = func(c *cli.Context) error { @@ -120,6 +121,11 @@ func setGlobalOptions(c *cli.Context, options *uvm.Options) { if c.GlobalIsSet(enableDeferredCommitArgName) { options.EnableDeferredCommit = c.GlobalBool(enableDeferredCommitArgName) } + if c.GlobalIsSet(enableDeferredCommitArgName) { + options.EnableDeferredCommit = c.GlobalBool(enableDeferredCommitArgName) + } + // Always set the console pipe in uvmboot, it helps with testing/debugging + options.ConsolePipe = "\\\\.\\pipe\\uvmpipe" } // todo: add a context here to propagate cancel/timeouts to runFunc uvm diff --git a/internal/uvm/create.go b/internal/uvm/create.go index d4c87e3594..2a78f83da8 100644 --- a/internal/uvm/create.go +++ b/internal/uvm/create.go @@ -122,6 +122,28 @@ type Options struct { NumaProcessorCounts []uint32 // NumaMemoryBlocksCounts are the number of memory blocks per vNUMA node. NumaMemoryBlocksCounts []uint64 + + EnableGraphicsConsole bool // If true, enable a graphics console for the utility VM + ConsolePipe string // The named pipe path to use for the serial console (COM1). eg \\.\pipe\vmpipe +} + +func verifyWCOWBootFiles(bootFiles *WCOWBootFiles) error { + if bootFiles.BootType == VmbFSBoot { + if bootFiles.VmbFSFiles == nil { + return fmt.Errorf("VmbFS boot files is empty") + } else if bootFiles.BlockCIMFiles != nil { + return fmt.Errorf("confidential boot files should be empty") + } + } else if bootFiles.BootType == BlockCIMBoot { + if bootFiles.BlockCIMFiles == nil { + return fmt.Errorf("Confidential boot files is empty") + } else if bootFiles.VmbFSFiles != nil { + return fmt.Errorf("VmbFS boot files should be empty") + } + } else { + return fmt.Errorf("invalid boot type specified") + } + return nil } // Verifies that the final UVM options are correct and supported. @@ -156,6 +178,12 @@ func verifyOptions(_ context.Context, options interface{}) error { if opts.SCSIControllerCount != 1 { return errors.New("exactly 1 SCSI controller is required for WCOW") } + if err := verifyWCOWBootFiles(opts.BootFiles); err != nil { + return err + } + if opts.SecurityPolicyEnabled && opts.GuestStateFilePath == "" { + return fmt.Errorf("GuestStateFilePath must be provided when enabling security policy") + } } return nil } diff --git a/internal/uvm/create_lcow.go b/internal/uvm/create_lcow.go index 545e6333fa..1f310f8d60 100644 --- a/internal/uvm/create_lcow.go +++ b/internal/uvm/create_lcow.go @@ -115,8 +115,6 @@ type OptionsLCOW struct { KernelDirect bool // Skip UEFI and boot directly to `kernel` RootFSFile string // Filename under `BootFilesPath` for the UVMs root file system. Defaults to `InitrdFile` KernelBootOptions string // Additional boot options for the kernel - EnableGraphicsConsole bool // If true, enable a graphics console for the utility VM - ConsolePipe string // The named pipe path to use for the serial console. eg \\.\pipe\vmpipe UseGuestConnection bool // Whether the HCS should connect to the UVM's GCS. Defaults to true ExecCommandLine string // The command line to exec from init. Defaults to GCS ForwardStdout bool // Whether stdout will be forwarded from the executed program. Defaults to false @@ -164,8 +162,6 @@ func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW { KernelDirect: kernelDirectSupported, RootFSFile: InitrdFile, KernelBootOptions: "", - EnableGraphicsConsole: false, - ConsolePipe: "", UseGuestConnection: true, ExecCommandLine: fmt.Sprintf("/bin/gcs -v4 -log-format json -loglevel %s", logrus.StandardLogger().Level.String()), ForwardStdout: false, diff --git a/internal/uvm/create_wcow.go b/internal/uvm/create_wcow.go index 05b4a77aae..098753599c 100644 --- a/internal/uvm/create_wcow.go +++ b/internal/uvm/create_wcow.go @@ -28,9 +28,21 @@ import ( "github.com/Microsoft/hcsshim/osversion" ) +type ConfidentialWCOWOptions struct { + GuestStateFilePath string // The vmgs file path + SecurityPolicyEnabled bool // Set when there is a security policy to apply on actual SNP hardware, use this rathen than checking the string length + SecurityPolicy string // Optional security policy + + /* Below options are only included for testing/debugging purposes - shouldn't be used in regular scenarios */ + IsolationType string + DisableSecureBoot bool + FirmwareParameters string +} + // OptionsWCOW are the set of options passed to CreateWCOW() to create a utility vm. type OptionsWCOW struct { *Options + *ConfidentialWCOWOptions BootFiles *WCOWBootFiles @@ -44,6 +56,15 @@ type OptionsWCOW struct { AdditionalRegistryKeys []hcsschema.RegistryValue } +// WindowsSidecarGcsHvsockServiceID is the hvsock service ID that the Windows GCS +// sidecar will connect to. This is only used in the confidential mode. +var windowsSidecarGcsHvsockServiceID = guid.GUID{ + Data1: 0xae8da506, + Data2: 0xa019, + Data3: 0x4553, + Data4: [8]uint8{0xa5, 0x2b, 0x90, 0x2b, 0xc0, 0xfa, 0x04, 0x11}, +} + // NewDefaultOptionsWCOW creates the default options for a bootable version of // WCOW. The caller `MUST` set the `BootFiles` on the returned value. // @@ -53,18 +74,24 @@ type OptionsWCOW struct { // executable files name. func NewDefaultOptionsWCOW(id, owner string) *OptionsWCOW { return &OptionsWCOW{ - Options: newDefaultOptions(id, owner), - AdditionalRegistryKeys: []hcsschema.RegistryValue{}, + Options: newDefaultOptions(id, owner), + AdditionalRegistryKeys: []hcsschema.RegistryValue{}, + ConfidentialWCOWOptions: &ConfidentialWCOWOptions{}, } } -func (uvm *UtilityVM) startExternalGcsListener(ctx context.Context) error { +// startExternalGcsListener connects to the GCS service running inside the +// UVM. gcsServiceID can either be the service ID of the default GCS that is present in +// all UtilityVMs or it can be the service ID of the sidecar GCS that is used mostly in +// confidential mode. +func (uvm *UtilityVM) startExternalGcsListener(ctx context.Context, gcsServiceID guid.GUID) error { log.G(ctx).WithField("vmID", uvm.runtimeID).Debug("Using external GCS bridge") l, err := winio.ListenHvsock(&winio.HvsockAddr{ VMID: uvm.runtimeID, - ServiceID: gcs.WindowsGcsHvsockServiceID, + ServiceID: gcsServiceID, }) + if err != nil { return err } @@ -72,7 +99,7 @@ func (uvm *UtilityVM) startExternalGcsListener(ctx context.Context) error { return nil } -func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (*hcsschema.ComputeSystem, error) { +func prepareConfigDocCommon(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (*hcsschema.ComputeSystem, error) { processorTopology, err := processorinfo.HostProcessorInfo(ctx) if err != nil { return nil, fmt.Errorf("failed to get host processor information: %w", err) @@ -85,20 +112,6 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (* // Align the requested memory size. memorySizeInMB := uvm.normalizeMemorySize(ctx, opts.MemorySizeInMB) - // UVM rootfs share is readonly. - vsmbOpts := uvm.DefaultVSMBOptions(true) - vsmbOpts.TakeBackupPrivilege = true - virtualSMB := &hcsschema.VirtualSmb{ - DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere - Shares: []hcsschema.VirtualSmbShare{ - { - Name: "os", - Path: opts.BootFiles.OSFilesPath, - Options: vsmbOpts, - }, - }, - } - var registryChanges hcsschema.RegistryChanges // We're getting asked to setup local dump collection for WCOW. We need to: // @@ -188,15 +201,7 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (* SchemaVersion: schemaversion.SchemaV21(), ShouldTerminateOnLastHandleClosed: true, VirtualMachine: &hcsschema.VirtualMachine{ - StopOnReset: true, - Chipset: &hcsschema.Chipset{ - Uefi: &hcsschema.Uefi{ - BootThis: &hcsschema.UefiBootEntry{ - DevicePath: filepath.Join(opts.BootFiles.OSRelativeBootDirPath, "bootmgfw.efi"), - DeviceType: "VmbFs", - }, - }, - }, + StopOnReset: true, RegistryChanges: ®istryChanges, ComputeTopology: &hcsschema.Topology{ Memory: &hcsschema.VirtualMachineMemory{ @@ -221,7 +226,6 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (* ServiceTable: make(map[string]hcsschema.HvSocketServiceConfig), }, }, - VirtualSmb: virtualSMB, }, }, } @@ -242,7 +246,21 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (* } } - // Set boot options + if opts.ConsolePipe != "" { + doc.VirtualMachine.Devices.ComPorts = map[string]hcsschema.ComPort{ + "0": { + NamedPipe: opts.ConsolePipe, + }, + } + } + + if opts.EnableGraphicsConsole { + doc.VirtualMachine.Devices.Keyboard = &hcsschema.Keyboard{} + doc.VirtualMachine.Devices.EnhancedModeVideo = &hcsschema.EnhancedModeVideo{} + doc.VirtualMachine.Devices.VideoMonitor = &hcsschema.VideoMonitor{} + } + + // Set crash dump options if opts.DumpDirectoryPath != "" { if info, err := os.Stat(opts.DumpDirectoryPath); err != nil { return nil, fmt.Errorf("failed to stat dump directory %s: %w", opts.DumpDirectoryPath, err) @@ -252,16 +270,166 @@ func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (* if err := security.GrantVmGroupAccessWithMask(opts.DumpDirectoryPath, security.AccessMaskAll); err != nil { return nil, fmt.Errorf("failed to add SDL to dump directory: %w", err) } - debugOpts := &hcsschema.DebugOptions{ + doc.VirtualMachine.DebugOptions = &hcsschema.DebugOptions{ BugcheckSavedStateFileName: filepath.Join(opts.DumpDirectoryPath, fmt.Sprintf("%s-savedstate.vmrs", uvm.id)), BugcheckNoCrashdumpSavedStateFileName: filepath.Join(opts.DumpDirectoryPath, fmt.Sprintf("%s-savedstate_nocrashdump.vmrs", uvm.id)), } - doc.VirtualMachine.DebugOptions = debugOpts + + doc.VirtualMachine.Devices.GuestCrashReporting = &hcsschema.GuestCrashReporting{ + WindowsCrashSettings: &hcsschema.WindowsCrashReporting{ + DumpFileName: filepath.Join(opts.DumpDirectoryPath, fmt.Sprintf("%s-windows-crash", uvm.id)), + DumpType: "Full", + }, + } + } + + doc.VirtualMachine.Devices.Scsi = map[string]hcsschema.Scsi{} + for i := 0; i < int(uvm.scsiControllerCount); i++ { + doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[i]] = hcsschema.Scsi{ + Attachments: make(map[string]hcsschema.Attachment), + } } return doc, nil } +func prepareSecurityConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (*hcsschema.ComputeSystem, error) { + if opts.BootFiles.BootType != BlockCIMBoot { + return nil, fmt.Errorf("expected BlockCIM boot type, found: %d", opts.BootFiles.BootType) + } + + doc, err := prepareConfigDocCommon(ctx, uvm, opts) + if err != nil { + return nil, err + } + + if opts.DisableSecureBoot { + doc.VirtualMachine.Chipset = &hcsschema.Chipset{ + Uefi: &hcsschema.Uefi{ + BootThis: nil, + ApplySecureBootTemplate: "Skip", + }, + } + } else { + doc.VirtualMachine.Chipset = &hcsschema.Chipset{ + Uefi: &hcsschema.Uefi{ + BootThis: nil, + ApplySecureBootTemplate: "Apply", + SecureBootTemplateId: "1734c6e8-3154-4dda-ba5f-a874cc483422", // aka MicrosoftWindowsSecureBootTemplateGUID equivalent to "Microsoft Windows" template from Get-VMHost | select SecureBootTemplates, + }, + } + } + + enableHCL := true + doc.VirtualMachine.SecuritySettings = &hcsschema.SecuritySettings{ + EnableTpm: false, + Isolation: &hcsschema.IsolationSettings{ + IsolationType: "SecureNestedPaging", + HclEnabled: &enableHCL, + }, + } + + if opts.IsolationType != "" { + doc.VirtualMachine.SecuritySettings.Isolation.IsolationType = opts.IsolationType + } + + doc.VirtualMachine.GuestState = &hcsschema.GuestState{ + GuestStateFilePath: opts.GuestStateFilePath, + GuestStateFileType: "BlockStorage", + } + + if opts.FirmwareParameters != "" { + doc.VirtualMachine.Chipset.FirmwareFile = &hcsschema.FirmwareFile{ + Parameters: []byte(opts.FirmwareParameters), + } + } + + memoryBacking := hcsschema.MemoryBackingType_PHYSICAL + doc.VirtualMachine.ComputeTopology.Memory.Backing = &memoryBacking + doc.SchemaVersion = schemaversion.SchemaV25() + doc.VirtualMachine.Version = &hcsschema.Version{ + Major: 11, + Minor: 0, + } + + if err := wclayer.GrantVmAccess(ctx, uvm.id, opts.BootFiles.BlockCIMFiles.BootCIMVHDPath); err != nil { + return nil, errors.Wrap(err, "failed to grant vm access to boot CIM VHD") + } + + if err := wclayer.GrantVmAccess(ctx, uvm.id, opts.BootFiles.BlockCIMFiles.EFIVHDPath); err != nil { + return nil, errors.Wrap(err, "failed to grant vm access to EFI VHD") + } + + if err := wclayer.GrantVmAccess(ctx, uvm.id, opts.BootFiles.BlockCIMFiles.ScratchVHDPath); err != nil { + return nil, errors.Wrap(err, "failed to grant vm access to scratch VHD") + } + + doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"] = hcsschema.Attachment{ + Path: opts.BootFiles.BlockCIMFiles.ScratchVHDPath, + Type_: "VirtualDisk", + } + doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["1"] = hcsschema.Attachment{ + Path: opts.BootFiles.BlockCIMFiles.EFIVHDPath, + Type_: "VirtualDisk", + } + doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["2"] = hcsschema.Attachment{ + Path: opts.BootFiles.BlockCIMFiles.BootCIMVHDPath, + Type_: "VirtualDisk", + } + + uvm.reservedSCSISlots = append(uvm.reservedSCSISlots, + scsi.Slot{Controller: 0, LUN: 0}, + scsi.Slot{Controller: 0, LUN: 1}, + scsi.Slot{Controller: 0, LUN: 2}) + + return doc, nil +} + +func prepareConfigDoc(ctx context.Context, uvm *UtilityVM, opts *OptionsWCOW) (*hcsschema.ComputeSystem, error) { + if opts.BootFiles.BootType != VmbFSBoot { + return nil, fmt.Errorf("expected VmbFS boot type, found: %d", opts.BootFiles.BootType) + } + + doc, err := prepareConfigDocCommon(ctx, uvm, opts) + if err != nil { + return nil, err + } + + vsmbOpts := uvm.DefaultVSMBOptions(true) + vsmbOpts.TakeBackupPrivilege = true + doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{ + DirectFileMappingInMB: 1024, // Sensible default, but could be a tuning parameter somewhere + Shares: []hcsschema.VirtualSmbShare{ + { + Name: "os", + Path: opts.BootFiles.VmbFSFiles.OSFilesPath, + Options: vsmbOpts, + }, + }, + } + + doc.VirtualMachine.Chipset = &hcsschema.Chipset{ + Uefi: &hcsschema.Uefi{ + BootThis: &hcsschema.UefiBootEntry{ + DevicePath: filepath.Join(opts.BootFiles.VmbFSFiles.OSRelativeBootDirPath, "bootmgfw.efi"), + DeviceType: "VmbFs", + }, + }, + } + + if err := wclayer.GrantVmAccess(ctx, uvm.id, opts.BootFiles.VmbFSFiles.ScratchVHDPath); err != nil { + return nil, errors.Wrap(err, "failed to grant vm access to scratch") + } + + doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"] = hcsschema.Attachment{ + Path: opts.BootFiles.VmbFSFiles.ScratchVHDPath, + Type_: "VirtualDisk", + } + uvm.reservedSCSISlots = append(uvm.reservedSCSISlots, scsi.Slot{Controller: 0, LUN: 0}) + + return doc, nil +} + // CreateWCOW creates an HCS compute system representing a utility VM. // // WCOW Notes: @@ -308,36 +476,29 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error return nil, errors.Wrap(err, errBadUVMOpts.Error()) } - doc, err := prepareConfigDoc(ctx, uvm, opts) + var doc *hcsschema.ComputeSystem + if opts.SecurityPolicyEnabled { + doc, err = prepareSecurityConfigDoc(ctx, uvm, opts) + log.G(ctx).Tracef("CreateWCOW prepareSecurityConfigDoc result doc: %v err %v", doc, err) + } else { + doc, err = prepareConfigDoc(ctx, uvm, opts) + log.G(ctx).Tracef("CreateWCOW prepareConfigDoc result doc: %v err %v", doc, err) + } if err != nil { return nil, fmt.Errorf("error in preparing config doc: %w", err) } - if err := wclayer.GrantVmAccess(ctx, uvm.id, opts.BootFiles.ScratchVHDPath); err != nil { - return nil, errors.Wrap(err, "failed to grant vm access to scratch") - } - - doc.VirtualMachine.Devices.Scsi = map[string]hcsschema.Scsi{} - for i := 0; i < int(uvm.scsiControllerCount); i++ { - doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[i]] = hcsschema.Scsi{ - Attachments: make(map[string]hcsschema.Attachment), - } - } - - doc.VirtualMachine.Devices.Scsi[guestrequest.ScsiControllerGuids[0]].Attachments["0"] = hcsschema.Attachment{ - - Path: opts.BootFiles.ScratchVHDPath, - Type_: "VirtualDisk", - } - - uvm.reservedSCSISlots = append(uvm.reservedSCSISlots, scsi.Slot{Controller: 0, LUN: 0}) - err = uvm.create(ctx, doc) if err != nil { return nil, fmt.Errorf("error while creating the compute system: %w", err) } - if err = uvm.startExternalGcsListener(ctx); err != nil { + if opts.SecurityPolicyEnabled { + err = uvm.startExternalGcsListener(ctx, windowsSidecarGcsHvsockServiceID) + } else { + err = uvm.startExternalGcsListener(ctx, gcs.WindowsGcsHvsockServiceID) + } + if err != nil { return nil, err } diff --git a/internal/uvm/types.go b/internal/uvm/types.go index 29e6d0040d..150b204999 100644 --- a/internal/uvm/types.go +++ b/internal/uvm/types.go @@ -153,7 +153,28 @@ type OutputHandler func(io.Reader) type OutputHandlerCreator func(*Options) OutputHandler +type WCOWBootFilesType uint8 + +const ( + VmbFSBoot WCOWBootFilesType = iota + BlockCIMBoot +) + +// WCOWBootFiles provides the files paths (and other data) required to configure boot of +// an UVM. This struct (more like a union) maintains a type variable to specify what kind +// of boot we are doing and then the struct applicable to that boot type will have the +// necessary data. All other fields should be null. (Maybe we can make this into an +// interface with a method that configures boot given the UVM HCS doc, but configuring +// boot requires access to the uvm struct itself to update the used SCSI mounts etc. and +// then the interface gets ugly...) type WCOWBootFiles struct { + BootType WCOWBootFilesType + VmbFSFiles *VmbFSBootFiles + BlockCIMFiles *BlockCIMBootFiles +} + +// files required to boot an UVM with layer files stored on NTFS in legacy (WCIFS) format. +type VmbFSBootFiles struct { // Path to the directory that contains the OS files. OSFilesPath string // Path of the boot directory relative to the `OSFilesPath`. This boot directory MUST @@ -162,3 +183,13 @@ type WCOWBootFiles struct { // Path for the scratch VHD of thef UVM ScratchVHDPath string } + +// files required to boot an UVM with the layer files stored in a block CIM. +type BlockCIMBootFiles struct { + // Path to the VHD that has a block CIM (which contains the OS files) on it. + BootCIMVHDPath string + // VHD that contains the EFI partition + EFIVHDPath string + // A non formatted scratch VHD + ScratchVHDPath string +}