Add support for creating shielded VMs to GCP

This commit is contained in:
Anish Bhatt 2020-10-27 01:11:39 -07:00
parent 8350ade7ef
commit ecb72663f3
3 changed files with 149 additions and 53 deletions

View File

@ -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))

View File

@ -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
}

View File

@ -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.