From ecb72663f3636689ae8102dd48aa975090003566 Mon Sep 17 00:00:00 2001 From: Anish Bhatt Date: Tue, 27 Oct 2020 01:11:39 -0700 Subject: [PATCH] Add support for creating shielded VMs to GCP --- .../googlecompute-import/post-processor.go | 100 ++++++++++++++++-- .../post-processor.hcl2spec.go | 92 ++++++++-------- .../Config-not-required.mdx | 10 +- 3 files changed, 149 insertions(+), 53 deletions(-) diff --git a/post-processor/googlecompute-import/post-processor.go b/post-processor/googlecompute-import/post-processor.go index c86ddbac3..5d8e8c61a 100644 --- a/post-processor/googlecompute-import/post-processor.go +++ b/post-processor/googlecompute-import/post-processor.go @@ -5,7 +5,11 @@ package googlecomputeimport import ( "context" + "crypto/x509" + "encoding/base64" + "encoding/pem" "fmt" + "io/ioutil" "os" "strings" "time" @@ -48,7 +52,7 @@ type Config struct { //The name of the image family to which the resulting image belongs. ImageFamily string `mapstructure:"image_family"` //A list of features to enable on the guest operating system. Applicable only for bootable images. Valid - //values are `MULTI_IP_SUBNET`, `SECURE_BOOT`, `UEFI_COMPATIBLE`, + //values are `MULTI_IP_SUBNET`, `UEFI_COMPATIBLE`, //`VIRTIO_SCSI_MULTIQUEUE` and `WINDOWS` currently. ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features"` //Key/value pair labels to apply to the created image. @@ -61,6 +65,14 @@ type Config struct { //`false`. SkipClean bool `mapstructure:"skip_clean"` VaultGCPOauthEngine string `mapstructure:"vault_gcp_oauth_engine"` + //A key used to establish the trust relationship between the platform owner and the firmware. You may only specify one platform key, and it must be a valid X.509 certificate. + ImagePlatformKey string `mapstructure:"image_platform_key"` + //A key used to establish a trust relationship between the firmware and the OS. You may specify multiple comma-separated keys for this value. + ImageKeyExchangeKey []string `mapstructure:"image_key_exchange_key"` + //A database of certificates that have been revoked and will cause the system to stop booting if a boot file is signed with one of them. You may specify single or multiple comma-separated values for this value. + ImageSignaturesDB []string `mapstructure:"image_signatures_db"` + //A database of certificates that are trusted and can be used to sign boot files. You may specify single or multiple comma-separated values for this value. + ImageForbiddenSignaturesDB []string `mapstructure:"image_forbidden_signatures_db"` account *googlecompute.ServiceAccount ctx interpolate.Context @@ -165,7 +177,12 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact return nil, false, false, err } - gceImageArtifact, err := CreateGceImage(opts, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures) + shieldedVMStateConfig, err := CreateShieldedVMStateConfig(p.config.ImageGuestOsFeatures, p.config.ImagePlatformKey, p.config.ImageKeyExchangeKey, p.config.ImageSignaturesDB, p.config.ImageForbiddenSignaturesDB) + if err != nil { + return nil, false, false, err + } + + gceImageArtifact, err := CreateGceImage(opts, ui, p.config.ProjectId, rawImageGcsPath, p.config.ImageName, p.config.ImageDescription, p.config.ImageFamily, p.config.ImageLabels, p.config.ImageGuestOsFeatures, shieldedVMStateConfig) if err != nil { return nil, false, false, err } @@ -180,6 +197,68 @@ func (p *PostProcessor) PostProcess(ctx context.Context, ui packer.Ui, artifact return gceImageArtifact, false, false, nil } +func FillFileContentBuffer(certOrKeyFile string) (*compute.FileContentBuffer, error) { + data, err := ioutil.ReadFile(certOrKeyFile) + if err != nil { + err := fmt.Errorf("Unable to read Certificate or Key file %s", certOrKeyFile) + return nil, err + } + shield := &compute.FileContentBuffer{ + Content: base64.StdEncoding.EncodeToString(data), + FileType: "X509", + } + block, _ := pem.Decode(data) + + if block == nil || block.Type != "CERTIFICATE" { + _, err = x509.ParseCertificate(data) + } else { + _, err = x509.ParseCertificate(block.Bytes) + } + if err != nil { + shield.FileType = "BIN" + } + return shield, nil + +} + +func CreateShieldedVMStateConfig(imageGuestOsFeatures []string, imagePlatformKey string, imageKeyExchangeKey []string, imageSignaturesDB []string, imageForbiddenSignaturesDB []string) (*compute.InitialStateConfig, error) { + shieldedVMStateConfig := &compute.InitialStateConfig{} + for _, v := range imageGuestOsFeatures { + if v == "UEFI_COMPATIBLE" { + if imagePlatformKey != "" { + shieldedData, err := FillFileContentBuffer(imagePlatformKey) + if err != nil { + return nil, err + } + shieldedVMStateConfig.Pk = shieldedData + } + for _, v := range imageKeyExchangeKey { + shieldedData, err := FillFileContentBuffer(v) + if err != nil { + return nil, err + } + shieldedVMStateConfig.Keks = append(shieldedVMStateConfig.Keks, shieldedData) + } + for _, v := range imageSignaturesDB { + shieldedData, err := FillFileContentBuffer(v) + if err != nil { + return nil, err + } + shieldedVMStateConfig.Dbs = append(shieldedVMStateConfig.Dbs, shieldedData) + } + for _, v := range imageForbiddenSignaturesDB { + shieldedData, err := FillFileContentBuffer(v) + if err != nil { + return nil, err + } + shieldedVMStateConfig.Dbxs = append(shieldedVMStateConfig.Dbxs, shieldedData) + } + + } + } + return shieldedVMStateConfig, nil +} + func UploadToBucket(opts option.ClientOption, ui packer.Ui, artifact packer.Artifact, bucket string, gcsObjectName string) (string, error) { service, err := storage.NewService(context.TODO(), opts) if err != nil { @@ -216,7 +295,7 @@ func UploadToBucket(opts option.ClientOption, ui packer.Ui, artifact packer.Arti return storageObject.SelfLink, nil } -func CreateGceImage(opts option.ClientOption, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string) (packer.Artifact, error) { +func CreateGceImage(opts option.ClientOption, ui packer.Ui, project string, rawImageURL string, imageName string, imageDescription string, imageFamily string, imageLabels map[string]string, imageGuestOsFeatures []string, shieldedVMStateConfig *compute.InitialStateConfig) (packer.Artifact, error) { service, err := compute.NewService(context.TODO(), opts) if err != nil { @@ -232,13 +311,14 @@ func CreateGceImage(opts option.ClientOption, ui packer.Ui, project string, rawI } gceImage := &compute.Image{ - Description: imageDescription, - Family: imageFamily, - GuestOsFeatures: imageFeatures, - Labels: imageLabels, - Name: imageName, - RawDisk: &compute.ImageRawDisk{Source: rawImageURL}, - SourceType: "RAW", + Description: imageDescription, + Family: imageFamily, + GuestOsFeatures: imageFeatures, + Labels: imageLabels, + Name: imageName, + RawDisk: &compute.ImageRawDisk{Source: rawImageURL}, + SourceType: "RAW", + ShieldedInstanceInitialState: shieldedVMStateConfig, } ui.Say(fmt.Sprintf("Creating GCE image %v...", imageName)) diff --git a/post-processor/googlecompute-import/post-processor.hcl2spec.go b/post-processor/googlecompute-import/post-processor.hcl2spec.go index aeed85a0f..2c0a726d2 100644 --- a/post-processor/googlecompute-import/post-processor.hcl2spec.go +++ b/post-processor/googlecompute-import/post-processor.hcl2spec.go @@ -9,27 +9,31 @@ import ( // FlatConfig is an auto-generated flat version of Config. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatConfig struct { - PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` - PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` - PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` - PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` - PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` - PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` - PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` - PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` - AccountFile *string `mapstructure:"account_file" required:"true" cty:"account_file" hcl:"account_file"` - ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"` - ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"` - IAP *bool `mapstructure-to-hcl:",skip" cty:"iap" hcl:"iap"` - Bucket *string `mapstructure:"bucket" required:"true" cty:"bucket" hcl:"bucket"` - GCSObjectName *string `mapstructure:"gcs_object_name" cty:"gcs_object_name" hcl:"gcs_object_name"` - ImageDescription *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"` - ImageFamily *string `mapstructure:"image_family" cty:"image_family" hcl:"image_family"` - ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features" cty:"image_guest_os_features" hcl:"image_guest_os_features"` - ImageLabels map[string]string `mapstructure:"image_labels" cty:"image_labels" hcl:"image_labels"` - ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"` - SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"` - VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"` + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + AccountFile *string `mapstructure:"account_file" required:"true" cty:"account_file" hcl:"account_file"` + ImpersonateServiceAccount *string `mapstructure:"impersonate_service_account" required:"false" cty:"impersonate_service_account" hcl:"impersonate_service_account"` + ProjectId *string `mapstructure:"project_id" required:"true" cty:"project_id" hcl:"project_id"` + IAP *bool `mapstructure-to-hcl:",skip" cty:"iap" hcl:"iap"` + Bucket *string `mapstructure:"bucket" required:"true" cty:"bucket" hcl:"bucket"` + GCSObjectName *string `mapstructure:"gcs_object_name" cty:"gcs_object_name" hcl:"gcs_object_name"` + ImageDescription *string `mapstructure:"image_description" cty:"image_description" hcl:"image_description"` + ImageFamily *string `mapstructure:"image_family" cty:"image_family" hcl:"image_family"` + ImageGuestOsFeatures []string `mapstructure:"image_guest_os_features" cty:"image_guest_os_features" hcl:"image_guest_os_features"` + ImageLabels map[string]string `mapstructure:"image_labels" cty:"image_labels" hcl:"image_labels"` + ImageName *string `mapstructure:"image_name" required:"true" cty:"image_name" hcl:"image_name"` + SkipClean *bool `mapstructure:"skip_clean" cty:"skip_clean" hcl:"skip_clean"` + VaultGCPOauthEngine *string `mapstructure:"vault_gcp_oauth_engine" cty:"vault_gcp_oauth_engine" hcl:"vault_gcp_oauth_engine"` + ImagePlatformKey *string `mapstructure:"image_platform_key" cty:"image_platform_key" hcl:"image_platform_key"` + ImageKeyExchangeKey []string `mapstructure:"image_key_exchange_key" cty:"image_key_exchange_key" hcl:"image_key_exchange_key"` + ImageSignaturesDB []string `mapstructure:"image_signatures_db" cty:"image_signatures_db" hcl:"image_signatures_db"` + ImageForbiddenSignaturesDB []string `mapstructure:"image_forbidden_signatures_db" cty:"image_forbidden_signatures_db" hcl:"image_forbidden_signatures_db"` } // FlatMapstructure returns a new FlatConfig. @@ -44,27 +48,31 @@ func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } // The decoded values from this spec will then be applied to a FlatConfig. func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ - "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, - "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, - "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, - "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, - "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, - "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, - "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, - "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, - "account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false}, - "impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false}, - "project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false}, - "iap": &hcldec.AttrSpec{Name: "iap", Type: cty.Bool, Required: false}, - "bucket": &hcldec.AttrSpec{Name: "bucket", Type: cty.String, Required: false}, - "gcs_object_name": &hcldec.AttrSpec{Name: "gcs_object_name", Type: cty.String, Required: false}, - "image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false}, - "image_family": &hcldec.AttrSpec{Name: "image_family", Type: cty.String, Required: false}, - "image_guest_os_features": &hcldec.AttrSpec{Name: "image_guest_os_features", Type: cty.List(cty.String), Required: false}, - "image_labels": &hcldec.AttrSpec{Name: "image_labels", Type: cty.Map(cty.String), Required: false}, - "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, - "skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false}, - "vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false}, + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "account_file": &hcldec.AttrSpec{Name: "account_file", Type: cty.String, Required: false}, + "impersonate_service_account": &hcldec.AttrSpec{Name: "impersonate_service_account", Type: cty.String, Required: false}, + "project_id": &hcldec.AttrSpec{Name: "project_id", Type: cty.String, Required: false}, + "iap": &hcldec.AttrSpec{Name: "iap", Type: cty.Bool, Required: false}, + "bucket": &hcldec.AttrSpec{Name: "bucket", Type: cty.String, Required: false}, + "gcs_object_name": &hcldec.AttrSpec{Name: "gcs_object_name", Type: cty.String, Required: false}, + "image_description": &hcldec.AttrSpec{Name: "image_description", Type: cty.String, Required: false}, + "image_family": &hcldec.AttrSpec{Name: "image_family", Type: cty.String, Required: false}, + "image_guest_os_features": &hcldec.AttrSpec{Name: "image_guest_os_features", Type: cty.List(cty.String), Required: false}, + "image_labels": &hcldec.AttrSpec{Name: "image_labels", Type: cty.Map(cty.String), Required: false}, + "image_name": &hcldec.AttrSpec{Name: "image_name", Type: cty.String, Required: false}, + "skip_clean": &hcldec.AttrSpec{Name: "skip_clean", Type: cty.Bool, Required: false}, + "vault_gcp_oauth_engine": &hcldec.AttrSpec{Name: "vault_gcp_oauth_engine", Type: cty.String, Required: false}, + "image_platform_key": &hcldec.AttrSpec{Name: "image_platform_key", Type: cty.String, Required: false}, + "image_key_exchange_key": &hcldec.AttrSpec{Name: "image_key_exchange_key", Type: cty.List(cty.String), Required: false}, + "image_signatures_db": &hcldec.AttrSpec{Name: "image_signatures_db", Type: cty.List(cty.String), Required: false}, + "image_forbidden_signatures_db": &hcldec.AttrSpec{Name: "image_forbidden_signatures_db", Type: cty.List(cty.String), Required: false}, } return s } diff --git a/website/pages/partials/post-processor/googlecompute-import/Config-not-required.mdx b/website/pages/partials/post-processor/googlecompute-import/Config-not-required.mdx index aa3a5243e..bacbd84e5 100644 --- a/website/pages/partials/post-processor/googlecompute-import/Config-not-required.mdx +++ b/website/pages/partials/post-processor/googlecompute-import/Config-not-required.mdx @@ -13,7 +13,7 @@ - `image_family` (string) - The name of the image family to which the resulting image belongs. - `image_guest_os_features` ([]string) - A list of features to enable on the guest operating system. Applicable only for bootable images. Valid - values are `MULTI_IP_SUBNET`, `SECURE_BOOT`, `UEFI_COMPATIBLE`, + values are `MULTI_IP_SUBNET`, `UEFI_COMPATIBLE`, `VIRTIO_SCSI_MULTIQUEUE` and `WINDOWS` currently. - `image_labels` (map[string]string) - Key/value pair labels to apply to the created image. @@ -24,3 +24,11 @@ `false`. - `vault_gcp_oauth_engine` (string) - Vault GCP Oauth Engine + +- `image_platform_key` (string) - A key used to establish the trust relationship between the platform owner and the firmware. You may only specify one platform key, and it must be a valid X.509 certificate. + +- `image_key_exchange_key` ([]string) - A key used to establish a trust relationship between the firmware and the OS. You may specify multiple comma-separated keys for this value. + +- `image_signatures_db` ([]string) - A database of certificates that have been revoked and will cause the system to stop booting if a boot file is signed with one of them. You may specify single or multiple comma-separated values for this value. + +- `image_forbidden_signatures_db` ([]string) - A database of certificates that are trusted and can be used to sign boot files. You may specify single or multiple comma-separated values for this value.