Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions cmd/containerd-shim-lcow-v1/specs/boot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//go:build windows

package specs

import (
"context"
"fmt"
"os"
"path/filepath"

shimsandbox "github.com/Microsoft/hcsshim/api/sandbox/v1"
runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/oci"
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
"github.com/Microsoft/hcsshim/osversion"
shimannotations "github.com/Microsoft/hcsshim/pkg/annotations"

"github.com/sirupsen/logrus"
)

// resolveBootFilesPath resolves and validates the boot files root path.
func resolveBootFilesPath(ctx context.Context, opts *runhcsoptions.Options, annotations map[string]string) (string, error) {
// If the customer provides the boot files path then it is given preference over the default path.
// Similarly, based on the existing behavior in old shim, the annotation provided boot files path
// is given preference over those in runhcs options.
bootFilesRootPath := oci.ParseAnnotationsString(annotations, shimannotations.BootFilesRootPath, opts.BootFilesRootPath)
if bootFilesRootPath == "" {
bootFilesRootPath = vmutils.DefaultLCOWOSBootFilesPath()
}

if p, err := filepath.Abs(bootFilesRootPath); err == nil {
bootFilesRootPath = p
} else {
log.G(ctx).WithFields(logrus.Fields{
logfields.Path: bootFilesRootPath,
logrus.ErrorKey: err,
}).Warning("could not make boot files path absolute")
}

if _, err := os.Stat(bootFilesRootPath); err != nil {
return "", fmt.Errorf("boot_files_root_path %q not found: %w", bootFilesRootPath, err)
}

return bootFilesRootPath, nil
}

