Merge pull request #3790 from giggsoff/support-gcp-vtpm

Support for vTPM on GCP
This commit is contained in:
Avi Deitcher 2022-06-21 18:43:09 +02:00 committed by GitHub
commit 1073b2dd8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 10 deletions

View File

@ -8,6 +8,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"strings"
"time" "time"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
@ -20,8 +21,13 @@ import (
"google.golang.org/api/storage/v1" "google.golang.org/api/storage/v1"
) )
const pollingInterval = 500 * time.Millisecond const (
const timeout = 300 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 // GCPClient contains state required for communication with GCP
type GCPClient struct { type GCPClient struct {
@ -125,8 +131,8 @@ func (g GCPClient) UploadFile(src, dst, bucketName string, public bool) error {
return nil return nil
} }
// CreateImage creates a GCP image using the a source from Google Storage // CreateImage creates a GCP image using the source from Google Storage
func (g GCPClient) CreateImage(name, storageURL, family string, nested, replace bool) error { func (g GCPClient) CreateImage(name, storageURL, family string, nested, uefi, replace bool) error {
if replace { if replace {
if err := g.DeleteImage(name); err != nil { if err := g.DeleteImage(name); err != nil {
return err return err
@ -146,7 +152,13 @@ func (g GCPClient) CreateImage(name, storageURL, family string, nested, replace
} }
if nested { 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() 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 // 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 replace {
if err := g.DeleteInstance(name, zone, true); err != nil { if err := g.DeleteInstance(name, zone, true); err != nil {
return err return err
@ -204,6 +216,34 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D
sshKey := new(string) sshKey := new(string)
*sshKey = fmt.Sprintf("moby:%s moby", string(ssh.MarshalAuthorizedKey(k))) *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{ instanceDisks := []*compute.AttachedDisk{
{ {
AutoDelete: true, AutoDelete: true,
@ -227,7 +267,13 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D
} else { } else {
diskSizeGb = int64(convertMBtoGB(disk.Size)) 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 { if err != nil {
return err return err
} }
@ -274,9 +320,11 @@ func (g GCPClient) CreateInstance(name, image, zone, machineType string, disks D
} }
if nested { if nested {
// TODO(rn): We could/should check here if the image has nested virt enabled
instanceObj.MinCpuPlatform = "Intel Haswell" instanceObj.MinCpuPlatform = "Intel Haswell"
} }
if vtpm {
instanceObj.ShieldedInstanceConfig = &compute.ShieldedInstanceConfig{EnableVtpm: true}
}
// Don't wait for operation to complete! // Don't wait for operation to complete!
// A headstart is needed as by the time we've polled for this event to be // A headstart is needed as by the time we've polled for this event to be

View File

@ -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*") 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") 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") 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 { if err := flags.Parse(args); err != nil {
log.Fatal("Unable to parse args") log.Fatal("Unable to parse args")
@ -65,7 +66,7 @@ func pushGcp(args []string) {
if err != nil { if err != nil {
log.Fatalf("Error copying to Google Storage: %v", err) 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 { if err != nil {
log.Fatalf("Error creating Google Compute Image: %v", err) log.Fatalf("Error creating Google Compute Image: %v", err)
} }

View File

@ -46,6 +46,7 @@ func runGcp(args []string) {
skipCleanup := flags.Bool("skip-cleanup", false, "Don't remove images or VMs") skipCleanup := flags.Bool("skip-cleanup", false, "Don't remove images or VMs")
nestedVirt := flags.Bool("nested-virt", false, "Enabled nested virtualization") 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") 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") 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) 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) log.Fatal(err)
} }