From a51f40f68b9f2aba0b7830fcbffb5502f1517ab6 Mon Sep 17 00:00:00 2001 From: Petr Fedchenkov Date: Tue, 14 Jun 2022 17:29:00 +0300 Subject: [PATCH] Support for vTPM on GCP Add options to support vTPM-enabled VMs on GCP Signed-off-by: Petr Fedchenkov --- src/cmd/linuxkit/gcp.go | 64 +++++++++++++++++++++++++++++++----- src/cmd/linuxkit/push_gcp.go | 3 +- src/cmd/linuxkit/run_gcp.go | 3 +- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/cmd/linuxkit/gcp.go b/src/cmd/linuxkit/gcp.go index ef2238f64..8ace32ce7 100644 --- a/src/cmd/linuxkit/gcp.go +++ b/src/cmd/linuxkit/gcp.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "net/http" "os" + "strings" "time" "github.com/docker/docker/pkg/term" @@ -20,8 +21,13 @@ import ( "google.golang.org/api/storage/v1" ) -const pollingInterval = 500 * time.Millisecond -const timeout = 300 +const ( + pollingInterval = 500 * time.Millisecond + timeout = 300 + + uefiCompatibleFeature = "UEFI_COMPATIBLE" + vmxImageLicence = "projects/vm-options/global/licenses/enable-vmx" +) // GCPClient contains state required for communication with GCP type GCPClient struct { @@ -125,8 +131,8 @@ func (g GCPClient) UploadFile(src, dst, bucketName string, public bool) error { return nil } -// CreateImage creates a GCP image using the a source from Google Storage -func (g GCPClient) CreateImage(name, storageURL, family string, nested, replace bool) error { +// CreateImage creates a GCP image using the source from Google Storage +func (g GCPClient) CreateImage(name, storageURL, family string, nested, uefi, replace bool) error { if replace { if err := g.DeleteImage(name); err != nil { return err @@ -146,7 +152,13 @@ func (g GCPClient) CreateImage(name, storageURL, family string, nested, replace } if nested { - imgObj.Licenses = []string{"projects/vm-options/global/licenses/enable-vmx"} + imgObj.Licenses = []string{vmxImageLicence} + } + + if uefi { + imgObj.GuestOsFeatures = []*compute.GuestOsFeature{ + {Type: uefiCompatibleFeature}, + } } op, err := g.compute.Images.Insert(g.projectName, imgObj).Do() @@ -185,7 +197,7 @@ func (g GCPClient) DeleteImage(name string) error { } // CreateInstance creates and starts an instance on GCP -func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks Disks, data *string, nested, replace bool) error { +func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks Disks, data *string, nested, vtpm, replace bool) error { if replace { if err := g.DeleteInstance(name, zone, true); err != nil { return err @@ -204,6 +216,34 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D sshKey := new(string) *sshKey = fmt.Sprintf("moby:%s moby", string(ssh.MarshalAuthorizedKey(k))) + // check provided image to be compatible with provided options + op, err := g.compute.Images.Get(g.projectName, image).Do() + if err != nil { + return err + } + uefiCompatible := false + for _, feature := range op.GuestOsFeatures { + if feature != nil && feature.Type == uefiCompatibleFeature { + uefiCompatible = true + break + } + } + if vtpm && !uefiCompatible { + return fmt.Errorf("cannot use vTPM without UEFI_COMPATIBLE image") + } + // we should check for nested + vmxLicense := false + for _, license := range op.Licenses { + // we omit hostname and version when define license + if strings.HasSuffix(license, vmxImageLicence) { + vmxLicense = true + break + } + } + if nested && !vmxLicense { + return fmt.Errorf("cannot use nested virtualization without enable-vmx image") + } + instanceDisks := []*compute.AttachedDisk{ { AutoDelete: true, @@ -227,7 +267,13 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D } else { diskSizeGb = int64(convertMBtoGB(disk.Size)) } - diskOp, err := g.compute.Disks.Insert(g.projectName, zone, &compute.Disk{Name: diskName, SizeGb: diskSizeGb}).Do() + diskObj := &compute.Disk{Name: diskName, SizeGb: diskSizeGb} + if vtpm { + diskObj.GuestOsFeatures = []*compute.GuestOsFeature{ + {Type: uefiCompatibleFeature}, + } + } + diskOp, err := g.compute.Disks.Insert(g.projectName, zone, diskObj).Do() if err != nil { return err } @@ -274,9 +320,11 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D } if nested { - // TODO(rn): We could/should check here if the image has nested virt enabled instanceObj.MinCpuPlatform = "Intel Haswell" } + if vtpm { + instanceObj.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{EnableVtpm: true} + } // Don't wait for operation to complete! // A headstart is needed as by the time we've polled for this event to be diff --git a/src/cmd/linuxkit/push_gcp.go b/src/cmd/linuxkit/push_gcp.go index 884e08c85..b3d8d75d5 100644 --- a/src/cmd/linuxkit/push_gcp.go +++ b/src/cmd/linuxkit/push_gcp.go @@ -26,6 +26,7 @@ func pushGcp(args []string) { familyFlag := flags.String("family", "", "GCP Image Family. A group of images where the family name points to the most recent image. *Optional*") nameFlag := flags.String("img-name", "", "Overrides the name used to identify the file in Google Storage and the VM image. Defaults to the base of 'path' with the '.img.tar.gz' suffix removed") nestedVirt := flags.Bool("nested-virt", false, "Enabled nested virtualization for the image") + uefiCompatible := flags.Bool("uefi-compatible", false, "Enable UEFI_COMPATIBLE feature for the image, required to enable vTPM.") if err := flags.Parse(args); err != nil { log.Fatal("Unable to parse args") @@ -65,7 +66,7 @@ func pushGcp(args []string) { if err != nil { log.Fatalf("Error copying to Google Storage: %v", err) } - err = client.CreateImage(name, "https://storage.googleapis.com/"+bucket+"/"+name+suffix, family, *nestedVirt, true) + err = client.CreateImage(name, "https://storage.googleapis.com/"+bucket+"/"+name+suffix, family, *nestedVirt, *uefiCompatible, true) if err != nil { log.Fatalf("Error creating Google Compute Image: %v", err) } diff --git a/src/cmd/linuxkit/run_gcp.go b/src/cmd/linuxkit/run_gcp.go index d764e2d9d..4e6e3674f 100644 --- a/src/cmd/linuxkit/run_gcp.go +++ b/src/cmd/linuxkit/run_gcp.go @@ -46,6 +46,7 @@ func runGcp(args []string) { skipCleanup := flags.Bool("skip-cleanup", false, "Don't remove images or VMs") nestedVirt := flags.Bool("nested-virt", false, "Enabled nested virtualization") + vTPM := flags.Bool("vtpm", false, "Enable vTPM device") data := flags.String("data", "", "String of metadata to pass to VM; error to specify both -data and -data-file") dataPath := flags.String("data-file", "", "Path to file containing metadata to pass to VM; error to specify both -data and -data-file") @@ -87,7 +88,7 @@ func runGcp(args []string) { log.Fatalf("Unable to connect to GCP: %v", err) } - if err = client.CreateInstance(*name, image, zone, machine, disks, data, *nestedVirt, true); err != nil { + if err = client.CreateInstance(*name, image, zone, machine, disks, data, *nestedVirt, *vTPM, true); err != nil { log.Fatal(err) }