// parseBootOptions parses LCOW boot options from annotations and options.
// Returns the BootOptions proto and the full rootfs path.
func parseBootOptions(ctx context.Context, opts *runhcsoptions.Options, annotations map[string]string) (*shimsandbox.BootOptions, string, error) {
log.G(ctx).Debug("parseBootOptions: starting boot options parsing")

// Resolve and validate boot files path.
bootFilesPath, err := resolveBootFilesPath(ctx, opts, annotations)
if err != nil {
return nil, "", err
}

log.G(ctx).WithField(logfields.Path, bootFilesPath).Debug("using boot files path")

bootOptions := &shimsandbox.BootOptions{}

// Set the default rootfs to initrd.
rootFsFile := vmutils.InitrdFile

// Helper to check file existence in boot files path.
fileExists := func(filename string) bool {
_, err := os.Stat(filepath.Join(bootFilesPath, filename))
return err == nil
}

// Reset the default values based on the presence of files in the boot files path.
// We have a rootfs.vhd in the boot files path. Use it over an initrd.img
if fileExists(vmutils.VhdFile) {
rootFsFile = vmutils.VhdFile
log.G(ctx).WithField(
vmutils.VhdFile, filepath.Join(bootFilesPath, vmutils.VhdFile),
).Debug("updated LCOW root filesystem to " + vmutils.VhdFile)
}

// KernelDirect supports uncompressed kernel if the kernel is present.
// Default to uncompressed if on box. NOTE: If `kernel` is already
// uncompressed and simply named 'kernel' it will still be used
// uncompressed automatically.
kernelDirectBootSupported := osversion.Build() >= 18286
useKernelDirect := oci.ParseAnnotationsBool(ctx, annotations, shimannotations.KernelDirectBoot, kernelDirectBootSupported)

log.G(ctx).WithFields(logrus.Fields{
"kernelDirectSupported": kernelDirectBootSupported,
"useKernelDirect": useKernelDirect,
}).Debug("determined boot mode")

// If customer specifies kernel direct boot but the build does not support it, return an error.
if useKernelDirect && !kernelDirectBootSupported {
return nil, "", fmt.Errorf("KernelDirectBoot is not supported on builds older than 18286")
}

// Determine kernel file based on boot mode
var kernelFileName string
if useKernelDirect {
// KernelDirect supports uncompressed kernel if present.
if fileExists(vmutils.UncompressedKernelFile) {
kernelFileName = vmutils.UncompressedKernelFile
log.G(ctx).WithField(vmutils.UncompressedKernelFile, filepath.Join(bootFilesPath, vmutils.UncompressedKernelFile)).Debug("updated LCOW kernel file to " + vmutils.UncompressedKernelFile)
} else if fileExists(vmutils.KernelFile) {
kernelFileName = vmutils.KernelFile
} else {
return nil, "", fmt.Errorf("kernel file not found in boot files path for kernel direct boot")
}
} else {
kernelFileName = vmutils.KernelFile
if !fileExists(vmutils.KernelFile) {
return nil, "", fmt.Errorf("kernel file %q not found in boot files path: %w", vmutils.KernelFile, os.ErrNotExist)
}
}

log.G(ctx).WithField("kernelFile", kernelFileName).Debug("selected kernel file")

// Parse preferred rootfs type annotation. This overrides the default set above based on file presence.
if preferredRootfsType := oci.ParseAnnotationsString(annotations, shimannotations.PreferredRootFSType, ""); preferredRootfsType != "" {
log.G(ctx).WithField("preferredRootFSType", preferredRootfsType).Debug("applying preferred rootfs type override")
switch preferredRootfsType {
case "initrd":
rootFsFile = vmutils.InitrdFile
case "vhd":
rootFsFile = vmutils.VhdFile
default:
return nil, "", fmt.Errorf("invalid PreferredRootFSType: %s", preferredRootfsType)
}
if !fileExists(rootFsFile) {
return nil, "", fmt.Errorf("%q not found in boot files path", rootFsFile)
}
}

log.G(ctx).WithField("rootFsFile", rootFsFile).Debug("selected rootfs file")

// Get kernel boot options from annotations (will be incorporated into kernel cmd line later)
kernelBootOptions := oci.ParseAnnotationsString(annotations, shimannotations.KernelBootOptions, "")

// Set up boot configuration based on boot mode
if useKernelDirect {
log.G(ctx).Debug("configuring kernel direct boot")
bootOptions.LinuxKernelDirect = &shimsandbox.LinuxKernelDirect{
KernelFilePath: filepath.Join(bootFilesPath, kernelFileName),
// KernelCmdLine will be populated later by buildKernelArgs
}
if rootFsFile == vmutils.InitrdFile {
bootOptions.LinuxKernelDirect.InitRdPath = filepath.Join(bootFilesPath, rootFsFile)
log.G(ctx).WithField("initrdPath", bootOptions.LinuxKernelDirect.InitRdPath).Debug("configured initrd for kernel direct boot")
}
// Store kernel boot options temporarily in KernelCmdLine; will be appended to full args later
bootOptions.LinuxKernelDirect.KernelCmdLine = kernelBootOptions
} else {
// UEFI boot
log.G(ctx).Debug("configuring UEFI boot")
bootOptions.Uefi = &shimsandbox.UEFI{
BootThis: &shimsandbox.UefiBootEntry{
DevicePath: `\` + kernelFileName,
DeviceType: "VmbFs",
VmbFsRootPath: bootFilesPath,
// OptionalData will be populated later by buildKernelArgs
OptionalData: kernelBootOptions,
},
}
}

rootFsFullPath := filepath.Join(bootFilesPath, rootFsFile)
log.G(ctx).WithFields(logrus.Fields{
"rootFsFullPath": rootFsFullPath,
"kernelFilePath": filepath.Join(bootFilesPath, kernelFileName),
"useKernelDirect": useKernelDirect,
"kernelBootOptions": kernelBootOptions,
}).Info("boot options configured successfully")

return bootOptions, rootFsFullPath, nil
}
133 changes: 133 additions & 0 deletions cmd/containerd-shim-lcow-v1/specs/confidential.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//go:build windows

package specs

import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"

shimsandbox "github.com/Microsoft/hcsshim/api/sandbox/v1"
runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/oci"
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
shimannotations "github.com/Microsoft/hcsshim/pkg/annotations"
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
)

// parseConfidentialOptions parses LCOW confidential options from annotations.
// This should only be called for confidential scenarios.
func parseConfidentialOptions(
ctx context.Context,
opts *runhcsoptions.Options,
annotations map[string]string,
boot *shimsandbox.BootOptions,
mem *shimsandbox.MemoryConfig,
) (*shimsandbox.ConfidentialOptions, error) {

log.G(ctx).Debug("parseConfidentialOptions: starting confidential options parsing")

confidentialOptions := &shimsandbox.ConfidentialOptions{}
confidentialOptions.SecurityPolicy = oci.ParseAnnotationsString(annotations, shimannotations.LCOWSecurityPolicy, "")
confidentialOptions.SecurityPolicyEnforcer = oci.ParseAnnotationsString(annotations, shimannotations.LCOWSecurityPolicyEnforcer, "")
confidentialOptions.UvmReferenceInfoFile = oci.ParseAnnotationsString(annotations, shimannotations.LCOWReferenceInfoFile, vmutils.DefaultUVMReferenceInfoFile)

// Resolve boot files path for confidential mode
bootFilesPath, err := resolveBootFilesPath(ctx, opts, annotations)
if err != nil {
return nil, fmt.Errorf("failed to resolve boot files path for confidential VM: %w", err)
}

// Set the default GuestState filename.
// The kernel and minimal initrd are combined into a single vmgs file.
guestStateFile := vmutils.DefaultGuestStateFile

// Allow override from annotation
if annotationGuestStateFile := oci.ParseAnnotationsString(annotations, shimannotations.LCOWGuestStateFile, ""); annotationGuestStateFile != "" {
guestStateFile = annotationGuestStateFile
}

// Validate the VMGS template file exists and save the full path
vmgsTemplatePath := filepath.Join(bootFilesPath, guestStateFile)
if _, err := os.Stat(vmgsTemplatePath); os.IsNotExist(err) {
return nil, fmt.Errorf("the GuestState vmgs file '%s' was not found", vmgsTemplatePath)
}
confidentialOptions.VmgsTemplatePath = vmgsTemplatePath
log.G(ctx).WithField("vmgsTemplatePath", vmgsTemplatePath).Debug("VMGS template path configured")

// Set default DmVerity rootfs VHD.
// The root file system comes from the dmverity vhd file which is mounted by the initrd in the vmgs file.
dmVerityRootfsFile := vmutils.DefaultDmVerityRootfsVhd

// Allow override from annotation
if annotationDmVerityRootFsVhd := oci.ParseAnnotationsString(annotations, shimannotations.DmVerityRootFsVhd, ""); annotationDmVerityRootFsVhd != "" {
dmVerityRootfsFile = annotationDmVerityRootFsVhd
}

// Validate the DmVerity rootfs VHD file exists and save the full path
dmVerityRootfsTemplatePath := filepath.Join(bootFilesPath, dmVerityRootfsFile)
if _, err := os.Stat(dmVerityRootfsTemplatePath); os.IsNotExist(err) {
return nil, fmt.Errorf("the DM Verity VHD file '%s' was not found", dmVerityRootfsTemplatePath)
}
confidentialOptions.DmVerityRootfsTemplatePath = dmVerityRootfsTemplatePath
log.G(ctx).WithField("dmVerityRootfsPath", dmVerityRootfsTemplatePath).Debug("DM Verity rootfs path configured")

// Note: VPMem and vPCI assigned devices are already disabled in parseDeviceOptions
// when isConfidential is true.

// Required by HCS for the isolated boot scheme, see also https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/learn-more/generation-2-virtual-machine-security-settings-for-hyper-v
// A complete explanation of the why's and wherefores of starting an encrypted, isolated VM are beond the scope of these comments.
log.G(ctx).Debug("configuring UEFI secure boot for confidential VM")
boot.Uefi = &shimsandbox.UEFI{
ApplySecureBootTemplate: "Apply",
// aka MicrosoftWindowsSecureBootTemplateGUID equivalent to "Microsoft Windows" template from Get-VMHost | select SecureBootTemplates
SecureBootTemplateID: "1734c6e8-3154-4dda-ba5f-a874cc483422",
}
// Clear any existing boot options to only have UEFI secure boot configuration.
boot.LinuxKernelDirect = nil

// Set memory to physical backing (no overcommit) for confidential VMs
log.G(ctx).Debug("disabling memory overcommit for confidential VM")
mem.AllowOvercommit = false

// Part of the protocol to ensure that the rules in the user's Security Policy are
// respected is to provide a hash of the policy to the hardware. This is immutable
// and can be used to check that the policy used by opengcs is the required one as
// a condition of releasing secrets to the container.
log.G(ctx).Debug("creating security policy digest")
policyDigest, err := securitypolicy.NewSecurityPolicyDigest(confidentialOptions.SecurityPolicy)
if err != nil {
return nil, fmt.Errorf("failed to create security policy digest: %w", err)
}

// HCS API expects a base64 encoded string as LaunchData. Internally it
// decodes it to bytes. SEV later returns the decoded byte blob as HostData
// field of the report.
hostData := base64.StdEncoding.EncodeToString(policyDigest)

// Put the measurement into the LaunchData field of the HCS creation command.
// This will end-up in HOST_DATA of SNP_LAUNCH_FINISH command and the ATTESTATION_REPORT
// retrieved by the guest later.
confidentialOptions.SecuritySettings = &shimsandbox.SecuritySettings{
EnableTpm: false,
Isolation: &shimsandbox.IsolationSettings{
IsolationType: "SecureNestedPaging",
LaunchData: hostData,
// HclEnabled: true, /* Not available in schema 2.5 - REQUIRED when using BlockStorage in 2.6 */
HclEnabled: oci.ParseAnnotationsNullableBool(ctx, annotations, shimannotations.LCOWHclEnabled),
},
}

// Set default UVM reference info file if not set
if confidentialOptions.UvmReferenceInfoFile == "" {
confidentialOptions.UvmReferenceInfoFile = vmutils.DefaultUVMReferenceInfoFile
}

// Note: HvSocket service table for confidential VMs is configured in parseAdditionalOptions.

log.G(ctx).Info("confidential options configured successfully")
return confidentialOptions, nil
}
Loading
Loading